Generic Functions in TypeScript

A generic function captures the type of its arguments and uses that type for parameters, return values, and internal logic. Learn how to write reusable functions that preserve type information.

6 min read

A TypeScript generic function remembers the type it receives, so instead of writing separate functions for each type, you write one function that works with any type. The key idea is a type parameter: it holds a type instead of a value, and TypeScript fills it in with the actual type each time you call the function.

Here is the smallest useful generic function:

typescripttypescript
function identity<T>(value: T): T {
  return value;
}
 
const a = identity("hello");
const b = identity(42);

The letter T is the type parameter. TypeScript sets it to string for the first call and number for the second call, so the return type matches the argument type exactly: a is a string and b is a number.

Compare that to the same function written with any:

typescripttypescript
function identityAny(value: any): any {
  return value;
}
 
const c = identityAny("hello");

The result also has the type any. You get no autocomplete, no type checking, and no compiler help on c. The generic version keeps the exact type instead, so you get full autocomplete and refactoring support on the result.

How Type Inference Works

In most cases you do not need to write the type argument explicitly. TypeScript looks at the value you pass in and fills in the type parameter for you.

Here is a function that returns the first element of an array, with both inferred and explicit type arguments shown together:

typescripttypescript
function firstElement<T>(arr: T[]): T | undefined {
  return arr[0];
}
 
// TypeScript infers T from the array
const num = firstElement([10, 20, 30]);
const str = firstElement(["a", "b", "c"]);
 
// Or you can write the type argument explicitly
const mixed = firstElement<string | number>([10, "hello", 20]);

The variable num has the type number-or-undefined, and str has the type string-or-undefined. TypeScript infers the type parameter from the array element type. You did not write angle brackets in the first two calls.

The third call uses an explicit type argument. The array contains both numbers and strings, and the explicit form lets you control exactly which types are accepted. Without it, TypeScript would still infer a union type, but the explicit form makes your intent clear to both the compiler and other developers reading the code.

Working with the Type Parameter Inside the Function

A type parameter stands for any type, so you cannot use methods or properties that only exist on some types:

typescripttypescript
function logLength<T>(value: T): T {
  console.log(value.length);
  return value;
}

This produces a compiler error: Property 'length' does not exist on type 'T'.

TypeScript is right to flag this. Someone could call the function with the number 42, and numbers do not have a length property. The type parameter could be anything, so you can only use operations that work on all types.

If your function really does need a length property, you have two options. The cleaner approach is to accept an array instead of a single value:

typescripttypescript
function logLength<T>(value: T[]): T[] {
  console.log(value.length);
  return value;
}
 
logLength([1, 2, 3]);
logLength(["a", "b"]);

Now TypeScript knows the argument is an array, and arrays always have a length property. The type parameter still tracks the element type.

The other option is to constrain the type parameter so only types with a length property are allowed. That is what generic constraints cover.

Multiple Type Parameters

A generic function can have more than one type parameter. Use a comma-separated list inside the angle brackets:

typescripttypescript
function pair<T, U>(first: T, second: U): [T, U] {
  return [first, second];
}
 
const p = pair("hello", 42);

TypeScript infers the first type parameter as string and the second as number, so the result is a tuple of string and number. Each type parameter is tracked independently.

This pattern is common when you want to relate two types. For example, a function that merges two objects:

typescripttypescript
function merge<T, U>(a: T, b: U): T & U {
  return { ...a, ...b };
}
 
const result = merge({ name: "Ada" }, { age: 30 });

The result combines both object types, so the compiler knows it has both a name property of type string and an age property of type number. TypeScript gives you autocomplete for both properties on the merged object.

When to Use a Generic Function

Use a generic function when:

  • The function needs to work with more than one type.
  • The return type depends on the input type.
  • You want the caller to get accurate autocomplete and type checking on the result.

Do not use a generic function when:

  • The function works with a single concrete type. Just write the type directly.
  • You are adding a type parameter that appears only once in the signature. That usually means you do not need generics.
  • The function body would work fine with unknown and you do not need to relate the input and output types.

Common Mistakes

Adding a type parameter that is never used:

typescripttypescript
function greet<T>(name: string): string {
  return `Hello, ${name}`;
}

The type parameter T appears in the angle brackets but nowhere in the function signature. TypeScript will complain or silently ignore it. Remove the type parameter.

Forgetting that type parameters have no runtime presence:

typescripttypescript
function createArray<T>(value: T, count: number): T[] {
  if (typeof T === "string") {
    return Array(count).fill(value);
  }
  return Array(count).fill(value);
}

A type parameter is a compile-time placeholder, not a runtime value, so this code does not even compile. TypeScript rejects the comparison itself with this error:

texttext
'T' only refers to a type, but is being used as a value here.

Type parameters do not exist in the emitted JavaScript, so typeof T has nothing to check. If you need a runtime type check, use typeof value on the argument itself, or pass the type as a value with a discriminated union.

Using a generic function when a union type would be simpler:

If the function does not relate the input and output types, write a union parameter directly:

typescripttypescript
function print(value: string | number): void {
  console.log(value);
}

This is clearer than a generic with a constraint because the reader immediately sees which types are accepted. Only reach for generics when the return type depends on the input type or when multiple parameters share a type.

For more on how generic functions interact with interfaces, see generic interfaces in TypeScript.

Rune AI

Rune AI

Key Insights

  • A generic function uses a type parameter to capture and reuse the type of its arguments.
  • TypeScript infers type arguments automatically in most cases, so callers rarely need to write angle brackets.
  • Generic functions preserve type information through the function body and return type, unlike any.
  • You can use multiple type parameters when a function needs to relate two or more types.
  • Type parameters are compile-time only. They disappear from the emitted JavaScript.
RunePowered by Rune AI

Frequently Asked Questions

Can I use more than one type parameter in a generic function?

Yes. Separate them with commas inside the angle brackets, like `<T, U>`. Each type parameter can be used independently in the function signature and body.

Do I always need to write the angle brackets when calling a generic function?

No. TypeScript infers the type argument from the value you pass in most cases. You only need to write explicit type arguments when inference fails or you want to override the inferred type.

What is the difference between a generic function and using the any type?

A generic function preserves the specific type through the function. With any, you lose all type information: the return type becomes any, and you get no autocomplete or type checking on the result.

Conclusion

Generic functions let you write one function that works with many types while keeping full type safety. Instead of using any and losing all type information, you use a type parameter that captures the actual type and carries it through the function body. TypeScript infers the type argument from how you call the function, so most generic functions feel just as natural to call as regular ones.