Skip to content

Step 1 of 7 · Reading · ~4 min

Learn

Functions

Generics

A generic function or type works with many kinds of values without giving up type checking. By the end of this lesson you can write a function with a type parameter, use several type parameters at once, constrain what a type parameter may be, and recognise when a generic is not worth it.

The motivating problem

Suppose you want first, which returns the first element of an array:

function first(arr: any[]): any {
    return arr[0];
}

const n = first([1, 2, 3]);   // n: any, not number
n.toUpperCase();              // compiles, then crashes at runtime

any throws away what the caller knew. Writing firstNumber, firstString and so on for every type is absurd. A type parameter solves it:

function first<T>(arr: T[]): T | undefined {
    return arr[0];
}

const a = first([1, 2, 3]);    // a: number | undefined
const b = first(["x", "y"]);   // b: string | undefined

Type parameters

<T> introduces a placeholder type. Read it as "for some type T". The compiler infers T from the arguments at each call, so you almost never write it out. A generic function can have several parameters, each linking types together:

function pair<A, B>(a: A, b: B): [A, B] {
    return [a, b];
}

const p = pair("age", 30);    // p: [string, number]

A and B are independent: the result remembers that the first slot is a string and the second a number. The names T, U, K, V are conventional; longer names such as <Item> or <Key, Value> are fine when they help.

The standard library is full of generics. You have already met one: Array<T> is the same type as T[]. Map<K, V> and Set<T> come later in this course, and Promise<T> in the intermediate course.

Generic interfaces and aliases

interface Box<T> {
    value: T;
}

const numBox: Box<number> = { value: 42 };
const textBox: Box<string> = { value: "hi" };

Here nothing can be inferred from a call, so you supply the type argument when you use the type.

Constraints with extends

Sometimes the function needs the type to have a particular shape:

function longest<T extends { length: number }>(a: T, b: T): T {
    return a.length >= b.length ? a : b;
}

longest("hello", "hi");     // OK: strings have a length
longest([1, 2, 3], [4]);    // OK: arrays have a length
longest(42, 5);             // compile error: numbers do not

T extends X means "any type that has at least X's shape". It is a lower bound, not an exact match.

Default type parameters

interface ApiResponse<T = unknown> {
    status: number;
    data: T;
}

const raw: ApiResponse = { status: 200, data: "anything" };   // T defaults to unknown

Generics are erased

Like every type, a type parameter is removed when the code is compiled. first<T> and a version written for numbers produce the identical JavaScript. Generics exist to make the checker's job precise; they cost nothing at runtime and do nothing at runtime. Generic classes such as Stack<T> work the same way, and the intermediate course builds one.

When not to use a generic

Do not make every function generic. A function that always handles User should be typed User. Reach for a type parameter when the same logic must work over many element types and the output type depends on the input type.

Common mistakes

  • Using any instead of T. The result loses its type and mistakes slip through.
  • Using a property without a constraint. function len<T>(x: T) { return x.length; } fails because T might not have length.
  • Passing type arguments by hand. first<number>([1, 2, 3]) is noise when inference already works.
  • Using one parameter where two are needed. pair<T>(a: T, b: T) forces both values to be the same type; <A, B> lets them differ.

Your exercise

Write a generic zip that pairs up two lists whose element types differ. The starter parses a list of words and a list of numbers, calls zip, and prints the pairs. To build the result, create an empty array and add each pair to its end with result.push(pair) (the next lesson covers array methods in full). The mistake the hidden tests catch is what happens when the two lists have different lengths.

Up nextArrays and TuplesCollections

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…