Hedronite Lesson · Polyglot-Dev / TypeScript · Fri 2026-09-18

Generics and utility types

Name a placeholder once. Reshape object types with Partial, Pick, Omit, and Record.

Lesson Class: Duha (TypeScript Primary track)
Focus: generic T · extends · Partial · Pick · Omit · Record · mapped types
Code Blocks: clean blocks, explanation in prose
Done-criteria: generic + extends + Partial/Pick/Omit/Record
Grounding: Cherny Ch.4 + Ch.6 on disk · TS Handbook Utility Types + Generics
The placeholder
T ties array element, predicate, and return so overloads do not pile up.
The bound
extends names what T must provide before the body may use it.
The reshape
Partial/Pick/Omit/Record change object shapes without retyping fields.
Name a placeholder once. Reshape object types with Partial, Pick, Omit, and Record.

<!-- hal:authoritative:yaml -->

Name a placeholder once. Reuse it across parameters and return types. Then reshape object types with Partial, Pick, Omit, and Record instead of hand-copying fields.

§I — Frame

Duha Primary TypeScript session 5. A teammate’s 2026-09-18 kick seats Topics #9 here and defers Bun-centric rows 5–8 (Bun.serve, Bun.file, bun test, @types/bun) at this clock. Prior Asr sessions taught runtime, strict basics, install, and moduleResolution: bundler. Today the track turns to type reuse: generics and the built-in utilities that reshape object types.

Done-criteria: Can write a generic function/type, constrain with extends, and apply Partial / Pick / Omit / Record with intent.

Primary cites: Cherny Programming TypeScript Chapter 4 (generics under Polymorphism) and Chapter 6 (Record, mapped types) on disk at , plus the live TypeScript Handbook Utility Types for the catalog Cherny only partly freezes in 2019 (Omit lands later). Handbook Generics is secondary wording for constraints.

Home: . Do not dump into Polyglot-Dev/Web/.

§II — Why a placeholder beats an overload pile

Cherny’s filter walk shows the trap. A number-only signature works for numbers. Overload for strings. Overload for object[] and watch property access die: object carries no fields. The fix is a generic type parameter: a placeholder that ties the array element, the predicate argument, and the return element together:

type Filter = {
  <T>(array: T[], f: (item: T) => boolean): T[];
};

const filter: Filter = (array, f) => {
  const result = [];
  for (let i = 0; i < array.length; i++) {
    const item = array[i];
    if (f(item)) result.push(item);
  }
  return result;
};

Angle brackets declare the parameter; placement scopes it. Call-site inference usually binds T from the array argument. Explicit bindings are all-or-nothing: annotate every required generic, or none.

map needs two placeholders: T for input members, U for output members. Cherny’s standing advice: use generics when they keep the code general without lying about shape.

§III — Constraints, aliases, and defaults

A bare T accepts anything. A constraint narrows what you may bind:

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

longest([1, 2], [1, 2, 3]); // number[]
longest("hi", "hello"); // string
// longest(10, 100); // error: number has no length

That is Cherny’s constraint idea and the Handbook’s extends form: T must be assignable to the bound.

Generics also live on type aliases, interfaces, and classes. Defaults fill optional type arguments when inference has nothing to chew on (type MyEvent<Type extends string = string> = ... in Cherny’s defaults section). Prefer inference; reach for defaults when the API has a sensible fallback.

§IV — Record, keyof, and mapped types

Cherny implements Record with a mapped type:

type Record<K extends keyof any, T> = {
  [P in K]: T;
};

Record<Keys, Type> builds an object whose keys are Keys and whose values are Type. Unlike a plain index signature, Keys can be a string-literal union, so missing days fail loudly.

Mapped types generalize that reshape:

type MyMappedType = {
  [Key in UnionType]: ValueType;
};

You map over keys. Pair with keyof when you need “every key of this type” as a union. That is the engine under several utilities.

§V — Utility types you will reach for daily

The Handbook’s global utilities (cite the live page; do not memorize the full list today):

UtilityMove
Partial<Type>Every property optional — good for patches / updates.
Required<Type>Every property required — opposite of Partial.
Readonly<Type>Every property readonly — assignment fails in the type checker.
Pick<Type, Keys>Keep only listed keys.
Omit<Type, Keys>Drop listed keys (Handbook; post-dates Cherny 2019).
Record<Keys, Type>Object with constrained keys and a shared value type.
interface Todo {
  title: string;
  description: string;
  completed: boolean;
  createdAt: number;
}

type TodoPreview = Pick<Todo, "title" | "completed">;
type TodoUpdate = Partial<Todo>;
type TodoPublic = Omit<Todo, "createdAt">;

const cats: Record<"miffy" | "boris", { age: number }> = {
  miffy: { age: 4 },
  boris: { age: 7 },
};

Pick when the public surface is a named subset. Omit when the secret is easier to name than the keep-list. Partial when callers may send any subset. Record when keys are a known union and values share one shape.

§VI — One complete proof

  1. Write a generic pluck<T, K extends keyof T>(items: T[], key: K): T[K][] (or Cherny-style filter<T>) and call it so inference binds T.
  2. Constrain a second generic with extends so a bad call fails in the checker.
  3. From one interface, form Partial, Pick, and Omit aliases and assign legal objects to each.
  4. Build a Record<"a" | "b", number> and confirm a missing key is an error.
  5. Say aloud: generics share a placeholder; utilities reshape existing object types without retyping fields.

When those five hold, Topics #9’s selected depth is done.

§VII — Closing

Generics keep one implementation honest across element types. Utilities keep object shapes honest when you patch, preview, or index by a known key set. Cherny teaches the placeholder and the mapped-type engine; the Handbook names the global toolkit including Omit. Bun rows 5–8 stay deferred. Next Primary TypeScript depth follows the syllabus after #9 once a teammate seats it.

Done-criteria: Can write a generic function/type, constrain with extends, and apply Partial / Pick / Omit / Record with intent.

Related