Typorithm · easy

3. Objects, structural compatibility and optional fields

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

3. Objects, structural compatibility and optional fields

0%

Concept and contract

Model object capabilities without confusing structural typing with exact runtime validation.

Object types describe required capabilities: named properties, their readable types and permitted writes. TypeScript is structural, so a value does not need to declare that it implements a particular interface when its shape already meets the contract.

Use an interface for an extendable object contract or a type alias when composing unions and other type expressions. Neither performs runtime validation. Prefer small contracts that describe what a function consumes instead of exposing every property of a database row.

Mechanism and worked example

The example projects a larger stored record through a smaller public contract.

The example projects a larger stored record through a smaller public contract. The extra company field does not prevent assignment through a variable, because the consumer only requires id and title. A readonly property prevents writes through that typed reference, not through every alias.

Optional subtitle means the property may be absent. Under exactOptionalPropertyTypes it does not mean a present property may hold undefined unless undefined is explicitly included. Reading either an absent optional property or an explicitly permitted undefined value still requires handling a possibly undefined result.

Complete worked example

typescript

interface Summary { readonly id: string; title: string; subtitle?: string }
const stored = { id: "j1", title: "Developer", company: "Acme" };
const summary: Summary = stored;
Reasoning
A larger stored object satisfies the smaller structural contract; the annotation does not strip company.

Counterexamples and common mistakes

Fresh object literals receive excess property checking, which catches likely misspellings such as titel.

Fresh object literals receive excess property checking, which catches likely misspellings such as titel. Passing a previously stored wider value can be legal; this does not mean exactness has been established. A runtime object can still contain additional fields after either assignment.

Do not claim that readonly produces a deep immutable record. Nested arrays and aliases may remain mutable. At a public API boundary, construct the explicit output projection so private fields cannot leak merely because a static type describes a smaller shape.

Rejected counterexample: expected compiler diagnostic

typescript

interface Summary { readonly id: string; title: string }
const value: Summary = { id: "j1", title: "Developer" };
// @ts-expect-error readonly through this reference.
value.id = "j2";
Reasoning
readonly rejects a write through Summary; it does not freeze the runtime value.

Independent practice

Create a CompanySummary with readonly id, required name and optional website.

Create a CompanySummary with readonly id, required name and optional website. Implement publicCompany from a larger internal record that includes a secret note. Return a fresh object containing only public fields and omit website entirely when no address exists.

Test both presence and absence, plus the absence of the secret field at runtime. Add a rejected write to id in a compiler fixture. The task is a data projection problem as well as a typing exercise; a return annotation alone does not remove extra properties.

Step 1: Write the public field list. Step 2: Construct a fresh output and omit absent website.

Practice starter: solve before reading the solution

typescript

type Internal = { id: string; name: string; website: string | undefined; secret: string };
function publicCompany(value: Internal) { throw new Error("TODO: project public fields"); }
Reasoning
Build a fresh public object and preserve absent optional properties.
  • Hint 1: A smaller return annotation does not strip secret.
  • Hint 2: A conditional spread can preserve absence.

Solution, tests and trade-offs

The solution explicitly constructs the output and uses a conditional spread for website.

The solution explicitly constructs the output and uses a conditional spread for website. That preserves absent-property semantics and removes the private note. The declared return type protects checked consumers, while the object construction enforces the serialized public shape.

Compare Object.keys on input and output. Then try returning the original input while keeping the annotation: it can compile, yet the secret still appears during serialization. This counterexample demonstrates why privacy boundaries require deliberate runtime projection.

Practice solution and acceptance checks

typescript

type Internal = { id: string; name: string; website: string | undefined; secret: string };
type CompanySummary = { readonly id: string; name: string; website?: string };
function publicCompany(value: Internal): CompanySummary {
  return { id: value.id, name: value.name, ...(value.website === undefined ? {} : { website: value.website }) };
}

// Acceptance checks
const x=publicCompany({id:"c1",name:"Acme",website:undefined,secret:"private"});if("secret" in x||"website" in x)throw new Error("projection");if(publicCompany({id:"c2",name:"B",website:"https://example.com",secret:"x"}).website!=="https://example.com")throw new Error("website");
Reasoning
Conditional construction removes the secret and omits website when unavailable.

Interview reasoning and next step

In an interview, explain structural assignment, fresh-literal excess checks and property omission as separate mechanisms.

In an interview, explain structural assignment, fresh-literal excess checks and property omission as separate mechanisms. Show why a typed API response may still leak fields if the implementation returns an unfiltered object.

Next, represent alternatives with unions instead of an object containing many unrelated optional fields. Required properties are most useful when the type can say which combinations actually exist in each state.

Optional stretch — after the core task works: Add a decoder for an unknown internal input fixture.

Model object capabilities without confusing structural typing with exact runtime validation.