Appearance
Generics - one function, many shapes
Type parameters. Fancy name for "this works for some
Tthe caller chooses." Super common with lists, fetch helpers, and hooks/composables.
Why generics
ts
function firstString(items: string[]): string | undefined {
return items[0]
}
function firstNumber(items: number[]): number | undefined {
return items[0]
}
// One function, many element types
function first<T>(items: T[]): T | undefined {
return items[0]
}
const a = first(['a', 'b']) // string | undefined
const b = first([1, 2]) // number | undefinedAs you can see - same helper, no copy-paste for every element type. 🙌
Constrained generics
ts
type HasId = { id: string }
function byId<T extends HasId>(items: T[], id: string): T | undefined {
return items.find((item) => item.id === id)
}
byId([{ id: '1', title: 'Desk' }], '1')extends limits what T can be so you can safely use .id. Classic interview pattern.
Default type parameters
ts
type ApiResponse<T = unknown> = {
data: T
meta?: { page: number }
}
type Loose = ApiResponse // data: unknown
type Products = ApiResponse<Product[]>fetchJson<T> pattern
ts
async function fetchJson<T>(url: string): Promise<T> {
const res = await fetch(url)
if (!res.ok) throw new Error(`HTTP ${res.status}`)
return res.json() as Promise<T>
}
type Product = { id: string; title: string }
const product = await fetchJson<Product>('/api/products/1')
product.titleImportant detail: that cast is an assumption at the boundary. Pair it with runtime validation in production (see Type guards). Interviews still expect you to know this generic pattern though.
keyof for safe property access
ts
function pluck<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]
}
const title = pluck({ id: '1', title: 'Lamp' }, 'title') // stringCommon pitfall
ts
async function badFetch<T>(): Promise<T> {
return {} as T // lies about the type - compiles, crashes later
}Generics do not create values. If you can't produce a real T, don't fake it with as T. Which now DOESN'T WORK the way you hoped at runtime. 😅
Mini check
Question: Type a helper unwrapList that takes { items: T[] } and returns T[].
Answer
ts
function unwrapList<T>(payload: { items: T[] }): T[] {
return payload.items
}Next: Async & APIs.