2 min read

TypeScript Generics

Generics allow you to create reusable components that can work over a variety of types rather than a single one. This allows users to consume these components and use their own types while maintaining type safety.

1. The Problem

Without generics, we would either have to give a specific type to a function or use any.

// Specific type: Not reusable for numbers
function identity(arg: string): string {
    return arg;
}

// Any: We lose information about what that type was when the function returns
function identity(arg: any): any {
    return arg;
}

2. Generic Syntax

We use a type variable, usually denoted as <T>, to capture the type the user provides (e.g., number or string).

function identity<T>(arg: T): T {
    return arg;
}

Now we can call it in two ways:

// 1. Explicitly passing the type
let output = identity<string>("myString");

// 2. Type Argument Inference (Compiler figures it out)
let output2 = identity("myString");

3. Generic Interfaces

You can define interfaces that accept type arguments.

interface Box<T> {
    contents: T;
}

let stringBox: Box<string> = { contents: "Hello" };
let numberBox: Box<number> = { contents: 42 };

4. Generic Classes

Classes can also be generic.

class KeyValuePair<K, V> {
    constructor(public key: K, public value: V) {}
}

let pair = new KeyValuePair<number, string>(1, "a");

5. Generic Constraints

Sometimes you want to restrict the types that can be used with a generic. For example, if you want to access .length, you need to constrain T to types that have a .length property.

interface Lengthwise {
    length: number;
}

// T must extend Lengthwise
function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length); // Now we know it has a .length property, so no error
    return arg;
}

loggingIdentity({ length: 10, value: 3 }); // OK
// loggingIdentity(3); // Error, number doesn't have .length

programming/javascript/typescript/typescript