TypeScript — Generics
Generic functions
function identity<T>(value: T): T {
return value;
}
console.log(identity<string>("hello")); // hello
console.log(identity(42)); // 42 (inferred)
function pair<T, U>(first: T, second: U): [T, U] {
return [first, second];
}
const p = pair("Alice", 30); // [string, number]
Generic constraints
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(value: T): void {
console.log(`Length: ${value.length}`);
}
logLength("hello"); // 5
logLength([1, 2, 3]); // 3
// logLength(42); // Error: number has no length
// Constraint with keyof
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
Generic interfaces
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
interface User {
id: string;
name: string;
email: string;
}
async function fetchUser(id: string): Promise<ApiResponse<User>> {
return {
data: { id, name: "Alice", email: "alice@example.com" },
status: 200,
message: "OK"
};
}
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];
}
get size(): number {
return this.items.length;
}
}
const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
console.log(numberStack.pop()); // 2
const stringStack = new Stack<string>();
stringStack.push("hello");
console.log(stringStack.peek()); // hello
Generic defaults
interface Paginated<T = unknown> {
data: T[];
page: number;
totalPages: number;
}
// Uses default
const result: Paginated = {
data: [1, 2, 3],
page: 1,
totalPages: 5
};
// Specifies type
const userResult: Paginated<User> = {
data: [],
page: 1,
totalPages: 1
};
Utility types
interface User {
id: string;
name: string;
email: string;
age: number;
}
// Partial: all optional
type PartialUser = Partial<User>;
// Required: all required
type RequiredUser = Required<User>;
// Pick: select properties
type UserBasic = Pick<User, "name" | "email">;
// Omit: exclude properties
type UserWithoutEmail = Omit<User, "email">;
// Record
type UserMap = Record<string, User>;
// Exclude
type StringOrNumber = string | number | boolean;
type JustStringOrNumber = Exclude<StringOrNumber, boolean>; // string | number
// Extract
type JustString = Extract<StringOrNumber, string>; // string
// ReturnType
function getUser() { return { id: "1", name: "Alice" }; }
type UserType = ReturnType<typeof getUser>;
Mapped types
// Make all properties optional
type Optional<T> = {
[K in keyof T]?: T[K];
};
// Make all properties readonly
type Immutable<T> = {
readonly [K in keyof T]: T[K];
};
// Make all properties nullable
type Nullable<T> = {
[K in keyof T]: T[K] | null;
};
type User = { name: string; age: number };
type OptionalUser = Optional<User>;
type ImmutableUser = Immutable<User>;
Conditional types
type IsString<T> = T extends string ? true : false;
type A = IsString<"hello">; // true
type B = IsString<42>; // false
// Infer keyword
type ElementType<T> = T extends (infer U)[] ? U : T;
type X = ElementType<number[]>; // number
type Y = ElementType<string>; // string
Best practices
- Use generics for reusable, type-safe code
- Add constraints with
extendsto limit type parameters - Use defaults for common cases
- Prefer built-in utility types over custom ones
- Keep generic signatures simple
Mini Practice
Write TypeScript code that:
- Creates a generic
Result<T>type for success/error - Writes a generic function that filters arrays
- Uses utility types to create variations of an interface
- Implements a generic
Map<K, V>class
Up Next
In the next lesson, you'll learn about Modules — organizing code with imports and exports.
Related Topics
Frequently Asked Questions about Generics
What is Generics in TypeScript?
Generics is a fundamental concept in TypeScript. This lesson explains it step by step with clear examples, making it easy for beginners to understand.
How do I learn Generics?
Start by reading the explanation above, then try the code examples. Practice by modifying the examples and experimenting with different values. Hands-on practice is the best way to learn Generics.
Why is Generics important in TypeScript?
Generics is essential for TypeScript development. Understanding this concept will help you write better code and solve real-world problems more effectively.