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> -
Tis 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