Implera is currently offline. The blog stays up.
Back to insights

Insights

What Is Deterministic Code?

Call a function with the same arguments twice. If you get the same answer both times, with no other effect on the world, that function is deterministic. If you might get a different answer, it is not.

That is the whole idea. The reason it is worth a thousand words is that almost every flaky test, every "works on my machine", and every Heisenbug you have ever chased traces back to a piece of code that quietly stopped being deterministic.

This is not the same question as whether your analysis is deterministic. That is about the tools that read your code. This is about the code itself.

A precise definition

A function is deterministic when its output depends only on its inputs, and it produces no observable effect beyond returning that output.

Two clauses, both load-bearing:

  1. Output depends only on input. Same arguments in, same value out, every time, forever. No reading of a clock, a random source, a global variable or a file that might change.
  2. No hidden side effects. It does not mutate shared state, write to disk, send a request or log in a way that other code can observe and branch on.

A function that satisfies both is what functional programmers call pure. Purity is the strongest form of determinism. You can memoise a pure function, run it in parallel, replay it in a test and reason about it in isolation, all because nothing it does depends on when or where it runs.

// Deterministic
function vat(amount: number): number {
	return Math.round(amount * 1.2 * 100) / 100;
}

// Not deterministic: reads a clock the caller did not pass in
function isExpired(token: Token): boolean {
	return token.expiresAt < Date.now();
}

vat(100) is 120 today, tomorrow and on a colleague's laptop. isExpired returns a different answer depending on the second you call it. Neither is wrong, but only one is safe to test with a hard-coded expectation.

The four classic sources of nondeterminism

When a function misbehaves only sometimes, the cause is almost always one of these.

Time

Anything that reads the current time is non-deterministic by construction. Date.now(), new Date(), performance.now(), process.hrtime(). A test that asserts a timestamp, or that an item created "now" sorts before one created a millisecond later, will eventually fail on a slow CI runner.

Randomness

Math.random(), crypto.randomUUID(), UUID generation, shuffles, jitter, sampling. Each call invents a value that did not come from the inputs. A function that picks a random discount and a test that asserts the discount is 10% are at war with each other.

Shared mutable state

Module-level variables, singletons, caches, the database, the filesystem. If two calls to the same function can see each other's writes, order starts to matter, and order is rarely guaranteed. This is the source that hides best, because the function looks pure until a second caller arrives.

Concurrency and iteration order

Promise races, Promise.all resolution order, worker pools, parallel reduces. The classic JavaScript trap is relying on object key order or the order in which async tasks settle. It works on every run until the day the scheduler interleaves things differently.

Source Example API Telltale symptom
Time Date.now(), new Date() Test fails only near midnight or on slow CI
Randomness Math.random(), randomUUID() Snapshot changes every run
Shared state singletons, caches, DB Test passes alone, fails in the suite
Concurrency Promise.all, worker pools Fails one run in fifty, never reproducibly

That last symptom is the giveaway. Deterministic code fails the same way every time. Non-deterministic code fails one run in fifty, which is exactly the bug class that survives code review and lands in production.

Why determinism is a code quality signal, not just a testing trick

The connection most teams miss: the same property that makes code testable makes it maintainable.

A deterministic function can be understood by reading it. Its behaviour is a function of its arguments and nothing else, so you do not have to hold the state of the database, the clock and three singletons in your head to predict what it does. A non-deterministic function forces you to simulate the whole system to reason about one call.

This is why a flaky test suite is a quality problem disguised as a tooling problem. When a test is flaky, the usual reflex is to retry it, raise a timeout or quarantine it. All three hide the signal. The honest reading is that the code under test reads from a source that the test cannot control, and the fix lives in the code, not the test.

How to make non-deterministic code testable

You cannot remove time, randomness, state and concurrency from a real program. The trick is to push them to the edges and inject them, so the core stays pure.

The single most useful move is to stop calling the non-deterministic source inside your logic and start receiving its result as an argument.

// Hard to test: reaches out to the clock itself
function isExpired(token: Token): boolean {
	return token.expiresAt < Date.now();
}

// Easy to test: the caller supplies "now"
function isExpired(token: Token, now: number): boolean {
	return token.expiresAt < now;
}

The second version is deterministic. The non-determinism now lives in one place at the edge of the system, where the real Date.now() is read once and passed down. Your tests pass any now they like and assert on a fixed answer. This is the same idea as dependency injection, just applied to time and randomness instead of services.

The same pattern handles the rest:

  • Randomness: take a seeded generator or the generated value as a parameter. Seed it in tests.
  • Shared state: pass the store in rather than reaching for a module-level singleton. Hand the test a fresh one per case.
  • Concurrency: make ordering explicit. If results must be ordered, sort them; do not rely on settle order.

You are not eliminating nondeterminism. You are corralling it into a thin shell so the bulk of your code becomes pure, fast to test and cheap to reason about.

Determinism is not the same as correctness

A deterministic function can be reliably, repeatably wrong. add(a, b) { return a - b } is perfectly deterministic and perfectly broken. Determinism buys you reproducibility, not correctness: it guarantees that if the function is wrong, it is wrong the same way every time, which is precisely what lets a test catch it.

That is the payoff. Reproducible failure is debuggable failure. The hours teams lose are not spent on bugs that always happen; they are spent on bugs that happen one time in fifty, and those are almost always a determinism problem wearing a different hat.

If you want a read on where nondeterminism and other quality signals sit across your own repo, you can connect it to Implera and get a baseline across seven domains in a couple of minutes.

FAQ

Common questions

© 2026 Implera