As a project grows, types should help keep related contracts synchronized. keyof describes the known property keys of a type. Indexed access obtains a property type. Mapped types transform a collection of properties. Built-in utilities cover many common transformations without a custom type-level program.
type Session = { id: string; topic: string; minutes: number; done: boolean };
type SessionPreview = Pick<Session, "id" | "topic">;
type SessionPatch = Partial<Pick<Session, "topic" | "minutes">>;
function readField<T, K extends keyof T>(value: T, key: K): T[K] {
return value[key];
}
function rename(session: Session, patch: SessionPatch): Session {
const next = { ...session, ...patch };
if (!next.topic.trim() || !Number.isInteger(next.minutes) || next.minutes <= 0) {
throw new Error("Invalid session update");
}
return next;
}
const session: Session = { id: "a", topic: "Types", minutes: 25, done: false };
const preview: SessionPreview = { id: session.id, topic: session.topic };
console.log(readField(preview, "topic"));
console.log(rename(session, { topic: "Generics" }).topic);
export {};
The lines are Types and Generics. The patch excludes id and done from the fields this typed function accepts. Runtime validation still checks the resulting values. For untrusted HTTP data, first build the patch from inspected allowed keys; a structural type does not remove extra runtime properties from an arbitrary object. Do not pass an unvalidated cast directly into this spread.
Learn utility types through a real requirement
Pick selects fields; Omit excludes fields; Partial makes each selected field optional; Required removes optionality; Readonly restricts assignment through that type. A Record models a dictionary from keys to values. With unchecked indexed access enabled, a runtime dictionary lookup still needs a missing-value check. ReturnType and Awaited can derive results from functions and promise-like values when doing so clarifies a boundary.
Partial<Session> would also permit changing id or omitting essential state. It is therefore too broad for many update APIs. Write the domain operation first, then select the type transformation that expresses it. A deeply nested conditional type is not inherently better than an understandable explicit union.
Types and executable dependencies
Use export type and import type for declarations needed only by the checker. The emitted JavaScript should not load a module merely to obtain an interface. Keep runtime validators in ordinary executable modules; importing only a type does not execute validation. ES module resolution and the host's available APIs remain runtime concerns even after compilation.
The satisfies operator checks whether a value meets a contract while often retaining more specific inferred information. An as assertion instead tells the checker to accept your claim; neither changes runtime data. Compile-time private fields, readonly types and omitted properties are not authorization checks. A server must still verify who may read or change each record.
Exercise: define a public preview of a record that also contains a private token. Return a newly constructed preview object and inspect its JSON. Then compare that with merely assigning the original object to a narrower type.
Solution guidance: assigning a narrower type does not delete the token from the underlying object. Explicitly construct the output with only allowed properties, and keep secrets out of browser bundles regardless of their TypeScript visibility.
Reference: Creating types from types.
Previous: Interfaces, Generics and Typed Collections. Next: Testing and Debugging JavaScript and TypeScript.

