Generics: Constraints & Defaults

Bound a type parameter so you can safely use its members, and give it a sensible default.

Generics become genuinely powerful once you constrain them. A bare <T> could be anything, so you can barely touch it. Adding extends teaches the compiler what T is guaranteed to have.

function longest<T extends { length: number }>(a: T, b: T): T {
  return a.length >= b.length ? a : b;
}
longest("hi", "hello"); // strings have length
longest([1], [1, 2]);    // arrays too

Key-based constraints

The K extends keyof T pattern is the backbone of type-safe property access β€” the key must be a real key of the object.

Defaults

A type parameter can default, just like a function argument: <T = string>. Callers who don't specify get the default; those who do, override it.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • A sort utility constrains its type parameter to Comparable so only types that support ordering can be sorted.
  • A repository class defaults its entity type parameter to BaseEntity so simple usages require no explicit type argument.
  • A merge utility constrains both type parameters to object to prevent accidental use with primitives.

More examples

Generic with extends constraint

Constrains T to any type with a length property so the function can compare strings, arrays, or custom objects.

Example Β· ts
interface HasLength { length: number; }

function longest<T extends HasLength>(a: T, b: T): T {
  return a.length >= b.length ? a : b;
}

console.log(longest('hello', 'hi'));   // 'hello'
console.log(longest([1, 2], [3]));     // [1, 2]
// longest(1, 2); // Error: number has no .length

Default type parameter

A default type parameter (= unknown) makes the type argument optional while still allowing an explicit override.

Example Β· ts
interface ApiResponse<T = unknown> {
  data: T;
  status: number;
}

// No type argument needed β€” defaults to unknown
const raw: ApiResponse = { data: null, status: 200 };

// Explicit type argument
const typed: ApiResponse<{ name: string }> = {
  data: { name: 'Alice' },
  status: 200,
};

Constraint referencing another parameter

K extends keyof T constrains the key parameter to the object's actual keys and infers the exact value type.

Example Β· ts
function getField<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const user = { id: 1, name: 'Alice', active: true };
const name = getField(user, 'name');   // string
const id   = getField(user, 'id');     // number

Discussion

  • Be the first to comment on this lesson.
Generics: Constraints & Defaults β€” TypeScript | SoundsCode