Skip to content
All posts
Tutorial4 min read

Why TypeScript Generics Are More Powerful Than You Think

DebuggerMe TeamDebuggerMe TeamApril 15, 2026
On this page

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:

typescript
// 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:

typescript
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:

typescript
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:

typescript
// 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] infer only works inside the extends clause 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:

typescript
// 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:

typescript
// 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:

typescript
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:

TypeDescription
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:

  1. Basic generics<T> as a type placeholder
  2. Constraintsextends to restrict T
  3. Conditional typesT extends X ? A : B
  4. infer — extract types from patterns
  5. Mapped types — transform every key of T

Start using them in your API layer and you'll never want to go back to any.

DebuggerMe Team

Written by

DebuggerMe Team

The DebuggerMe team builds developer tools, writes technical content, and helps teams ship better software.

Share this post

Back to all posts

Related Articles

All articles →