skip to content

Test Isolation & Independence

Keeping tests independent: no shared mutable state, fresh fixtures, cleanup that does not leak, and no assumptions about execution order. Asked because independence is what makes a suite safe to run in parallel or as a subset.

on this pageshow

questions

3

What makes a test independent of the other tests in its suite?

level: juniorimportance: must knowfreq 78%

answer

  1. Would it still pass on its own?
  2. Three properties, not one
  3. Arrange it yourself, share nothing writable
  4. Leave the environment as you found it
  5. Buys shuffling, subsets and parallel runs

basics

~20 s

An independent test arranges everything it needs itself, writes to no state another test can observe, and leaves the environment as it found it. It gives the same verdict alone, in any position in the run, or beside tests on another worker.

solid answer

~40 s

Independence means a test's result never depends on which other tests ran, or in what order. Three properties produce it. First, **self-contained arrangement**: the test creates its own inputs rather than assuming a record, session or counter left by an earlier test. Second, **no observable shared mutable state**: it does not write to a module-level fixture, a cache, a static counter, a fixed identifier or a global setting that another test reads. Third, **cleanup**, so the next run starts where this one did. The payoff is practical rather than aesthetic: an independent suite can be shuffled, run as a subset around the module you just changed, and split across parallel workers, and when one test fails the failure names one behaviour instead of leaving you to ask whether something upstream wrecked the state.

code

pseudocode · 21 lines
pseudocode
# coupled: the second test only passes if the first ran, and ran first
shared_booking = null

test "creates a booking":
    shared_booking = service.create(guest: "ok", nights: 2)
    assert shared_booking.status == "CONFIRMED"

test "cancels a booking":
    result = service.cancel(shared_booking.id)   # reads state test 1 left
    assert result.status == "CANCELLED"


# independent: each test arranges what it asserts on
test "creates a booking":
    booking = service.create(guest: "ok", nights: 2)
    assert booking.status == "CONFIRMED"

test "cancels a booking":
    booking = service.create(guest: "ok", nights: 2)   # own arrangement
    result = service.cancel(booking.id)
    assert result.status == "CANCELLED"

go deeper

for a junior

Be ready to state the property in one sentence and give one concrete example of breaking it, such as a second test reading a record the first one created. Know that tests are not guaranteed to run in the order they appear.

for a middle

Explain the three mechanics that produce independence — own arrangement, no writable shared state, cleanup — and name where shared state hides, especially setup that runs once per file rather than once per test.

for a senior

Show you connect independence to operations: shuffled ordering in the pipeline, running a subset around a change, and splitting a long run across workers. Be able to say why a failing coupled test costs more to diagnose than to fix.

for a principal

Own the tradeoff between per-test arrangement cost and suite runtime, and the rule you would set for shared immutable fixtures. Decide how the property is enforced so it does not decay, since coupling is always the locally cheaper choice for whoever is in a hurry.

**Independence** is a property of a single test measured against the rest of its suite: the test's result is the same whether it runs alone, first, last, or beside other tests on another worker. A suite in which every test has that property can be re-ordered, sliced into subsets, and spread across parallel workers without changing a single verdict. A suite in which some tests lack it has a hidden execution graph that nobody wrote down and nobody maintains. ### The three properties that add up to independence **1. Self-contained arrangement.** Everything the test needs to reach its starting state is created by the test itself, or by a setup hook that runs for that test. It never assumes a record, a file, a counter, a cached value or a logged-in session exists because an earlier test made one. The practical test: if you deleted every other test in the file, this one would still have the inputs it asserts on. **2. No observable shared mutable state.** The test writes only to state that no other test can read. Shared mutable state comes in more shapes than people expect: a module-level collection reused as a fixture, a singleton cache, a static counter, a fixed identifier such as a customer code that two tests both claim, a temporary directory with a hardcoded name, an altered runtime locale or time zone, a stubbed clock left frozen, a global configuration flag flipped in one test and never restored. Any of these turns two tests into one test with two entry points. **3. Cleanup that leaves the world as it was found.** The test undoes what it did, so a second run against the same environment starts from the same place as the first. This is why a suite can be order-independent and still not be *repeatable*: nothing depended on ordering within a run, but the run as a whole left residue that makes the next run behave differently. The specific mechanics for resetting a shared store are a discipline of their own; the property to understand here is simply that after the test, no other test's premises have changed. ### Why interviewers care Independence is not tidiness — it is what buys three concrete capabilities. *Any order.* The moment a suite can be shuffled, the runner is free to order tests for speed, and a newly added test cannot break an old one by being inserted above it. *Any subset.* Developers want to run the nine tests around the module they just changed. In a coupled suite those nine fail, not because the change is wrong but because the state they silently relied on was built by tests that were filtered out. Teams that hit this stop running subsets and start running everything, which is how a suite grows into an hours-long ritual nobody executes before pushing. *Parallel execution.* Splitting work across workers is the cheapest way to shorten a long run, and it is only safe when no two tests contend for the same mutable thing. There is a fourth, quieter benefit: **diagnosis**. When an independent test fails, the failure names one behaviour, and the fix is scoped to that behaviour. When a coupled test fails, the first question is always "did this test fail, or did something upstream fail and leave me the wreckage?" — and answering that question costs more than the fix. ### The tempting exception, and its one safe form Building a fresh fixture per test costs time, so teams share. The distinction that matters is **mutable versus immutable**. A fixture that is genuinely read-only for the whole run — a parsed reference table, a compiled schema definition, a set of static lookup codes nothing writes to — can be built once and shared without breaking independence, because no test can change what another test observes. The failure mode is a fixture that starts read-only and quietly acquires a writer six months later; from that commit onward the suite has an undocumented ordering constraint, and it usually surfaces as a case that fails only when the run is shuffled or split. ### The chained-test anti-pattern The most common breach is deliberate: a sequence such as *create the record*, then *update the record*, then *cancel the record*, written as three cases that must run in that order. It looks like three tests and reports as three tests, but it is one scenario with three checkpoints. If the first case fails, the other two fail for a reason that has nothing to do with what they were written to check, and the report claims three defects where there is one. If the scenario really is one journey worth testing end to end, express it as one test with intermediate assertions — one honest verdict for one scenario — rather than as a chain that lies about how many things are broken. ### How independence is enforced in practice Randomise or reverse execution order in the pipeline so a dependency is discovered by the build rather than by a developer at midnight; run any suspect test on its own; and treat a case that passes in the full run but fails alone as a defect in the test, not a curiosity to be worked around by pinning the order.

  • Is it ever acceptable for tests to share one fixture object?
    Yes, when it is genuinely immutable for the whole run: a parsed reference table, a compiled schema definition, a set of static lookup codes nothing writes to. No test can change what another observes, so independence holds and you save the build cost. The risk is drift: a read-only fixture that quietly acquires a writer later creates an undocumented ordering constraint. Make the shared object unmodifiable so the first attempted write fails loudly instead of silently coupling two tests.
  • A suite passes in any order but fails the second time it is run against the same environment. What is wrong?
    It is order-independent but not repeatable. Nothing depended on ordering within a run, yet the run left residue — records, files, a changed setting — so the second run no longer starts from the same premises. The usual culprit is a test that creates state and never removes it, or one that asserts a count from zero. Independence has to hold across runs as well as within one, which means cleanup, not just self-contained arrangement.
  • Where does shared mutable state usually hide, apart from the test bodies?
    In setup that runs once for the whole file rather than once per test, because it reads like ordinary arrangement code while actually being a fixture that outlives every test. After that: singletons and caches, static counters, a stubbed clock left frozen, an altered runtime locale or time zone, a global configuration flag flipped and never restored, a temporary directory with a hardcoded name, and fixed identifiers that two tests both claim.

Each test should be a self-service kitchen station: you bring your own ingredients, cook on your own surface, and wipe it down. Sharing one chopping board means whoever cooks after you inherits your mess.

saying these in an interview costs you the question

  • Thinks independence just means tests run fast
  • Assumes tests always execute in declared file order
  • Shares one mutable fixture object across the whole file
  • Fixes an ordering failure by pinning the order
  • Cleans up only in the success path
  • Treats a global stubbed clock as harmless shared state

context

open as a page

How do you prove that one test in a suite depends on another test running first?

level: middleimportance: should knowfreq 57%

basics

~20 s

Run the suspect test on its own: failing alone but passing in the full run means it consumes state another test leaves. Then reverse or shuffle the order to reproduce it, and bisect the preceding tests to find the pair.

open as a page

Why does a suite of order-coupled tests turn one real defect into dozens of failures, and what does that cost?

level: seniorimportance: should knowfreq 49%

basics

~20 s

Coupled tests inherit each other's state, so when one fails its dependents fail on arrangements that never happened. The red count then measures how far the breakage spread, not how many defects exist, and triage, re-verification and trust all pay for it.

open as a page