Skip to main content
๐Ÿ“œ WAYPOINT LESSON

Generics: Reuse With Type Safety

โญโญ intermediateโณ 15 min read๐Ÿ“ Lesson 88 of 143

Type parameters, inference at call sites, constraints with extends, and utility types you will actually use.

Generics are functions for types: one implementation, types vary per use, and the compiler tracks each use separately.

The idea

function first<T>(items: T[]): T | undefined {
  return items[0];
}
const a = first(["x", "y"]); // T inferred as string โ†’ a: string | undefined
const b = first([1, 2]); // T inferred as number โ†’ b: number | undefined

Call-site inference means you rarely write the angle brackets โ€” you get them from context.

Constraints

When the body needs capabilities beyond unknown, constrain:

function pluck<T, K extends keyof T>(items: T[], key: K): T[K][] {
  return items.map((i) => i[key]);
}
pluck(users, "email"); // โœ“ โ€” and "emial" is a compile error

K extends keyof T ties the key to keys that actually exist on T โ€” a typo becomes a compile-time error instead of undefined at runtime.

Utility types

The built-ins cover 90% of needs:

  • Partial<T> โ€” all fields optional (patch/update payloads)
  • Pick<T, K> / Omit<T, K> โ€” select or remove fields (public view of a row)
  • ReturnType<typeof fn> โ€” capture what a function gives back
  • Record<K, V> โ€” typed dictionaries
type PublicUser = Omit<User, "passwordHash">;
function updateUser(id: number, patch: Partial<User>) { ... }

Use generics when the SHAPE relationship matters (in โ†’ out); use unknown when you truly accept anything.

โšก Now practice

Ready to Code
Generics โ€” PracticeShape-preserving functions: a first-or-default picker, a keyed group-by, and a result-wrapping combinator whose output shape mirrors its input.
3 challenges ยท ยท ~18 min