deterministic and predictable programming

rule

  • same explicit inputs, dependency results, and starting state -> same result and observable transition. a reader traces why a value exists, why a branch ran, and what happened — without guessing at hidden time, ambient config, randomness, mutable globals, or inferred strings.
  • deterministic = the mechanism repeats. predictable = a person or agent can anticipate it from named inputs, boundaries, and state transitions. both are required: repeatable hidden behavior is hard to repair; explainable behavior that changes incidentally is still unsafe.
  • never derive meaning by rewriting strings. identifiers, paths, URLs, commands, casing, domain values are structured data with one canonical representation: parse or model at the boundary; explicit mappings for closed transformations.
  • determinism does not ban time, random IDs, network calls, or side effects — it isolates them at named boundaries, records results, and passes them into the domain decision explicitly.

why

a replayable decision is diagnosable: same inputs and state -> same result narrows a failure to an input, a dependency result, or a transition — not to hidden time, ambient config, or a forgotten string rewrite. string-derived identifiers multiply the risk: casing and delimiters silently change identity and collide. invariant: every value that changes behavior has one canonical representation and enters the decision through a named boundary.

required approach

  • parse URLs, paths, JSON, YAML, SQL, command arguments, Markdown with their purpose-built parsers or builders.
  • validate external values with schemas; represent closed domains as named enum-like objects.
  • store authored slugs, display labels, business keys exactly as authored.
  • typed mapping table when one finite domain maps to another.
  • pass current time, generated identifier, feature decision, external response as explicit values when they affect a domain decision.
  • sort externally sourced collections with an explicit stable comparison before deriving a visible order, batch, digest, or persisted projection.
  • make writes idempotent via an explicit operation key and durable state; never rely on a retry happening only once.

keep uncertainty at the boundary

source of variationdeterministic boundarydomain input
current timeclock adapter records one instantoccurred_at
generated IDID provider produces it onceoperation_id
random selectionchooser records its chosen memberselected_variant
remote requestadapter validates and records the responsetyped response or typed failure
configuration or flagconfiguration owner resolves one named decisionenum-like decision value
unordered collectionboundary applies an explicit sortordered values

the domain service receives values, not ambient APIs. an adapter may read a clock or call a provider; a mapper may convert a validated response; the decision itself must not reach outward to rediscover the same fact.

make behavior predictable to a reader

one capability, one visible decision path: name the input, validate it, make the state transition, expose the resulting state. never hide a business decision in a callback, getter, presentation component, generic utility, or implicit framework default.

reader questioncode must make visible
what can change this outcome?validated input, named dependency result, and durable starting state
which rule selected this branch?named enum member, guard, or exhaustive mapper
what happens next?explicit state transition or typed failure
can a retry change the answer?durable operation identity and retry state
where can this be diagnosed?correlation data, structured event, and owning boundary

select the tool by the input's real structure

input or changecorrect ownerwhy
URL, path, command argument, or query stringplatform parser or typed builderdelimiters and escaping already have a grammar.
JSON, YAML, MDX, SQL, or source syntaxpurpose-built parser/schema/ASTstring patterns cannot prove structural validity.
closed external value to internal valueexhaustive mapping over named membersunmapped and future values fail visibly.
authored slug, label, or business keystored exact authored valuethe author, not a casing function, owns identity.
final human display textnarrow formatting at the final rendering edgepresentation does not become a new identifier.

decision process

tempting shortcutfailureselected direction
replace a route/path segmentdelimiter collisions and encoding rules turn a local patch into a different resourceparse and construct from named path parts.
lower-case a label into a keycopy editing silently changes identity and creates collisionsrequest/store an explicit authored key.
split a provider value and infer its fieldsan undocumented format change becomes silent data corruptionvalidate a typed provider contract, then map it.
regex-rewrite structured sourcea near match can corrupt syntax while appearing successfuluse a parser/AST or change the owning structured input.
silently normalize malformed inputfailure loses its repair boundaryreject with a typed diagnostic and correction action.

boundary workflow

  1. identify whether the input has a grammar, a closed value set, or authored human meaning.
  2. parse/validate once at the outer boundary.
  3. represent the accepted result with named fields or members.
  4. map between stable shapes only through an explicit directional mapper.
  5. construct output through a builder or exact stored value; never reverse engineer it from display copy.

effects and retries

separate the deterministic decision from its effect: decide the intended state transition from explicit values -> persist the operation identity and the transition -> perform the external effect through its adapter and record the outcome. a retry reads the durable operation state and either continues the same operation or stops; it never makes a second ambiguous decision.

concerndeterministic owner
whether an operation is validpure domain decision from validated input and state
whether it already happeneddurable operation record keyed by an explicit ID
what an external provider returnedvalidated adapter result with correlation data
how retry proceedsnamed state transition and idempotency key
what a user seesprojection of the durable state, never a guessed success

good and bad

bad

function createInvoice(customerId: string) {
return invoiceRepository.insert({
id: crypto.randomUUID(),
customerId,
issuedAt: new Date(),
position: Math.floor(Math.random() * 10),
});
}

the result cannot be replayed or explained from the input. a retry creates a different invoice identity, time, and position — a transient network failure after a successful insert delivers an undetectable duplicate. a test cannot assert the decision without mocking crypto, the clock, and Math.random together, so behavior is verified by luck. a disputed queue position cannot be reconstructed because the choice was never a recorded input.

good

interface DecideInvoiceProps {
invoiceId: string;
customerId: string;
issuedAt: Date;
position: number;
}
function decideInvoice(props: DecideInvoiceProps) {
return {
id: props.invoiceId,
customerId: props.customerId,
issuedAt: props.issuedAt,
position: props.position,
};
}

the adapter creates the ID, reads the clock, and chooses the position once. the decision receives those recorded values and always returns the same invoice for the same input.

permitted narrow exception

small final-display formatting is allowed when it cannot become an identifier, route, key, query, or structured value and its output is never read back as input. keep it next to rendering, name it as presentation. once the result crosses a boundary, it is no longer a formatting exception.

do not

  • call replace, split, join, slicing, regex rewriting, or casing methods to derive an identifier, route, storage key, schema name, import path, or domain value.
  • convert labels into slugs, derive filenames from display copy, or rely on case transformation as a naming convention.
  • repair malformed structured input with a best-effort rewrite; reject it with an actionable diagnostic.
  • call Date.now, random APIs, environment reads, or network clients inside a domain decision when their result changes behavior.
  • depend on incidental collection order, a mutable module singleton, or an implicit retry count for correctness.
  • recompute an operation decision on retry after its first result was already recorded.

verification

  1. review every introduced transformation: parser, builder, schema, or explicit exhaustive mapping.
  2. search the focused change for string rewrite APIs; justify any final-display formatting explicitly.
  3. identify every read of time, random state, environment, network, or shared mutable state; verify it enters through one named boundary.
  4. replay the same input, dependency results, and starting durable state; expect the same decision and idempotent effect behavior.

search pages

go to any page