Would you make a fresh-instance Kotest isolation mode the default for a whole test suite, or keep the single-instance default and manage state spec by spec? How do you decide?
answer
- default cheap, opt in per spec
- fresh instance multiplies body + container execution
- duplicated container effects = red, not just slow
- coarsest mode that works (InstancePerRoot)
- randomised order in CI; no mode isolates singletons/DB
basics
~20 sDefault to the cheap single-instance mode and treat shared mutable state as a defect to remove; opt individual legacy specs into a fresh-instance mode. Consider a suite-wide fresh-instance default only when you cannot refactor and correctness matters more than the multiplied runtime.
solid answer
~60 sMy default position is: keep `SingleInstance` project-wide, forbid mutable spec-level state by convention, and let specific specs opt into a fresh-instance mode. Reasons: a fresh-instance default multiplies work — the spec body and every container block on the path re-execute per test — so heavy `beforeSpec` setup and any effects written inside containers are repeated, which is a runtime cost *and* a correctness change for tests that assumed a container ran once. It also papers over the real defect: tests that depend on each other's state. What would move me: a large legacy suite where the leaks are pervasive and refactoring is not fundable; a team that keeps reintroducing shared fixtures; or a domain where a missed leak produces false green rather than red. Then I'd pick the coarsest mode that works — `InstancePerRoot` on Kotest Kotest 6 rather than per-leaf — roll it out behind measurement, and fix the specs whose container side effects duplicate. Either way I'd add order randomisation in CI, because no mode isolates singletons, statics or the database.
go deeper
Say that the default keeps one instance and that per-test freshness costs re-execution, so you would fix shared state in the spec instead.
Compare the modes on cost and give the two concrete alternatives — local state or rebuilding in a hook — before touching configuration.
Argue from the failure modes: latent order dependence versus multiplied setup and duplicated container effects, and pick the coarsest mode that works.
State a default policy, the triggers that would change it, how you would measure and roll it out, and the guard rails for state no isolation mode touches.
## Framing the decision Isolation mode looks like a configuration knob but is really a stance on where test independence comes from. Two philosophies: - **Design for independence.** Tests own their state; nothing mutable lives at spec or container scope; the framework does not need to rescue you. Fast, explicit, and it keeps the failure mode local. - **Enforce independence mechanically.** Let the framework rebuild the spec for each test so leaks cannot happen at instance level. Slower, but it works on code you did not write and cannot afford to rewrite. A principal-level answer picks a default, names what would change it, and prices both. ## The cost of a fresh-instance default Under `InstancePerLeaf`, Kotest constructs the spec once per leaf test: the spec body re-executes (re-registering the tree) and every container on the path re-executes. `InstancePerTest` is stricter still. Consequences at suite scale: - **Runtime.** A container with twenty leaves runs its body twenty times. If it seeds data, builds a large fixture, or reads a file, multiply accordingly. On suites with thousands of tests this is the difference between a five-minute and a forty-minute pipeline, and pipeline latency has real engineering cost. - **Duplicated side effects.** Effects written inside container bodies repeat. Row-count assertions, invocation counts against shared collaborators, and idempotency-sensitive setup break. This is a *behaviour* change, not just a slowdown, and it is the thing that makes a naive suite-wide flip go red. - **Multiplied spec-level resources.** `beforeSpec` fires per instance, so a started server or container is started per instance — sometimes failing outright on a bound port. - **Concurrency interactions.** More instances plus any parallel execution enlarges the space of interleavings you have to reason about. ## The cost of the cheap default - **Latent order dependence.** A shared list or a stubbed singleton silently couples tests; failures surface later, in a different order, on a different machine, and get labelled flaky. - **False green.** Worse than a red: a test that only passes because a previous test left the right state behind is asserting nothing. - **Review burden.** Independence depends on discipline, so it decays unless it is reviewed for. ## How I decide 1. **Where does the state live?** If the leaks are companion objects, singletons or the database, isolation mode is irrelevant — it only rebuilds the spec object. Diagnose before configuring. 2. **Can the fixtures be refactored?** If mutable spec-level state appears in a handful of specs, fix those specs. Local state or a `beforeTest` rebuild is cheaper and clearer than a global mode change. 3. **What is the blast radius of a missed leak?** For tests guarding money movement, auth or data integrity I weight correctness far above runtime and will pay for isolation. 4. **What does the suite cost today?** Measure before and after on a representative subset. If the fresh-instance mode doubles a twelve-minute build, that is a real tax on every engineer, every day. 5. **Coarsest mode that solves it.** `InstancePerRoot` (Kotest 6.0+, the recommended fresh-instance mode in Kotest 6 where the finer modes are deprecated) isolates top-level branches at a bounded instance count; per-leaf isolation is only needed when siblings under one container pollute each other. ## The policy I usually land on - Project default: `SingleInstance`. - Convention: spec-level declarations are immutable; anything mutable is created inside the test or rebuilt in `beforeTest`/`beforeEach`; container bodies build values and do not perform effects. - Escape hatch: an opt-in fresh-instance mode on specific specs, ideally via a named base spec so the choice is visible and countable. - Safety net: run the suite periodically with randomised or reversed ordering so order dependence surfaces as a failure rather than as folklore; treat any test that passes in only one order as a defect, never a retry candidate. - Guard rails for the state no mode isolates: fresh external resources per spec or explicit reset hooks for databases, files and global registries. ## When I would flip the project default A large inherited suite, pervasive leaks, no budget to rewrite fixtures, and a team that keeps reintroducing shared state. Then I'd flip to the coarsest workable mode, expect a red patch caused by duplicated container effects, fix those specs, and record the runtime delta so the trade is explicit rather than folkloric. I would also treat it as temporary scaffolding with a plan to migrate specs back as they get cleaned up — because the mode is a mitigation, not a cure for tests that were never independent. ## What interviewers listen for A clear default with named triggers to change it; pricing both failure modes; knowing that the mode isolates only the spec object; and choosing the coarsest mode rather than the strictest available.
- You flip the project default to a fresh-instance mode and a batch of specs turns red. What is the most likely cause and how do you triage?Container bodies that perform side effects — seeding rows, starting services, configuring shared stubs — now execute once per test beneath them, so counts and idempotency assumptions break. Triage by instrumenting the container bodies to count executions, then move each effect into a per-test hook that also cleans up, or make the container purely declarative so re-execution is harmless.
- What do you put in place so the cheap default stays safe over time?Convention plus feedback. Convention: spec-level declarations immutable, mutable fixtures created per test, container bodies free of effects — enforced in code review. Feedback: run the suite periodically with randomised or reversed test ordering so order dependence fails loudly, and treat an order-sensitive test as a defect rather than something to retry.
- Is there a case where you would accept the runtime cost without hesitation?Yes — a small, high-stakes suite where a missed leak would produce false green on something like money movement, authorisation or data-integrity invariants. The absolute runtime is small, so the multiplier barely matters, and the value of knowing every test starts from a defined state is high. The calculus changes entirely on a multi-thousand-test suite where the multiplier lands on the whole team's build latency.
saying these in an interview costs you the question
- Treating the strictest isolation mode as free and obviously correct
- Believing a fresh-instance default also cleans companions, singletons or the database
- Flipping a project-wide default without measuring runtime or expecting duplicated effects
- Retrying order-dependent failures as flakes instead of fixing the shared state
- Having no policy at all and letting each spec decide silently