Skip to main content

[Medium] Generics

1. What are generics?

What problem do generics solve in TypeScript?

Generics let you write reusable, type-safe code without hard-coding one concrete type.

Without generics, you often duplicate functions for each type. With generics, one implementation can work for many types while preserving type information.

2. Generic functions

Basic syntax

function identity<T>(value: T): T {
return value;
}

const n = identity<number>(123);
const s = identity('hello'); // type is inferred as string

Generic array helper

function firstItem<T>(items: T[]): T | undefined {
return items[0];
}

const firstNumber = firstItem([1, 2, 3]);
const firstString = firstItem(['a', 'b']);

3. Generic constraints

Sometimes you need a generic type with required fields.

function getLength<T extends { length: number }>(value: T): number {
return value.length;
}

getLength('TypeScript');
getLength([1, 2, 3]);
// getLength(123); // Error: number has no length

extends here means "must satisfy this shape".

4. Multiple type parameters

function toPair<K, V>(key: K, value: V): [K, V] {
return [key, value];
}

const pair = toPair('id', 1001); // [string, number]

This is common in maps, dictionaries, and data transformation utilities.

5. Generic interfaces and type aliases

Generic interface

interface ApiResponse<T> {
success: boolean;
data: T;
error?: string;
}

const userResponse: ApiResponse<{ id: number; name: string }> = {
success: true,
data: { id: 1, name: 'Pitt' },
};

Generic type alias

type Result<T> =
| { ok: true; value: T }
| { ok: false; message: string };

6. Generic classes

class Queue<T> {
private items: T[] = [];

enqueue(item: T): void {
this.items.push(item);
}

dequeue(): T | undefined {
return this.items.shift();
}
}

const numberQueue = new Queue<number>();
numberQueue.enqueue(10);

7. Default generic types

You can define fallback types.

type ApiResult<T = string> = {
data: T;
status: number;
};

const a: ApiResult = { data: 'ok', status: 200 };
const b: ApiResult<number> = { data: 1, status: 200 };

8. keyof with generics

function pick<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}

const user = { id: 1, name: 'Alex' };
const name = pick(user, 'name'); // string

This pattern is core to many utility types.

9. Common mistakes and best practices

Common mistakes

  • Using too many generic parameters without clear purpose
  • Naming everything T in complex APIs
  • Falling back to any instead of proper constraints

Best practices

  • Use descriptive names in complex cases (TItem, TValue)
  • Add constraints where behavior depends on shape
  • Prefer inference first, explicit type arguments only when needed
  • Keep generic APIs small and focused

10. Quick interview answers

Q1: What is the biggest benefit of generics?

Reusable code with compile-time type safety.

Q2: What does T extends U mean?

T must be assignable to U; it is a generic constraint.

Q3: When should I avoid generics?

When the abstraction does not improve clarity or only supports one concrete type.