All posts/
TypeScriptJavaScriptBest Practices

Mastering TypeScript Generics: From Basics to Advanced Patterns

Jan 14, 2026·10 min read

Unlock the full power of TypeScript generics — conditional types, mapped types, infer keyword, and real-world utility type patterns that make codebases type-safe and maintainable.

Why Generics?

Generics let you write functions and data structures that work over a variety of types while preserving type information. Without them you'd reach for `any` and lose all the safety TypeScript gives you.

typescript
// Without generics — information is lost
function first(arr: any[]): any { return arr[0]; }

// With generics — the return type matches the array element type
function first<T>(arr: T[]): T | undefined { return arr[0]; }

const n = first([1, 2, 3]);   // number | undefined ✓
const s = first(['a', 'b']);  // string | undefined ✓

Conditional Types

Conditional types let you express branching type logic: `T extends U ? X : Y`. They are the foundation for most advanced utility types in the TypeScript standard library.

typescript
type IsArray<T> = T extends any[] ? true : false;

type A = IsArray<string[]>;  // true
type B = IsArray<number>;    // false

// Unwrap the element type of any array
type Unwrap<T> = T extends (infer Item)[] ? Item : T;

type C = Unwrap<string[]>;   // string
type D = Unwrap<number>;     // number

Mapped Types

Mapped types iterate over the keys of an existing type to produce a new one. All built-in utility types like `Partial<T>`, `Readonly<T>`, and `Required<T>` are just mapped types under the hood.

typescript
// Make every property optional and nullable
type Nullable<T> = { [K in keyof T]: T[K] | null };

interface User { id: number; name: string; email: string }
type NullableUser = Nullable<User>;
// { id: number | null; name: string | null; email: string | null }

Real-World: A Type-Safe API Client

typescript
type ApiRoutes = {
  '/users':        { response: User[] };
  '/users/:id':    { params: { id: string }; response: User };
  '/posts':        { response: Post[] };
};

async function get<P extends keyof ApiRoutes>(
  path: P,
): Promise<ApiRoutes[P]['response']> {
  const res = await fetch(path);
  return res.json();
}

const users = await get('/users');   // type: User[]
const post  = await get('/posts');   // type: Post[]
TypeScript generics are not just about reusability — they are about making impossible states unrepresentable at compile time.
PreviousBuilding Scalable Microservices with Node.js and DockerNextNext.js 15 App Router: Server Components & Streaming Explained