Typorithm · hard

7. Utility types and shallow transformations

typescriptstable-typescriptinterviewindependent-practice
Saved on this device
Full lesson · 4 learning steps Open any exercise for focused practice
1

Step 1 of 4 · intro card

7. Utility types and shallow transformations

0%

Concept and contract

Choose standard helpers while keeping validation, serialization and nested ownership explicit.

Pick selects known properties; Omit removes known properties from a static view. Partial makes properties optional, Required makes them required, and Readonly restricts writes through the resulting reference. These helpers express common relationships clearly without a custom mapped type for every use.

They do not perform runtime projection, supply defaults or deep-freeze nested values. Partial<Model> is often convenient for an internal patch, but a public update API may need to exclude immutable ids, reject unknown fields and distinguish omission from explicit null.

Mechanism and worked example

The example derives an editable patch from selected Role fields.

The example derives an editable patch from selected Role fields. A caller cannot statically patch id because it is outside the Pick. The implementation constructs a new Role by spreading the trusted patch over the original record, preserving existing fields.

Record describes a key-to-value relationship, while Extract and Exclude filter union members. NonNullable removes null and undefined statically. Use Awaited and ReturnType for their established extraction contracts rather than reimplementing only the easy cases.

Complete worked example

typescript

type Role = { id: string; title: string; remote: boolean };
type Patch = Partial<Pick<Role, "title" | "remote">>;
function edit(role: Role, patch: Patch): Role { return { ...role, ...patch }; }
const updated = edit({id:"a",title:"QA",remote:false},{remote:true});
Reasoning
The patch vocabulary is finite and deliberately excludes the immutable id field.

Counterexamples and common mistakes

The rejected patch attempts to modify id.

The rejected patch attempts to modify id. A wider runtime object could still contain id, so this is not sufficient validation for external JSON. A boundary parser must select allowed properties rather than merely asserting that the object is a Patch.

Readonly is shallow: a nested array may still expose push. A generic DeepReadonly needs explicit policies for functions, arrays, maps and special objects. Do not introduce it as a universal one-line guarantee of runtime immutability or use it to avoid clarifying ownership.

Rejected counterexample: expected compiler diagnostic

typescript

type Patch = Partial<Pick<{id:string;title:string}, "title">>;
// @ts-expect-error id is outside the editable vocabulary.
const invalid: Patch = { id: "replacement" };
Reasoning
The helper rejects a known misuse but does not validate arbitrary external objects.

Independent practice

Create a ProfilePreview containing only name and headline from a larger profile.

Create a ProfilePreview containing only name and headline from a larger profile. Write a runtime projector that returns exactly those fields. Then define an editable headline-only patch and show that email is not part of that public contract.

Test Object.keys on the preview as well as the displayed values. Reject an email patch statically. Optional extension: validate an unknown incoming patch and reject unsupported keys; keep this separate from the basic use of Pick and Partial.

Step 1: Select name and headline in the type. Step 2: Construct only those two runtime properties.

Practice starter: solve before reading the solution

typescript

type Profile = { name: string; headline: string; email: string };
type Preview = Pick<Profile, "name" | "headline">;
function preview(profile: Profile): Preview { throw new Error("TODO"); }
Reasoning
Pair the static preview contract with a projector that actually removes private data.
  • Hint 1: Pick does not strip email by itself.
  • Hint 2: Test Object.keys as well as field values.

Solution, tests and trade-offs

The solution combines a small Pick type with explicit construction.

The solution combines a small Pick type with explicit construction. The preview annotation documents the intended surface, while construction actually strips the private email. A Partial<Pick<...>> expresses optional editing without broadening the entire profile to arbitrary missing fields.

Runtime output checks catch accidental leakage, and a negative patch assignment catches vocabulary drift. This combination prevents a common mistake: assuming Omit or Pick removes properties from an object merely because a function’s return type uses the helper.

Practice solution and acceptance checks

typescript

type Profile = { name: string; headline: string; email: string };
type Preview = Pick<Profile, "name" | "headline">;
type HeadlinePatch = Partial<Pick<Profile, "headline">>;
function preview(profile: Profile): Preview { return { name: profile.name, headline: profile.headline }; }

// Acceptance checks
const x=preview({name:"A",headline:"QA",email:"private"});if(Object.keys(x).sort().join(",")!=="headline,name")throw new Error("leak");
Reasoning
Pick describes the public fields; explicit construction enforces the serialized shape.

Interview reasoning and next step

In interviews, explain one appropriate use and one limitation for Partial, Pick and Readonly.

In interviews, explain one appropriate use and one limitation for Partial, Pick and Readonly. Distinguish Extract on unions from Pick on object keys. Show an output projection rather than claiming a utility type sanitizes user data.

Continue with correlated unions, where independently applying broad helpers can accidentally lose relationships between a tag and its payload. Standard utilities are building blocks, not a substitute for a valid domain model.

Optional stretch — after the core task works: Decode an unknown headline patch with an allowed-key policy.

Choose standard helpers while keeping validation, serialization and nested ownership explicit.