Generics let you write code that works with many types while preserving type information. Instead of using any, you declare a type parameter that callers specify.

Generic Functions

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

const num = identity<number>(42);       // T is number
const str = identity('hello');          // T inferred as string

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

console.log(firstItem([1, 2, 3]));      // number | undefined
console.log(firstItem(['a', 'b']));     // string | undefined
  

Generic Interfaces

  interface ApiResponse<T> {
    data: T;
    status: number;
    error: string | null;
}

interface User {
    id: number;
    name: string;
}

const response: ApiResponse<User> = {
    data: { id: 1, name: 'Alice' },
    status: 200,
    error: null
};
  

Generic Classes

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

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

    pop(): T | undefined {
        return this.items.pop();
    }

    peek(): T | undefined {
        return this.items[this.items.length - 1];
    }
}

const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
console.log(numberStack.pop()); // 2

const stringStack = new Stack<string>();
stringStack.push('hello');
// stringStack.push(42); // Error
  

Constraints

Limit what types a generic can accept with extends:

  interface HasLength {
    length: number;
}

function logLength<T extends HasLength>(item: T): number {
    console.log(item.length);
    return item.length;
}

logLength('hello');     // 5
logLength([1, 2, 3]);   // 3
// logLength(42);       // Error: number has no .length

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
    return obj[key];
}

const person = { name: 'Alice', age: 30 };
console.log(getProperty(person, 'name')); // "Alice"
// getProperty(person, 'email');           // Error
  

Multiple Type Parameters

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

const result = pair('Alice', 25);
// result: [string, number]
  

Default Type Parameters

  interface PaginatedResult<T = unknown> {
    items: T[];
    page: number;
    total: number;
}
  

Generic Type Aliases

  type Result<T, E = Error> =
    | { success: true; value: T }
    | { success: false; error: E };

function parseNumber(input: string): Result<number> {
    const n = Number(input);
    if (isNaN(n)) {
        return { success: false, error: new Error('Invalid number') };
    }
    return { success: true, value: n };
}
  

Generics are everywhere in TypeScript libraries — arrays, promises, React hooks, and API clients all rely on them. Next, we explore built-in utility types that transform existing types.