Generics are the feature most TypeScript developers underuse. Once you understand them deeply, you'll write code that's both more flexible and more type-safe simultaneously.
What Are Generics?
A generic is a placeholder for a type that gets filled in at call time. Think of it like a function parameter, but for types:
// Without generics - loses type information
function identity(value: any): any {
return value;
}
// With generics - type is preserved
function identity<T>(value: T): T {
return value;
}
const result = identity(42); // result: number ✅
const str = identity("hello"); // str: string ✅
Generic Constraints
Use extends to restrict what types a generic can be:
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: "Alice", age: 30 };
getProperty(user, "name"); // string ✅
getProperty(user, "age"); // number ✅
getProperty(user, "role"); // ❌ Error: "role" not in type
Conditional Types
This is where things get powerful. Conditional types let you create types that change based on conditions:
type IsArray<T> = T extends any[] ? true : false;
type A = IsArray<string[]>; // true
type B = IsArray<number>; // false
The infer Keyword
infer lets you extract a type from within a conditional type:
// Extract the return type of any function
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type Result = ReturnType<() => Promise<string>>; // Promise<string>
// Unwrap a Promise
type Awaited<T> = T extends Promise<infer R> ? Awaited<R> : T;
type Resolved = Awaited<Promise<Promise<number>>>; // number
[!TIP]
inferonly works inside theextendsclause of a conditional type. You can use it multiple times in a single conditional to extract multiple type parameters.
Mapped Types
Mapped types transform every property of a type:
// Make all properties optional (like TypeScript's built-in Partial<T>)
type MyPartial<T> = {
[K in keyof T]?: T[K];
};
// Make all properties readonly
type MyReadonly<T> = {
readonly [K in keyof T]: T[K];
};
// Transform values
type Stringify<T> = {
[K in keyof T]: string;
};
Remapping Keys
TypeScript 4.1+ allows remapping keys with as:
// Add a "get" prefix to every key
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
type User = { name: string; age: number };
type UserGetters = Getters<User>;
// { getName: () => string; getAge: () => number }
Real-World Pattern: Type-Safe API Response
Here's a practical generic pattern for API responses:
type ApiResponse<T> =
| { success: true; data: T; error: null }
| { success: false; data: null; error: string };
async function fetchUser(id: string): Promise<ApiResponse<User>> {
try {
const user = await db.users.findById(id);
return { success: true, data: user, error: null };
} catch (e) {
return { success: false, data: null, error: (e as Error).message };
}
}
// Usage - TypeScript narrows the type automatically
const result = await fetchUser("123");
if (result.success) {
console.log(result.data.name); // User ✅
} else {
console.log(result.error); // string ✅
}
Built-in Utility Types
TypeScript ships with these generic utility types. Learn them:
| Type | Description |
|---|---|
Partial<T> | All properties optional |
Required<T> | All properties required |
Readonly<T> | All properties readonly |
Pick<T, K> | Keep only keys K |
Omit<T, K> | Remove keys K |
Record<K, V> | Object with keys K and values V |
ReturnType<T> | Return type of a function |
Awaited<T> | Unwrap a Promise |
NonNullable<T> | Remove null and undefined |
Summary
Generics unlock a level of type safety that makes your code self-documenting and refactor-proof. The pattern progression:
- Basic generics:
<T>as a type placeholder - Constraints:
extendsto restrict T - Conditional types:
T extends X ? A : B infer: extract types from patterns- Mapped types: transform every key of T
Start using them in your API layer and you'll never want to go back to any.
Written by
DebuggerMe TeamThe DebuggerMe team builds developer tools, writes technical content, and helps teams ship better software.
Related Articles
All articles →TypeScript 5.8 Major Speed Up
TypeScript 5.8 introduces return type check optimizations and compile time improvements, making local development faster.
React 19 Stable Is Finally Here
React 19 has entered stable release, introducing the React Compiler, server actions, and cleaner async form handling hooks.
Getting Started with Next.js 16: A Complete Guide
Everything you need to know to build fast, modern web applications with Next.js 16 App Router, Server Components, and TypeScript. From project setup to production deployment.