C.W.K.
Stream
Lesson 02 of 07 · published

Generic Functions

~10 min · generics, functions, multiple-parameters

Level 0Curious
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete
"One signature, every type, no duplication."

Single type parameter

The simplest generic function takes one type parameter and uses it to relate inputs and outputs. function head<T>(arr: T[]): T | undefined — the return is either an element of the array (same type T) or undefined (empty array). The relationship between the input element type and the return is captured by sharing T.

Multiple type parameters

Real functions often relate two or more types. function map<T, U>(arr: T[], fn: (x: T) => U): U[] — read this as: take an array of T, and a function that maps T to U, return an array of U. The two parameters can be different types, and the relationship is between input and output of the callback.

Convention: name type parameters with single uppercase letters (T, U, V) when they're abstract, or with meaningful names (TInput, TResult) when the role helps readability. Both styles are common; pick one and be consistent.

Inference at call sites

TypeScript's inference for generic functions is excellent. For most calls, you never write <T> explicitly — the compiler figures it out from the arguments. The exception: when there's no argument to infer from (like calling a generic that only returns), or when inference picks too wide a type for your taste.

// Inferred:
const lengths = map([1, 2, 3], (n) => n.toString().length);   // U inferred as number

// Explicit (rare):
const empty = identity<string>(/* you'd need an arg */);
The minimum viable generic function: one type parameter, used in both a parameter position and the return position. If the parameter doesn't appear twice, you might not need a generic — you might just need unknown. Test by removing the generic and seeing if the relationship still holds.

Code

Generic functions — one or many type parameters·typescript
// One type parameter — relationship between input and output.
function head<T>(arr: T[]): T | undefined {
  return arr[0];
}
const a = head([1, 2, 3]);          // a: number | undefined
const b = head(['hi']);              // b: string | undefined

// Two type parameters — input and callback output.
function map<T, U>(arr: T[], fn: (x: T) => U): U[] {
  return arr.map(fn);
}
const c = map([1, 2, 3], (n) => n.toString());  // c: string[]

// Inferred from argument and callback together.
function findFirst<T>(arr: T[], predicate: (x: T) => boolean): T | undefined {
  for (const x of arr) if (predicate(x)) return x;
  return undefined;
}
const d = findFirst([{ id: 1 }, { id: 2 }], (u) => u.id === 1);  // d: { id: number } | undefined

External links

Exercise

Write a generic zip<T, U>(a: T[], b: U[]): [T, U][] function that pairs corresponding elements from two arrays. Use the inferred return type to thread the pair correctly. Try zip([1, 2], ['a', 'b']) and confirm the result type is [number, string][].
Hint
return a.map((x, i) => [x, b[i]]). The return type [T, U][] carries the pair structure through, so callers know exactly which side is which.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.