Skip to content

TypeScript Generics Without the Headache

A practical guide to writing reusable, type-safe functions using generics — without over-abstracting.

1 min read

Generics let you write one function that works with many types without losing type safety.

Generic T placeholder diagram

Start simple

A generic is a placeholder for a type you don’t know yet.

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

first([1, 2, 3]);     // number | undefined
first(['a', 'b']);    // string | undefined

Constrain when needed

Use extends to restrict what types are allowed.

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

getLength('hello');   // 5
getLength([1, 2, 3]); // 3

Generic interfaces

interface ApiResponse<T> {
  data: T;
  status: number;
  error?: string;
}

type UserResponse = ApiResponse<User>;
type PostsResponse = ApiResponse<Post[]>;

Default type parameters

interface Paginated<T, Meta = Record<string, unknown>> {
  items: T[];
  total: number;
  meta: Meta;
}

When not to use generics

If a function only works with one type, skip the generic. If you need any to make the generic work, the abstraction is wrong. Use concrete types until two or more callers need the same shape.

// over-engineered
function identity<T>(x: T): T { return x; }

// just use the type
function greet(name: string): string { return `Hello, ${name}`; }

Keep reading