SyntaxStudy
Sign Up
TypeScript Generic Types, Interfaces, and Constraints
TypeScript Beginner 1 min read

Generic Types, Interfaces, and Constraints

Generics are one of the most powerful features in TypeScript. They allow you to write code that works with a variety of types while still maintaining full type safety. A generic type parameter is a placeholder for an actual type that is specified when the generic is used. This avoids the need to write duplicate code for every type you want to support and avoids the unsafety of using any. Generic interfaces and type aliases let you describe data structures and APIs that are parameterised by the type of data they contain. A generic interface for a response wrapper, for instance, can be reused for every API endpoint by supplying the appropriate payload type. This single source of truth for the wrapper shape makes the codebase easier to maintain. Constraints are applied with the extends keyword and restrict which types a type parameter can be. A constrained generic function can access the members guaranteed by the constraint on its parameter. Multiple constraints can be combined using intersection types. Constraints bridge the gap between the flexibility of generics and the precision of concrete types.
Example
// Generic interface
interface ApiResponse<T> {
    data: T;
    status: number;
    message: string;
    timestamp: Date;
}

interface User { id: number; name: string; email: string }
interface Post { id: number; title: string; body: string }

type UserResponse = ApiResponse<User>;
type PostResponse = ApiResponse<Post>;

// Generic with multiple type parameters
interface KeyValuePair<K, V> {
    key: K;
    value: V;
}

// Generic function with keyof constraint
function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
    return keys.reduce((acc, key) => {
        acc[key] = obj[key];
        return acc;
    }, {} as Pick<T, K>);
}

const user: User = { id: 1, name: "Alice", email: "alice@example.com" };
const partial = pick(user, ["name", "email"]); // { name, email }

// Generic class with constraint
interface Comparable<T> {
    compareTo(other: T): number;
}

class SortedList<T extends Comparable<T>> {
    private items: T[] = [];

    add(item: T): void {
        this.items.push(item);
        this.items.sort((a, b) => a.compareTo(b));
    }

    toArray(): T[] { return [...this.items]; }
}

// Generic default type
type Nullable<T = string> = T | null;
const maybeStr: Nullable = null;       // string | null
const maybeNum: Nullable<number> = 42; // number | null