Special Sponsor:PromptBuilder— Fast, consistent prompt creation powered by 1,000+ expert templates.
Make your Product visible here.Contact Us
Taking new clients

Need a dev team that ships?

011BQ builds TypeScript-first products, migrations, and internal tools for startups and scale-ups.

  • JS/TS migration & codebase modernisation
  • Custom dev tools & internal platforms
  • React, Next.js & Node.js engineering
  • Code review, architecture & tech advisory

Or reach us directly

011bq.com

Send us a message

We respond within 1 business day.

check_dark

Thank You!

Your message has been successfully sent. We will get back to you soon!

Message sent!

Thanks for reaching out. The 011BQ team will get back to you within 1 business day.

HomeChevronBlogChevronTypeScript Generics: A Practical Guide with Real-World Examples

TypeScript Generics: A Practical Guide with Real-World Examples

j
js2ts Team
22/05/2026·2 minutes 7 seconds read
TypeScript Generics: A Practical Guide with Real-World ExamplesTypeScript Generics: A Practical Guide with Real-World Examples

What Are TypeScript Generics?

Generics let you write code that works with multiple types while preserving type information. Without generics, you'd choose between losing type safety (using any) or writing duplicate code for each type. Generics give you a third option: write it once, typed for everything.

// Without generics — loses type info
function first(arr: any[]): any {
  return arr[0];
}

// With generics — type-safe and reusable
function first<T>(arr: T[]): T {
  return arr[0];
}

const num = first([1, 2, 3]);    // num: number
const str = first(['a', 'b']);   // str: string

Generic Functions

The most common use of generics is in utility functions that operate on arrays or data structures:

function groupBy<T, K extends string | number>(
  arr: T[],
  key: (item: T) => K
): Record<K, T[]> {
  return arr.reduce((groups, item) => {
    const groupKey = key(item);
    return {
      ...groups,
      [groupKey]: [...(groups[groupKey] || []), item],
    };
  }, {} as Record<K, T[]>);
}

const users = [
  { name: 'Alice', role: 'admin' },
  { name: 'Bob', role: 'user' },
  { name: 'Carol', role: 'admin' },
];

const byRole = groupBy(users, u => u.role);
// byRole: Record

Generic Interfaces

Generic interfaces let you describe data structures that work with any type:

interface ApiResponse<T> {
  data: T;
  status: number;
  message: string;
  timestamp: Date;
}

interface PaginatedResponse<T> {
  items: T[];
  total: number;
  page: number;
  pageSize: number;
}

// Usage
type UserResponse = ApiResponse<User>;
type UsersPage = PaginatedResponse<User>;

Generic Constraints

Use extends to restrict what types a generic can accept:

// T must have an 'id' property
function findById<T extends { id: number }>(
  items: T[],
  id: number
): T | undefined {
  return items.find(item => item.id === id);
}

// K must be a key of T
function pluck<T, K extends keyof T>(items: T[], key: K): T[K][] {
  return items.map(item => item[key]);
}

const names = pluck(users, 'name'); // string[]
// pluck(users, 'invalid') — TypeScript error!

Conditional Types

Conditional types are generics that select a type based on a condition:

type NonNullable<T> = T extends null | undefined ? never : T;

type IsString<T> = T extends string ? true : false;

// Unwrap a Promise type
type Awaited<T> = T extends Promise<infer U> ? U : T;

type StringResult = Awaited<Promise<string>>; // string
type NumberResult = Awaited<number>; // number

The infer Keyword

The infer keyword extracts a type from a conditional type expression:

// Extract the return type of a function
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

// Extract the element type of an array
type ElementType<T> = T extends (infer E)[] ? E : never;

type StringElement = ElementType<string[]>; // string

Common Generic Patterns

Optional properties with DeepPartial:

type DeepPartial<T> = {
  [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};

ReadOnly recursive:

type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};

For a complete reference of TypeScript's built-in generic utility types, see our TypeScript Utility Types guide.

Share