TypeScript Generics and Function Types

Published Updated

Once you can name a shape, the next question is how to reuse it without copying it. This topic covers the three tools TypeScript gives you for that: function types that describe what a helper accepts and returns, generics that carry a caller's type through a helper untouched, and utility types that build new shapes out of shapes you already declared.

These three fit together more tightly than their names suggest. A utility type such as ReturnType is a generic type applied to a function type, so the vocabulary of the first guide shows up in the last one. Read them in order if any of that is new.

TypeScript Generics Topics

  • Typing Functions and Callbacks covers parameters, return types, optional and rest parameters, callback types, and where contextual typing saves you the annotation.
  • Generic Functions and Types covers type parameters, inference at the call site, constraints with extends, and defaults.
  • Utility Types covers the built-in transformers such as Pick, Omit, Partial, Record, and ReturnType, plus when a named type beats a clever one.

The Rule Everything Here Depends On

A generic is a type parameter: a placeholder the caller fills in, exactly the way a function parameter is a value placeholder. Nothing more mystical is going on, and seeing the two side by side usually settles it:

// a value parameter: the caller supplies the value
function identityValue(value: string): string {
  return value;
}

// a type parameter: the caller supplies (or the compiler infers) the type
function identity<Value>(value: Value): Value {
  return value;
}

const label = identity("draft");
const count = identity(3);

// hover in an editor shows:
// const label: "draft"
// const count: 3

The compiler read each call and filled Value in for you, which is inference doing the same job it does for ordinary variables. Those two results are literal types because the arguments were literals passed straight into an unconstrained type parameter; the same calls assigned through a wider annotation would widen to string and number.

The second half of the rule is that all of this disappears. Type parameters, constraints, and utility types are part of the type layer, so compilation erases them and the emitted JavaScript holds one plain function. That is why a generic cannot inspect its own type argument at runtime: there is nothing left to inspect. Checking what a value actually is remains a narrowing problem, which the types topic covers.

Common Pitfalls

  • A type parameter used once: if T appears in exactly one position and nowhere else, it is usually not connecting anything, though a parameter used once can still capture a type for a later inference. The generics guide covers the test for whether a parameter is pulling weight.
  • Annotating callback parameters that infer: a callback passed straight into a typed parameter is contextually typed already. The functions guide shows where that inference applies and where it stops.
  • Reaching for any to make a helper reusable: that makes the helper accept everything and return nothing useful. A type parameter keeps the caller's type instead of discarding it.
  • Nesting utility types until nobody can read them: Partial<Pick<User, "email">> is fine once. Repeated in three files, it wants a name, as the utility types guide argues.

Common Questions

What does T actually mean?

T is just a parameter name for a type, the way x is a parameter name for a value. The single letter is a convention inherited from other languages, not a rule: GenericItem or ItemType compiles identically. Longer names usually read better once a helper takes more than one type parameter.

Do you have to write the type argument yourself?

Usually not. When a type parameter appears in a position the arguments contribute to, the compiler infers it from the call site, though some positions supply no candidate and NoInfer blocks one deliberately. You supply it yourself when the call constrains nothing, or when you want a wider type than inference picked.

Are generics slower at runtime?

No. Type parameters are part of the type layer, so they are erased during compilation and the shipped JavaScript contains a single ordinary function. The cost lands on the compiler and the editor, which is why deeply nested generic types can slow type checking while leaving runtime performance untouched.

Continue Learning TypeScript

  • Start with TypeScript Types if annotations, interfaces, and unions are still new. Everything here builds on them.
  • Move on to TypeScript Tooling for tsconfig.json, the compiler flags that change how strict all of this is, React props, and API contracts.
  • Return to the TypeScript guide for the language overview and the full learning path.

Sources

  1. [1]
    More on Functions
    (typescriptlang.org)
  2. [2]
    Generics
    (typescriptlang.org)
  3. [3]
    Utility Types
    (typescriptlang.org)