Generics & Utility Types
Writing Code That Works With Any Type
Open interactive version (quiz + challenge)Real-world analogy
Generics are like a universal adapter plug you take on international trips. The adapter shape (generic type) stays the same, but you slot in different country prongs (concrete types) depending on where you are. ApiResponse<User> is the adapter configured for the User outlet. Same adapter, any country!
What is it?
Generics let you write flexible code that works with ANY type. Utility types transform existing types: Partial makes properties optional, Pick selects properties, Omit removes them. Learn these and you'll write DRY code!
Real-world relevance
React hooks use generics: useState<T>, useCallback<T>. API responses use generics: Promise<ApiResponse<T>>. Mastering generics = writing production-quality code!
Key points
- Generic Functions with <T> — function getFirst<T>(arr: T[]): T { return arr[0]; } — T is a placeholder. Call with getFirst<string>(['a','b']) and T becomes string.
- Generic Interfaces — interface ApiResponse<T> { data: T; success: boolean; }. Use ApiResponse<User> or ApiResponse<Post[]>. Same interface, different types!
- Constraints with extends — function process<T extends { length: number }>(item: T) — T MUST have a length property. Prevents passing number (which has no length).
- Partial<T> — Partial<User> makes all properties optional. Great for update DTOs where only SOME fields change.
- Pick<T, K> — Pick<User, 'name' | 'email'> = only those properties. Remove unnecessary fields!
- Omit<T, K> — Omit<User, 'password'> = everything EXCEPT password. Perfect for API responses (never expose secrets!).
- Record<K, V> — Record<string, number> = object with string keys and number values. Like a Map but as an object!
- Readonly<T> — Readonly<User> makes all properties readonly. Prevents accidental mutations!
Code example
// Generic function — T is a placeholder
function getFirst<T>(arr: T[]): T {
return arr[0];
}
getFirst<string>(["a", "b"]); // returns "a" (type: string)
getFirst<number>([1, 2, 3]); // returns 1 (type: number)
getFirst(["a", "b"]); // TS guesses T = string
// Generic interface
interface ApiResponse<T> {
data: T;
success: boolean;
message: string;
}
const userResponse: ApiResponse<User> = {
data: { id: "1", name: "John" },
success: true,
message: "Found"
};
const postsResponse: ApiResponse<Post[]> = {
data: [/* array of posts */],
success: true,
message: "All posts"
};
// Constraints — T must have "length"
function logLength<T extends { length: number }>(item: T): void {
console.log(item.length);
}
logLength("hello"); // 5 ✓
logLength([1, 2, 3]); // 3 ✓
// logLength(123); // ERROR! number has no length
// Utility Types ✨
// Partial<T> — all properties optional
type UpdateUserDto = Partial<User>;
// user can have any combination of User fields!
// Pick<T, K> — only selected properties
type UserPreview = Pick<User, "name" | "email">;
// { name: string; email: string; }
// Omit<T, K> — everything except selected
type UserPublic = Omit<User, "password" | "ssn">;
// Has everything but password and ssn!
// Record<K, V> — object with specific keys
type Scores = Record<string, number>;
const scores: Scores = {
math: 95,
science: 87,
english: 92
};
// Readonly<T> — all properties readonly
type Config = Readonly<{
apiUrl: string;
port: number;
}>;
const config: Config = { apiUrl: "https://api.com", port: 3000 };
// config.port = 4000; // ERROR!
// Enums — named constants
enum Role {
ADMIN = "admin",
USER = "user",
MODERATOR = "moderator"
}
const myRole: Role = Role.ADMIN;
// Tuples — fixed-length arrays with types
type Pair = [string, number];
const pair: Pair = ["John", 25];
pair[0]; // "John" (string)
pair[1]; // 25 (number)
// pair[2]; // ERROR!
// Type assertions (type casting)
const input = document.getElementById("name") as HTMLInputElement;
input.value = "John"; // TS knows it's an input nowLine-by-line walkthrough
- 1. Generic function — T is a placeholder
- 2. Declaring a function
- 3. Returning a value
- 4. Closing block
- 5.
- 6.
- 7.
- 8.
- 9.
- 10. Generic interface
- 11. Defining an interface shape
- 12.
- 13.
- 14.
- 15. Closing block
- 16.
- 17. Declaring a variable
- 18.
- 19.
- 20.
- 21. Closing block
- 22.
- 23. Declaring a variable
- 24.
- 25.
- 26.
- 27. Closing block
- 28.
- 29. Constraints — T must have "length"
- 30. Declaring a function
- 31. Printing output to the console
- 32. Closing block
- 33.
- 34.
- 35.
- 36. logLength(123); // ERROR! number has no length
- 37.
- 38. Utility Types ✨
- 39.
- 40. Partial<T> — all properties optional
- 41. Defining a type alias
- 42. user can have any combination of User fields!
- 43.
- 44. Pick<T, K> — only selected properties
- 45. Defining a type alias
- 46. { name: string; email: string; }
- 47.
- 48. Omit<T, K> — everything except selected
- 49. Defining a type alias
- 50. Has everything but password and ssn!
- 51.
- 52. Record<K, V> — object with specific keys
- 53. Defining a type alias
- 54. Declaring a variable
- 55.
- 56.
- 57.
- 58. Closing block
- 59.
- 60. Readonly<T> — all properties readonly
- 61. Defining a type alias
- 62.
- 63.
- 64.
- 65.
- 66. Declaring a variable
- 67. config.port = 4000; // ERROR!
- 68.
- 69. Enums — named constants
- 70. Defining an enum
- 71.
- 72.
- 73.
- 74. Closing block
- 75.
- 76. Declaring a variable
- 77.
- 78. Tuples — fixed-length arrays with types
- 79. Defining a type alias
- 80. Declaring a variable
- 81.
- 82.
- 83. pair[2]; // ERROR!
- 84.
- 85. Type assertions (type casting)
- 86. Declaring a variable
- 87.
Spot the bug
function getFirst<T>(arr: T[]): T {
return arr;
}Need a hint?
The return type is T (one item) but what is actually being returned?
Show answer
The function returns the entire array (T[]) instead of a single item (T). Fix: change 'return arr' to 'return arr[0]'.
Explain like I'm 5
Generics are like a magic lunchbox that can hold anything you choose! You decide what goes inside - sandwiches, fruit, or candy. The lunchbox doesn't care what's inside, but once you pick, it remembers. Same lunchbox shape, different food!
Fun fact
Utility types are so powerful that some developers write entire applications using just Partial, Pick, and Omit to manage types!
Hands-on challenge
Create a generic ApiResponse<T> interface. Use Partial, Pick, Omit with it. Make POST endpoint with Partial, GET with Pick!
More resources
- TypeScript Handbook - Generics (TypeScript Official)
- TypeScript Utility Types (TypeScript Official)
- TypeScript Generics Explained (Web Dev Simplified)