iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

TS Generics

Generics let a function, type, or class work with many types while keeping the relationship between input and output. TypeScript's superpower for building reusable code.

Basic generic function

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

const a = first(['x', 'y']);   // string | undefined
const b = first([1, 2, 3]);     // number | undefined
const c = first<boolean>([true, false]);   // boolean | undefined — explicit

Multiple type parameters

TS
function pair<A, B>(a: A, b: B): [A, B] {
    return [a, b];
}
const p = pair('hi', 42);   // [string, number]

Constraints — extends

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

longest('hi',  'hello');    // string
longest([1,2], [3,4,5]);     // number[]
// longest(1, 2);            // ✗ number has no .length

Default type arguments

TS
function create<T = string>(value: T): T[] {
    return [value];
}
create(42);          // number[]
create<boolean>(true);
create();            // ✗ — T defaults to string but value is required

Generic types & interfaces

TS
type Result<T> = { ok: true; value: T } | { ok: false; error: string };

interface Repo<T> {
    find(id: number): Promise<T | null>;
    save(value: T): Promise<void>;
}

Generic classes

TS
class Stack<T> {
    private items: T[] = [];
    push(item: T) { this.items.push(item); }
    pop(): T | undefined { return this.items.pop(); }
    peek(): T | undefined { return this.items[this.items.length - 1]; }
}

const s = new Stack<number>();
s.push(1); s.push(2);
const n = s.pop();    // number | undefined
Tip: Write generics when you have a relationship between types — input/output, container/contents, parameter/return. Don't add generics "just in case" — they make signatures harder to read.

Example

Example
function first<T>(items: T[]): T | undefined {
  return items[0];
}
console.log(first<string>(['a', 'b']));
console.log(first([1, 2, 3]));            // T inferred as number

// Generic with constraint
function longest<T extends { length: number }>(a: T, b: T): T {
  return a.length >= b.length ? a : b;
}
console.log(longest('hello', 'hi'));
Try it Yourself »

Exercise

Constraint syntax — keyword between T and the bound.

function key<T object>(o: T) {}

Test yourself

Q1. A constraint is written with…
Q2. A default type parameter is written…
Q3. You should add generics when…

Discussion

Loading…