---
title: "10 — Generics: Type Variables for Reusable Code"
uid: typescript-generics
tags: ["type-parameters", "generic-constraints", "typescript", "generics", "roadmap:typescript"]
excerpt: "A generic is a type variable — a placeholder filled in when the code is used — so one definition works with many types without losing precision or reaching for any."
date: 2026-08-13T03:27:27+0000
source: https://www.aveshina.my.id/en/blog/typescript-generics
---

"Scary advanced syntax I'll learn when I need it" was my generics stance, and the deferral kept costing me. Writing it down stripped the mystery: **a generic is a type variable, a placeholder for a type that gets filled in when the code is used, so one definition works with many types without losing precision or reaching for any.** [1] That's the whole idea; everything else is syntax.

The problem generics solve is direct. Suppose I want a function that returns whatever I pass in — an identity function:

```
function identity(value: any): any {
  return value;
}
```

This works for any type, but the return type is any — I've thrown away everything the compiler knew. If I pass a string, the result is any, and calling .toUpperCase() on it isn't checked. The alternative without generics is writing identity once per type, which defeats reuse. Generics let me keep the input and output types **linked**:

```
function identity<T>(value: T): T {
  return value;
}
```

The <T> declares a type parameter named T. When the function is called, TypeScript fills in T from the argument: identity("hello") has T inferred as string, so the return type is string. The link between input and output is preserved, and I wrote exactly one function.

```figure
<svg viewBox="0 0 720 240" xmlns="http://www.w3.org/2000/svg" class="my-6 w-full max-w-2xl" role="img" aria-label="Generic identity function. A central box identity<T> with a T slot. Three input arrows feed string, number, and User; each emerges as the same type returned, preserving the link.">
  <defs>
    <marker id="genarrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto">
      <path d="M0,0 L10,5 L0,10 z" fill="#64748b"/>
    </marker>
  </defs>
  <g font-family="ui-sans-serif, system-ui, sans-serif" text-rendering="geometricPrecision">

    <!-- function box -->
    <rect x="260" y="70" width="200" height="100" rx="8" fill="#e0e7ff" stroke="#6366f1" stroke-width="1.5"/>
    <text x="360" y="100" font-size="14" font-weight="700" fill="#1e1b4b" text-anchor="middle">identity&lt;T&gt;</text>
    <rect x="290" y="115" width="140" height="24" rx="4" fill="#c7d2fe" stroke="#6366f1" stroke-width="1"/>
    <text x="360" y="131" font-size="10.5" font-family="ui-monospace, monospace" fill="#1e1b4b" text-anchor="middle">(value: T): T</text>
    <text x="360" y="155" font-size="10" font-style="italic" fill="#475569" text-anchor="middle">T filled in at call site</text>

    <!-- inputs -->
    <path d="M70,90 L258,100" fill="none" stroke="#16a34a" stroke-width="1.5" marker-end="url(#genarrow)"/>
    <text x="60" y="86" font-size="10" font-family="ui-monospace, monospace" fill="#052e16" text-anchor="middle">string →</text>
    <path d="M70,140 L258,130" fill="none" stroke="#db2777" stroke-width="1.5" marker-end="url(#genarrow)"/>
    <text x="60" y="158" font-size="10" font-family="ui-monospace, monospace" fill="#500724" text-anchor="middle">number →</text>

    <!-- outputs -->
    <path d="M460,100 L648,90" fill="none" stroke="#16a34a" stroke-width="1.5" marker-end="url(#genarrow)"/>
    <text x="675" y="86" font-size="10" font-family="ui-monospace, monospace" fill="#052e16" text-anchor="middle">→ string</text>
    <path d="M460,130 L648,140" fill="none" stroke="#db2777" stroke-width="1.5" marker-end="url(#genarrow)"/>
    <text x="675" y="158" font-size="10" font-family="ui-monospace, monospace" fill="#500724" text-anchor="middle">→ number</text>
  </g>
</svg>
```

## Type parameters and angle brackets

The syntax is a pair of angle brackets after the function (or class, or type) name, listing one or more type parameters: <T>, <T, U>, <K extends string>. By convention single-letter names (T for Type, K for Key, V for Value, E for Element) are common, but descriptive names work too. A type parameter is a variable at the type level — it stands for "some type, to be determined" [1].

The same idea scales beyond functions:

- **Generic classes** — class Box<T> { contents: T } — a Box<string> holds strings, Box<number> holds numbers.
- **Generic types** — type Pair<A, B> = { first: A; second: B } — reusable shapes parameterized by type.
- **Generic interfaces** — interface Repository<T> { find(id: string): T } — contracts that work for any entity type.

Wherever I'd be tempted to use any, a generic is usually the right answer.

## Inference at the call site

Most of the time I don't spell out the type argument — the compiler infers it from the value passed in. identity("hello") infers T = string without me writing identity<string>("hello"). Explicit type arguments exist (identity<string>("hello")) and are useful when inference can't determine the type (e.g., when the argument doesn't carry enough information) or when I want to force a specific type. The default is to let inference work and only annotate when it can't.

## Generic constraints

Unconstrained, T can be anything — which means inside the function I can only use operations that exist on *every* type (basically none). A **generic constraint** limits what T can be, so I can rely on specific properties. The extends keyword sets the constraint [2]:

```
function getLength<T extends { length: number }>(value: T): number {
  return value.length; // safe: T is guaranteed to have length
}
```

Now T must have a length: number property — strings, arrays, and any object with length qualify. The constraint lets me use .length inside the function, while still being generic over the specific type. Constraints are how generics stay flexible *and* useful: I narrow what T can be just enough to do real work, without pinning it to a single type.

The common patterns: T extends { length: number } for "anything with a length"; T extends Item for "a subtype of Item"; keyof inside constraints (<K extends keyof Obj>) for "a valid key of this object." The last one is the foundation of most type-safe property-access utilities.

## How I use this

My defaults: reach for a generic the moment I'm about to write any to keep a function reusable — the generic preserves the type link that any destroys; let inference fill in type arguments at call sites and only annotate explicitly when inference is ambiguous; and add a **constraint** with extends the moment I need to use a property of T inside the body. The mental shift is that generics aren't advanced syntax — they're just type variables, and once that lands, reading Array<T> or Promise<T> or Map<K, V> becomes reading the type system's vocabulary for "this works for any type, precisely."

## References

[1] Microsoft, "Hello World of Generics," TypeScript Handbook, 2024. [Online]. Available: [https://www.typescriptlang.org/docs/handbook/2/generics.html#hello-world-of-generics](https://www.typescriptlang.org/docs/handbook/2/generics.html#hello-world-of-generics)

[2] Microsoft, "Generic Constraints," TypeScript Handbook, 2024. [Online]. Available: [https://www.typescriptlang.org/docs/handbook/2/generics.html#generic-constraints](https://www.typescriptlang.org/docs/handbook/2/generics.html#generic-constraints)

[3] Microsoft, "Generic Types," TypeScript Handbook, 2024. [Online]. Available: [https://www.typescriptlang.org/docs/handbook/2/generics.html](https://www.typescriptlang.org/docs/handbook/2/generics.html)

```quiz
Q: A generic type parameter is best described as…
- a runtime variable that holds a value
- a type variable — a placeholder for a type filled in when the code is used
correct: 1
explain: Generics operate at the type level. <T> declares a placeholder; when the function or class is used, T is filled in with a concrete type, preserving type safety across uses.

Q: Why is `function identity<T>(value: T): T` better than `function identity(value: any): any`?
- it runs faster
- it preserves the link between input and output types, so identity("hi") returns string, not any
correct: 1
explain: With any, the return type loses all information. With a generic, T is inferred from the argument and the return type matches, so the compiler keeps checking the result.

Q: Inside a generic function, what can you do with an unconstrained `T`?
- call any method on it
- almost nothing — you can only use operations available on every type
correct: 1
explain: An unconstrained T could be anything, so the compiler only allows operations common to all types. To use specific properties, you add a constraint.

Q: What does `function len<T extends { length: number }>(x: T)` ensure?
- that T is exactly { length: number }
- that T has at least a length: number property, so x.length is safe inside
correct: 1
explain: extends sets a lower bound: T must have at least the constrained shape. This lets you use x.length while staying generic over the specific type.

Q: When do you write the type argument explicitly, like identity<string>("hi")?
- always
- only when inference can't determine T or you want to force a specific type
correct: 1
explain: Inference usually fills in T from the argument. Explicit type arguments are for when inference lacks information or when you need to pin a specific type that differs from what would be inferred.
```
