Skip to content

Generics ​

Type parameters let one function or class work with many types while keeping them checked.

Generic functions ​

ts
function firstOr<T>(items: Array<T>, fallback: T): T {
    if (items.length == 0) {
        return fallback;
    }
    return items[0];
}

const n: int = firstOr([3, 4], 0);
const s: string = firstOr([], "none");
console.log(n, s);

The checker infers T from the arguments, then checks the result. const s: string = firstOr([3, 4], 0); would be an error, because T is int there.

You can also pass the type yourself: firstOr<int>(scores, 0).

Generic classes ​

ts
class Stack<T> {
    private items: Array<T> = [];

    push(item: T): void {
        this.items.push(item);
    }

    pop(): T | null {
        if (this.items.length == 0) {
            return null;
        }
        return this.items.pop();
    }

    get size(): int {
        return this.items.length;
    }
}

const moves: Stack<string> = new Stack<string>();
moves.push("left");
moves.push("jump");
console.log(moves.pop(), moves.size);

Write the type arguments in annotations ​

A type annotation must give every type argument. let m: Map = ... is an error. Write Map<string, int>, or Map<any, any> when you really mean any. A new expression can leave them out:

ts
const scores: Map<string, int> = new Map();
scores.set("ada", 10);
console.log(scores.get("ada")); // get answers int | undefined; use ! or ?? to get an int

Type arguments are checked ​

Array<int> and Array<string> are different types, and so are Promise<int> and Promise<string>. Arguments are covariant, as in TypeScript: an Array<int> can be used where an Array<float> is expected, but not the other way round.

ts
const ints: Array<int> = [1, 2];
const floats: Array<float> = ints;
console.log(floats.length);

At run time ​

Generics are erased. Every Stack<T> shares the same compiled code, and there is no type information for T at run time. All the checking happens before the program runs.