skip to content

Given `interface Options { retries?: number; timeout?: number }` in TypeScript, why does `const o = { retres: 3 }; const opts: Options = o;` still error, even though `o` is a variable rather than a fresh object literal?

level: middleimportance: should knowfreq 38%

answer

  1. all properties optional means nothing required
  2. zero overlap, not one bad key
  3. not tied to literal freshness
  4. empty object is still allowed
  5. coarse heuristic, per-assignment

basics

~20 s

Options is a weak type — every property is optional — so almost anything is structurally assignable to it. TypeScript adds a weak-type check: a source with properties but none in common with the target is rejected, and it applies to variables too.

solid answer

~50 s

A type whose properties are all optional is called a *weak type*, and structurally it requires nothing at all, so any object would satisfy it. That makes typos invisible: `{ retres: 3 }` would sail through. To close the hole the compiler adds a weak-type check — if the source type has at least one property and none of them appear in the weak target, the assignment is an error, reported as "Type '{ retres: number; }' has no properties in common with type 'Options'". The important difference from excess-property checking is that this rule is not tied to literal freshness: it applies to any assignment, which is why hoisting the value into a variable does not rescue it. An empty object still assigns fine, and the moment one real property overlaps, the check is satisfied.

code

typescript · 10 lines
typescript
interface Options { retries?: number; timeout?: number }

const src = { retres: 3 };
// @ts-expect-error weak type: no properties in common with Options
const bad: Options = src;

const empty: Options = {};                       // exempt: source has no properties
const partial: Options = { retries: 3 };         // one real overlap is enough

console.log(bad, empty, partial);

go deeper

for a junior

Recognise the message "has no properties in common with type" and know it means the object you supplied shares nothing with the expected options shape — usually a misspelled key. State the fix: correct the name.

for a middle

Define a weak type as one whose properties are all optional, state the check's exact condition — source has properties, none overlap — and contrast it with excess-property checking, which needs a fresh literal and fails on a single unknown key.

for a senior

Show that the check is coarse: one correct key satisfies it while other typos ride along. Explain when you would restructure an all-optional options type, and where runtime validation has to take over for externally supplied config.

for a principal

Own the API-design consequence: an entirely optional options bag is a shape the type system can barely defend. Weigh required discriminating fields or variant unions against caller convenience, and set the house convention for configuration surfaces.

## What makes a type "weak" The compiler treats a type as weak when it has at least one property and **every** property is optional. `interface Options { retries?: number; timeout?: number }` is the textbook case — the sort of type that shows up on every options bag, config object and settings parameter. Structural assignability asks "does the source have everything the target requires?" A weak type requires nothing. So `{ retres: 3 }`, `{ colour: "red" }` and `{}` are all structurally assignable to `Options`, and a misspelled option name would be accepted silently — arriving at run time as a key nobody reads, with the default quietly in effect. ## The weak-type check Because that failure mode is so common, the compiler adds a rule: when the target is weak and the source type has at least one property, at least one of those properties must also exist in the target. Otherwise you get error 2559, `Type '{ retres: number; }' has no properties in common with type 'Options'`. ```ts interface Options { retries?: number; timeout?: number } const a: Options = { retres: 3 }; // error — fresh literal, excess property check const src = { retres: 3 }; const b: Options = src; // error — weak type check, no overlap const c: Options = {}; // fine — source has no properties const mixed = { retries: 3, retres: 3 }; const d: Options = mixed; // fine — 'retries' overlaps ``` ## Why this is a different rule from excess-property checking They are easy to conflate because both surface as "TypeScript complained about my options object", but they differ on three axes: | | Excess property check | Weak type check | |---|---|---| | Trigger | source is a **fresh object literal** | target is a **weak type** | | Fails when | the literal has a key the target lacks | the source shares **no** key with the target | | Survives a variable? | no — widening disables it | yes — it applies to any assignment | That second row matters. Excess-property checking is per-property and strict: one unknown key is enough. The weak-type check is a much coarser "are we even talking about the same thing?" heuristic: it only complains when there is *zero* overlap. So `{ retries: 3, retres: 3 }` passes the weak-type check via the variable route, even though `retres` is still a typo. The two rules together catch far more than either alone, but neither is complete. ## The exemptions - **An empty source.** `const c: Options = {}` is legal: the source has no properties, so the rule does not engage. This is deliberate — passing "no options" must stay expressible. - **A target with an index signature.** A type carrying an index signature is not weak, because it explicitly declares that arbitrary keys are part of the contract. - **A target with call or construct signatures.** Those are not property-only types, and the weak heuristic does not apply. ## Designing around it If a genuinely all-optional options type is producing typo bugs that this coarse check misses, the fix is in the modelling, not in the checker: - Give the type **at least one required property** so structural typing itself starts doing work. - Model the options as a **union of variants**, each requiring the field that identifies it, so an unknown combination cannot type-check. - Keep the value as a **fresh literal at the call site** rather than hoisting it into a variable, so per-property excess checking stays in force — this is a real argument for inlining options objects. - For data arriving from outside your program, validate at run time; no compile-time rule can help with a value the compiler never saw written. ## How to answer it Name the rule ("weak type detection"), state its condition precisely (target all-optional, source has properties, no overlap), and — the part interviewers are really listening for — contrast it with excess-property checking on the freshness axis. Candidates who know only the literal rule cannot explain why the variable version still fails.

  • Does the weak-type check fire when you assign an empty object literal to an all-optional type?
    No. The rule engages only when the source type has at least one property; an empty source is exempt so that "pass no options" stays expressible. `const o: Options = {}` compiles, which is the behaviour you want for a defaults-everywhere options bag.
  • How would you design an options type so that a misspelled key is caught even through a variable?
    Give the type at least one required property, so ordinary structural assignability rejects a source that lacks it, or model the options as a union of variants where each member requires its identifying field. Keeping the object inline as a fresh literal also preserves the per-property excess check.
  • Does adding an index signature to the options type change any of this?
    Yes, and it weakens both protections. A type with an index signature is not treated as weak, so the weak-type check no longer applies, and the index signature also declares arbitrary keys legal, so excess-property checking stops rejecting them. You have chosen "any key is valid" as the contract.

saying these in an interview costs you the question

  • Says a variable always escapes every extra-property complaint
  • Thinks the error means one unknown key is enough to fail
  • Believes an empty object is rejected by an all-optional type
  • Confuses the weak-type check with excess-property checking
  • Claims the error comes from the object literal being fresh

context