TypeScriptDX

TypeScript Generics for Real Devs

Alejandro Gómez6 min read

Stop copy-pasting types. A practical guide to generics, conditional types, and utility types that will make your codebase 10x more maintainable.

The problem generics solve

Without generics you end up with three bad options: use any (unsafe), copy-paste the type for every variant (noisy), or write overloads (verbose). Generics give you a fourth option: write it once and let the caller specify the type.

Generic functions

The simplest generic is a function that returns whatever type it receives. The <T> is a type parameter — think of it as a variable, but for types.

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

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

Constraints with extends

You can constrain what T is allowed to be. This lets you access properties on T that TypeScript can guarantee exist.

function getLabel<T extends { label: string }>(item: T): string {
  return item.label; // safe — T must have label
}

Conditional types

Conditional types let you express if-else logic at the type level. They're the basis for most advanced utility types in TypeScript's standard library.

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

type A = IsString<string>; // true
type B = IsString<number>; // false

Utility types worth knowing

  • Partial<T> — makes all properties optional
  • Required<T> — makes all properties required
  • Pick<T, K> — creates a type with only the listed keys
  • Omit<T, K> — creates a type without the listed keys
  • ReturnType<F> — extracts the return type of a function
  • Awaited<T> — unwraps a Promise type
A well-typed codebase isn't one where every variable has an explicit annotation. It's one where the types are accurate, narrow, and do real work — catching bugs before you run the code.