skip to content

How does a component declare an input as required, optional or defaulted, and where is each rule enforced?

level: middleimportance: should knowfreq 60%

answer

  1. the declaration is a public contract
  2. build-time check versus runtime check
  3. who is compiled against the declaration
  4. absent is not the same as blank
  5. a default created once is shared

basics

~20 s

An input declaration states the name, whether a value must be supplied, and what stands in when it is absent. Required-ness is checked either at build time or at runtime; a default applies only when the caller omits the input.

solid answer

~50 s

A component's input declaration is its public contract, and each input is one of three kinds: **required** (no sensible rendering without it), **optional** (absence is a legitimate case the component handles), or **defaulted** (absent means a specific stated value). Enforcement happens in two different places. A static type check runs at build time and binds only the callers compiled against it — it cannot see markup assembled dynamically or values decoded from a network payload. A runtime validator checks the value as the instance is created, and in many setups reports only in development builds. A default applies when the input is *absent*, which is not the same as a caller explicitly passing a blank value — the blank usually wins. Because a default is part of the contract, changing one silently changes behaviour for every caller that relied on it.

go deeper

for a junior

Know the three kinds of input — required, optional, defaulted — and that a default applies only when the caller passes nothing at all. Do not invent a fallback to silence a missing required value.

for a middle

Explain the two enforcement points and their blind spots: a build-time check constrains compiled callers only; a runtime validator sees the value that actually arrived but often reports only in development builds.

for a senior

Demonstrate where you place checks in a real app: runtime validation at the boundary where decoded data enters the tree, loud failure for contract violations, and no default that turns a caller's bug into a plausible screen.

for a principal

Own the contract's evolution. Defaults and required-ness are published API; decide how they change, how callers learn, and how much runtime validation you keep in production for the debugging it buys.

## The declaration is a contract An input declaration names a value and states three things about it: what shape it accepts, whether the caller must supply it, and what happens when the caller does not. Together those declarations form the component's public surface — the only legal way in. Treat that surface the way you would a published function signature, because callers you cannot see are compiled against it. The three kinds: - **Required** — the component has no sensible rendering without it. A row cannot render without the record it displays. - **Optional** — absence is a real, handled case with its own behaviour, distinct from any concrete value. A missing label means render no label. - **Defaulted** — absence means one specific stated value. A missing size means the medium size, and every reader downstream sees "medium", not "absent". A subtle but common design error is declaring an input both required *and* defaulted. If the component can always fall back, the caller does not have to supply it, so it is optional with a default; keeping "required" is a lie in the contract that reviewers and tooling will trip over. ## Where each rule is actually enforced | enforcement point | when it runs | what it catches | what it misses | |---|---|---|---| | static type check | build time | a compiled caller that omits a required input or passes the wrong shape | anything not compiled against the declaration — dynamically assembled markup, values widened to an opaque shape, data from a network payload | | runtime validator | instance creation / input update | the actual value that arrived, including data that came from outside the build | nothing, but it usually only *reports* — and in many setups only in development builds | | the component's own guard | render time | the case the two above let through | nothing, but it costs a branch and a decision about what to render instead | The practical consequence: a build-time check is a *developer* tool, and a runtime validator is a *data* tool. They answer different questions and a component at the edge of the system — one fed by a response body, a query string or a persisted preference — needs the second even when the first is green. ## Absent is not the same as blank Three caller situations look alike and are not: 1. The input is **not passed at all** — the default applies. 2. The input is passed with an **explicit blank value** — in most runtimes the blank is a real value, so the default does not apply and the component receives the blank. 3. The input is passed a **value that is present but meaningless** for the domain — an empty list, a zero, an empty string. Nothing in the framework treats these as absent; only the component's own logic can. This is the source of a whole family of bugs where a value computed as blank by a caller erases a component's default. Decide explicitly, in the component, whether a blank should be coalesced to the default, and state the decision in the input's documentation. ## Traps around defaults - **A shared mutable default.** If a default is a single object or array created once with the declaration rather than per instance, every instance that omits the input gets the *same* object. One instance mutating it changes what every other instance sees. Produce the value per instance instead. - **A default that depends on another input.** A derived fallback is not a default; it is a computed value the component should derive on every update, or it goes stale when the other input changes. - **Changing a default is a breaking change.** Callers depending on the old fallback all change behaviour at once, and nothing in a build will point at them. - **A default that hides a missing required value.** A fallback chosen to stop a crash turns a loud contract violation into a screen that quietly shows the wrong thing. ## Validation is about the entry, not the lifetime Both kinds of check look at a value as it arrives. Neither watches what happens to it afterwards, so neither catches a child mutating the object it was handed, and neither catches a caller mutating the object *after* handing it down. Entry checks bound the contract; the read-only convention protects it afterwards. ## How to choose Make an input required when there is no honest rendering without it, and let the violation be loud. Default it when a single value is genuinely the common case and the component owns that opinion. Keep it optional with an explicitly handled absent case when the two branches really differ visually or behaviourally — because collapsing them into a default is how a component ends up rendering something plausible instead of reporting that a caller is wrong.

  • Why can a green build-time check still let a required input arrive missing at runtime?
    Because it only constrains callers compiled against the declaration. Markup assembled dynamically, values widened to an opaque shape on the way in, and data decoded from a network payload or persisted storage are all invisible to it. Components at the edge of the system need a runtime check on the value that actually arrived.
  • What goes wrong when a component's default input value is a single object created alongside the declaration?
    Every instance that omits the input receives the same object. If any instance mutates it, the change is visible to all of them and outlives their lifetimes, which reads as impossible cross-talk between unrelated parts of the screen. Produce a fresh value per instance instead.
  • Is changing an input's default value a safe, non-breaking change?
    No. Every caller that omitted the input silently changes behaviour, and no build step will point at them because they all still satisfy the contract. Treat a default as published API: change it deliberately, with the same care as renaming an input.

saying these in an interview costs you the question

  • Says a build-time type check makes a runtime validator unnecessary
  • Treats an explicitly passed blank value as an absent input
  • Declares an input both required and defaulted
  • Assumes validation keeps checking the value after it arrives
  • Calls a default change harmless because no caller has to be edited
  • Uses one shared object as the default for every instance