Handbooks / TypeScript / Chapter 4
Generics
7 pages · ~5 min✓ Reviewed
Builds on Narrowing. Next up: Utility and Mapped Types.
Generics Mental Model
1. What a Generic Is
- A generic is a type-level parameter:
function id<T>(x: T): T - One declaration works for many types without losing type info
| any | T | |
|---|---|---|
| Input type | discarded | linked to output |
id(5) returns | any | literal 5 |
2. Inference and Erasure
- Inferred from call arguments:
id('a')gives T ='a' - Pass explicitly when inference fails:
id<string>('a')
typescript
function id<T>(x: T): T { return x } id(5) // T = 5 id('a') // T = 'a' id<string>('a') // T = string
explicit argument overrides inference
Erased at runtime
T exists only for the compiler. So no new T() and no typeof T.
3. Interview Point
- What do generics fix over
any? T links input to output, so type info survives - Available at runtime? No, erased;
new T()is impossible - Infer or explicit? Inferred from arguments; explicit when no argument fixes T
Generic Functions, Interfaces and Classes
1. Declaring Generics
| Form | Declare | Use |
|---|---|---|
| Function | function first<T>(a: T[]): T | undefined { return a[0]; } | first([1, 2]) |
| Interface | interface Box<T> { value: T } | Box<number> |
| Call signature | interface Fn<T> { (x: T): T } | Fn<string> |
| Alias | type Pair<A, B> = [A, B] | Pair<string, number> |
2. Generic Class
new Stack<number>()fixes T for that instance
typescript
class Stack<T> { private items: T[] = []; push(x: T) { this.items.push(x) } pop(): T | undefined { return this.items.pop() } }
3. Rules
- Arrow in
.tsx: write<T,>(x: T) => x; the comma stops JSX parsing - Several params are comma-separated; supply each unless defaulted
Static
Static members cannot use T: it belongs to the instance side only.
4. Interview Point
- Declare a generic class?
class C<T> {}, thennew C<number>() - Static using T? Statics live on the constructor, not on instances, so no
T - Interface vs alias? Interface extends and merges; alias also names unions and tuples
Constraints with extends
1. Constraint syntax
T extends { length: number }: callers may pass only types that have length- Bare
Tis unknown-like, sox.lengtherrors Tkeeps the specific type:len([1, 2])givesT = number[],len('abc')givesT = string
typescript
function len<T extends { length: number }>(x: T) { return x.length }
accepts string, array, any object with length
2. Dependent params and constructors
K extends keyof Tties the key to the objectT extends new (...a: any[]) => objectaccepts a class you cannew
typescript
function pick<T, K extends keyof T>(o: T, k: K): T[K] function make<T extends new (...a: any[]) => object>(C: T) { return new C() }
3. Interview Point
- extends in a constraint? It means assignable to, not class inheritance
- Why no
.lengthon bare T? T could be anything, so onlyunknownmembers exist - One param depending on another?
K extends keyof T
Floor, not replacement
The constraint only checks the argument. T is still inferred from it.
Defaults, keyof and Indexed Access
1. Default type parameters
interface Res<T = unknown> { data: T }, soResmeansRes<unknown>- Applied only when T is not given and not inferred
- Defaulted params go after required ones
- Default must satisfy the constraint:
<T extends object = {}>
2. keyof and indexed access
| Type | Gives |
|---|---|
keyof {a: 1; b: 2} | 'a' | 'b' |
keyof any[] | number + method names |
keyof {[k: string]: X} | string | number |
User['id'] | type of property id |
T[number] | array element type |
T[keyof T] | union of all value types |
3. Typed getter
K extends keyof T: wrong keys fail at compile timeT[K]makes the return type exact, not a union
typescript
function get<T, K extends keyof T>(o: T, k: K): T[K] { return o[k] } get({ id: 1 }, 'id') // number get({ id: 1 }, 'x') // error
Common Generic Mistakes
1. Smells in the signature
function f<T>(x: T): void: T used once relates nothing, so it is a smell- Each T should link two positions, such as param and return
as Tor{} as Thides errors; the compiler cannot verify it
2. Constraint traps
| Mistake | Why it fails | Fix |
|---|---|---|
T extends string, return 'a' | T may be a narrower literal | Return string |
typeof x === 'string' | Does not narrow T itself | Overloads or conditional types |
3. Untyped keys
- Key typed
string: implicit any ono[k], safety lost - Use K extends keyof T instead
typescript
function get<T, K extends keyof T>( o: T, k: K ): T[K] { return o[k]; }
Remember
The caller picks T, so you cannot return a concrete value for it. To debug inference, hover the call or pass <T> explicitly.
Generics Cheat Sheet
1. Declare
| Form | Syntax |
|---|---|
| Function | function f<T>(x: T): T |
| Interface | interface I<T> { v: T } |
| Class | class C<T> {} |
| Alias | type A<T> = T[] |
2. Constrain and index
| Write | Means |
|---|---|
<T extends U> | constrain T |
<K extends keyof T> | K depends on T |
<T = string> | default, last |
keyof T | key union |
T[K] | value type |
T[number] | array element |
T[keyof T] | all values |
3. The idiom
typescript
function get<T, K extends keyof T>(o: T, k: K): T[K] { return o[k]; }
type-safe property access
4. Interview Point
- Type-safe pick?
pick<T, K extends keyof T>(o: T, ks: K[]): Pick<T, K> - Ties key to object?
K extends keyof T - Default order? Defaults go last, after required params
Check yourself
function len<T>(x: T) { return x.length } fails to compile. Why, and what is the fix?
- A bare
Tcould be anything, so the compiler knows no properties on it - Constrain it:
<T extends { length: number }>
Given get<T, K extends keyof T>(o: T, k: K): T[K], what is the type of get({a: 1, b: 'x'}, 'b')? What about get({a: 1}, 'c')?
string, becauseKis inferred as'b'andT['b']isstring- The second call is a compile error:
'c'is not inkeyof T
function f<T extends string>(): T { return 'a' } errors. Why?
- The caller may choose
T = 'b', and'a'is not assignable to'b' Tis the caller's type, so you cannot return a concrete value for it