skip to content

Why is validating invariants inside a constructor or factory (an "always-valid" object) considered a stronger application of fail fast than validating the same rules later, and what practical problems does it introduce?

level: middleimportance: must knowfreq 55%

answer

  1. invalid object can never exist
  2. one birth point, one check
  3. parse into a type, don't re-validate
  4. no side effects before validation
  5. deserialisation bypasses the constructor

basics

~20 s

If an object checks its rules when it is created, an invalid one can never exist, so no later code has to re-check or handle it. Validating later means broken objects float around until something notices.

solid answer

~50 s

Constructor or factory validation enforces the invariant at the single point where the object comes into existence, so every reference to that type is guaranteed valid. That removes defensive re-checking from every consumer, makes the type itself documentation of the rules, and points the stack trace at whoever built the bad object rather than at whoever later tripped over it. The alternative - a default constructor plus setters, validated by a separate validate() call - allows a window in which the object is legal to the compiler but meaningless to the domain, and it is easy to forget the call. Practical costs: exceptions thrown from constructors can leave partially acquired resources, so allocation should be side-effect free; frameworks that build objects by reflection or deserialisation can bypass the constructor entirely; and multi-field user input needs all errors collected at once, so the boundary usually parses into a validated type via a factory that returns a result rather than throwing per field.

code

pseudocode · 15 lines
pseudocode
// invalid state is reachable: temporal coupling + forgettable validate()
range = new DateRange()
range.start = mar1; range.end = feb1     // legal to the compiler, nonsense
range.validate()                        // easy to forget

// always-valid: the type itself is the proof
class DateRange {
  constructor(start, end) {
    if (start == null || end == null) throw ArgumentError("start/end required")
    if (end < start) throw ArgumentError("end " + end + " precedes start " + start)
    this.start = start; this.end = end   // immutable afterwards
  }
}
// boundary variant that collects errors instead of throwing per field
DateRange.parse(input) -> Result<DateRange, List<Error>>

go deeper

for a junior

Say that checking the rules in the constructor means a broken object can never be created, so later code does not need to re-check. One example such as a date range with end before start is enough.

for a middle

Contrast with default-constructor-plus-setters and with a separate validator, explain temporal coupling, and mention immutability plus builders that validate in build().

for a senior

Add which rules belong in the constructor versus a service (self-contained and cheap versus needing external state), the deserialisation bypass, side-effect-free construction, and Result-returning factories for boundary error aggregation.

for a principal

Frame it as pushing correctness into the type system so guarantees are structural rather than procedural, discuss the DTO-versus-domain-model boundary, framework constraints, and the migration cost of introducing always-valid types into a legacy anaemic model.

## The principle An **invariant** is a rule that must hold for the whole lifetime of an object: "an order always has at least one line", "a date range's end is not before its start", "an email address contains exactly one @". Fail fast says the moment such a rule is violated, stop. The earliest possible moment is **creation**. An **always-valid object** (sometimes called an always-valid domain model) validates its invariants in its constructor or in a factory function, and exposes no way to reach an invalid state afterwards - typically by being **immutable**, or by validating inside every mutating method. ## Why this beats validating later 1. **One enforcement point instead of N.** With constructor validation there is exactly one place an object is born, so exactly one place to check. With late validation, every consumer must either re-check or trust an unenforced promise. 2. **The stack trace names the culprit.** The exception fires in the frame of the code that supplied the bad values. Late validation fires in a frame that merely *read* them. 3. **The type becomes a proof.** A parameter of type `DateRange` is proof the range is ordered; nothing downstream needs an `if (end < start)`. This is the essence of the "parse, don't validate" idea: convert untrusted data into a type that can only hold valid data, and let the type carry the guarantee inward. 4. **No temporal coupling.** The pattern `obj = new Thing(); obj.setA(); obj.setB(); obj.validate();` requires callers to remember an ordering and a final call. Forget it and you have a live, invalid object. This is the classic anaemic-object failure mode. ## Contrast with the alternatives - **Default constructor + setters:** maximum window of invalidity, no enforcement, easy to misuse. Common because frameworks historically demanded it. - **Separate validator object (`OrderValidator.validate(order)`):** better than nothing, and genuinely useful for *cross-entity* or *user-facing* rules, but it does not prevent the invalid object from existing; it only reports on it. It also drifts out of sync with the class. - **Builder that validates in `build()`:** a good compromise. The mutable builder is allowed to be incomplete, but the built object is always valid - the invariant still lives at exactly one point of creation. ## The real complications **1. Throwing from a constructor.** In languages with deterministic destruction or manual resource management, an exception mid-constructor can leak resources already acquired. The remedy is to validate arguments *before* acquiring anything, and to keep constructors free of side effects (no I/O, no registering `this` in a global registry - a half-built object escaping is a classic bug). **2. Frameworks bypass constructors.** ORMs, JSON deserialisers and mocking libraries often instantiate objects reflectively or through a no-arg constructor and set fields directly, silently sidestepping validation. Mitigations: use constructor-binding features where available, validate on load, or keep the persistence representation separate from the domain type and validate when mapping between them. **3. Exceptions are a poor UX for form input.** Throwing on the first bad field gives the user one error at a time. At the input boundary, prefer a factory that *returns* a result - a value-or-errors object - so all field errors are collected and reported together. Note this is still fail fast: nothing invalid is constructed; only the reporting style differs. Failing fast is about not *proceeding* with bad state, not about maximising the number of exceptions thrown. **4. Which rules belong in the constructor?** Only rules that are *always* true of the object in isolation and are cheap to check. Rules needing external state ("this email is not already registered") require I/O, can change over time, and belong in a service or application layer - putting them in a constructor makes the type untestable and slow. **5. Mutation.** If the object is mutable, each mutator must preserve the invariant, and ideally do so atomically: validate the proposed new state fully before assigning any field, so a rejected change leaves the object exactly as it was. ## Summary trade-off Constructor validation trades a little construction-site rigidity and some framework friction for the elimination of an entire class of "how did this object get into that state?" bugs. For core domain types the trade is almost always worth it; for wire/DTO types at the edge, keep them dumb and validate during the mapping step into the domain type.

  • Your ORM instantiates entities reflectively and skips the constructor. How do you keep the fail-fast guarantee?
    Either use the framework's constructor/immutable-binding support, or separate the persistence row model from the domain type and run validation in the mapping step, or validate on load with a post-load hook. The key point is naming the bypass explicitly rather than assuming the constructor always runs.
  • Should a constructor check that a username is unique?
    No. Uniqueness needs external state, requires I/O, and can change after construction, so it is not an invariant of the object in isolation. Enforce it in the application service plus a database unique constraint; keep the constructor to cheap, self-contained rules.
  • Is returning a Result/Either from a factory instead of throwing still fail fast?
    Yes. Fail fast is about refusing to proceed with invalid state, not about the error-signalling mechanism. A Result makes the failure part of the type signature and lets a boundary aggregate several errors, while still guaranteeing no invalid object is ever constructed.

A passport control desk at the border versus asking every shopkeeper inside the country to check papers. Check once at entry and everyone inside can be trusted; check nowhere at entry and every interaction has to be defensive - and someone will forget.

saying these in an interview costs you the question

  • Believing that adding a separate validate() method makes objects always-valid - it reports on invalid objects that already exist.
  • Putting I/O or database lookups inside constructors so that construction becomes slow, non-deterministic and untestable.
  • Assuming the constructor always runs, when deserialisers, ORMs and mocking libraries routinely bypass it.
  • Throwing on the first invalid form field and calling it good UX, instead of aggregating field errors at the input boundary.
  • Letting a mutable object assign some fields before discovering the new state is invalid, leaving it half-updated.

context