TypeScript Basic Generics

Write reusable, type-safe code

🔄 What are Generics?

Generics allow you to create reusable components that work with multiple types while maintaining type safety. Use angle brackets <T> to define type parameters. They're like variables for types, making your code flexible without sacrificing TypeScript's type checking.


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

const num = identity<number>(42);
const str = identity<string>("hello");
                                    

Generic Concepts

🔧

Generic Functions

Functions that work with any type

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

Generic Classes

Classes that work with any type

class Box<T> {
    constructor(public value: T) {}
}
🎯

Generic Interfaces

Interfaces with type parameters

interface Pair<T, U> {
    first: T;
    second: U;
}
📋

Generic Arrays

Type-safe array operations

function first<T>(arr: T[]): T {
    return arr[0];
}

🔹 Generic Functions

Create functions that work with any type while preserving type information:

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

// TypeScript infers the type
const num = identity(42);           // number
const str = identity("hello");      // string
const bool = identity(true);        // boolean

// Or explicitly specify the type
const num2 = identity<number>(100);
const str2 = identity<string>("world");

console.log(num);   // 42
console.log(str);   // hello
console.log(bool);  // true

Output:

42
hello
true

🔹 Generic Array Functions

Work with arrays of any type safely:

// Get first element of array
function first<T>(arr: T[]): T | undefined {
    return arr[0];
}

// Get last element of array
function last<T>(arr: T[]): T | undefined {
    return arr[arr.length - 1];
}

const numbers = [1, 2, 3, 4, 5];
const words = ["hello", "world", "typescript"];

console.log(first(numbers));  // 1
console.log(last(numbers));   // 5
console.log(first(words));    // hello
console.log(last(words));     // typescript

Output:

1
5
hello
typescript

🔹 Generic Classes

Create classes that work with any type:

class Box<T> {
    private content: T;
    
    constructor(value: T) {
        this.content = value;
    }
    
    getValue(): T {
        return this.content;
    }
    
    setValue(value: T): void {
        this.content = value;
    }
}

// Create boxes for different types
const numberBox = new Box<number>(42);
const stringBox = new Box<string>("hello");
const boolBox = new Box<boolean>(true);

console.log(numberBox.getValue());  // 42
console.log(stringBox.getValue());  // hello
console.log(boolBox.getValue());    // true

numberBox.setValue(100);
console.log(numberBox.getValue());  // 100

Output:

42
hello
true
100

🔹 Generic Interfaces

Define interfaces with type parameters:

// Generic pair interface
interface Pair<T, U> {
    first: T;
    second: U;
}

// Generic response interface
interface ApiResponse<T> {
    data: T;
    status: number;
    message: string;
}

// Use the interfaces
const coordinates: Pair<number, number> = {
    first: 10,
    second: 20
};

const nameAge: Pair<string, number> = {
    first: "Alice",
    second: 25
};

const userResponse: ApiResponse<{ id: number; name: string }> = {
    data: { id: 1, name: "John" },
    status: 200,
    message: "Success"
};

console.log(coordinates);      // { first: 10, second: 20 }
console.log(nameAge);          // { first: 'Alice', second: 25 }
console.log(userResponse.data); // { id: 1, name: 'John' }

Output:

{ first: 10, second: 20 }
{ first: 'Alice', second: 25 }
{ id: 1, name: 'John' }

🔹 Multiple Type Parameters

Use multiple generic type parameters:

// Function with two type parameters
function merge<T, U>(obj1: T, obj2: U): T & U {
    return { ...obj1, ...obj2 };
}

const person = { name: "Alice" };
const details = { age: 30, city: "NYC" };

const merged = merge(person, details);
console.log(merged);  // { name: 'Alice', age: 30, city: 'NYC' }

// Generic map function
function map<T, U>(arr: T[], fn: (item: T) => U): U[] {
    return arr.map(fn);
}

const nums = [1, 2, 3, 4, 5];
const doubled = map(nums, n => n * 2);
const strings = map(nums, n => `Number: ${n}`);

console.log(doubled);   // [2, 4, 6, 8, 10]
console.log(strings);   // ['Number: 1', 'Number: 2', ...]

Output:

{ name: 'Alice', age: 30, city: 'NYC' }
[2, 4, 6, 8, 10]
['Number: 1', 'Number: 2', 'Number: 3', 'Number: 4', 'Number: 5']

🔹 Generic Stack Example

Build a type-safe stack data structure:

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];
    }
    
    isEmpty(): boolean {
        return this.items.length === 0;
    }
    
    size(): number {
        return this.items.length;
    }
}

const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
numberStack.push(3);

console.log(numberStack.peek());  // 3
console.log(numberStack.pop());   // 3
console.log(numberStack.size());  // 2

const stringStack = new Stack<string>();
stringStack.push("hello");
stringStack.push("world");
console.log(stringStack.pop());   // world

Output:

3
3
2
world

💡 Key Points:

  • Generics use angle brackets: <T>
  • T is a common convention for type parameters (can be any name)
  • TypeScript can often infer generic types automatically
  • Use generics for functions, classes, and interfaces
  • Multiple type parameters: <T, U, V>
  • Generics maintain type safety while being reusable

🧠 Test Your Knowledge

What syntax is used to define a generic type parameter?