Temporal Coupling
Temporal Coupling requires methods to be called in a hidden order, silently breaking state when violated. Eliminate the half-built window with constructors, builders, and stateful types so correct sequencing is enforced by the type system.
Temporal Coupling exists when two or more operations must be invoked in a particular order for correct behavior, but nothing in the API enforces or even reveals that order. A classic shape is obj.init(); obj.configure(); obj.start(); — call start() before init() and the object fails or corrupts its state, yet the compiler is silent.
Why It Happens
Objects are often built with setters and lifecycle methods that gradually assemble state. Each method does part of the setup, and the author mentally knows the sequence. The hidden ordering becomes 'tribal knowledge' captured at best in a comment. Mutable, multi-step initialization and 'open-construct-then-populate' styles naturally create these sequencing requirements.
Why It Hurts
The required order is invisible at the type level, so callers — especially new ones — get it wrong, producing bugs that depend on call sequence and are hard to reproduce. An object can exist in a half-initialized, invalid state that other code may touch. Refactoring is risky because rearranging calls can silently break the contract. The design fails to make illegal usage impossible, relying instead on discipline.
Warning Signs
- APIs documented as 'call X before Y.'
- Objects usable only after a separate
init()/open()step. - Errors caused by uninitialized or partially configured state.
- Bugs that appear only when method calls are reordered.
Better Alternatives
Make correct ordering the only possible ordering. Use the Builder Pattern to gather all required inputs and produce a fully valid object in one step, so there is no partially built state. Enforce invariants in the constructor, requiring all mandatory data up front so the object is valid the moment it exists. A fluent interface can encode legal sequences in the type returned at each step (a stage builder), so the compiler rejects out-of-order calls. Prefer immutability so there is no mutable lifecycle to misorder.
How to Refactor Out of It
Identify the implicit sequence and the state each step establishes. Collapse multi-step setup into a constructor or builder that demands all required inputs, eliminating the half-built window. For genuine lifecycles, model states as distinct types so each type only exposes the operations valid in that state (a connection that returns an OpenConnection from connect()). Remove or guard methods that assume prior calls. Add tests that attempt illegal orderings and confirm they are now impossible or clearly rejected.