Race detection for Node.js · Jest & Vitest

Your Node.js tests run the same way every single time.

That's the problem.

We measured it: 1,000 native runs of a Node.js test produce one distinct interleaving. Repetition doesn't sample the schedule space — it re-samples the same point. Every race condition in your suite is invisible to the only tool you have.

Tonda takes control of async completion order in a Jest or Vitest suite and explores thousands of orderings. Median time to find a race condition: 4 seconds.

See the evidence ↘

MITlocal-onlyzero telemetryno config

Schedule coverage, measured
races found
in 1,000 runs
unique schedules
explored
running your tests 0 0.1%
tonda 17 98.6%

510 batches · 17 planted races · measured 2026-07-31 · full results ↘

Quickstart

Scan a Jest or Vitest suite in one command.

No install. Point it at a repository with a Jest or Vitest suite and it runs. There is no config file, no test to rewrite, and nothing to sign up for.

npx tonda scan
$ npx tonda scan

  scanning 47 test files · 100 schedules each
  ████████████████████ 14.2s

  ✗ 3 races found

  checkout.test.ts:42   two DB reads settle in reverse order    seed 8a5ab266
  cart.test.ts:118      unawaited write lands after assertion   seed 3f1c9d02
  session.test.ts:71    timer/immediate ordering                seed b7e40a15

  reproduce:  npx tonda run --seed 8a5ab266
  analysed 31 of 47 files · 16 skipped (npx tonda explain)

That last line is not an apology. Sixteen files used something we cannot control — a native driver, a real socket — and rather than guess at an answer, Tonda tells you which ones and why.

01 / Why repetition fails

Why running a flaky test again runs it the same.

Node.js is single-threaded. On a fixed machine, with a fixed workload, the event loop delivers the same callbacks in the same order every time. That is normally a feature — it is why Node.js is predictable — and it is exactly what makes a race condition invisible.

When you run a flaky test a thousand times, you are not sampling a thousand points in the space of possible orderings. You are sampling one point a thousand times. The scheduler is not a source of variety; it is a function, and you keep feeding it the same input.

What actually changes the ordering is changing the machine: a different core count, a loaded CPU, a slower disk, a different worker count. Which is to say —

This is why flaky tests only fail in CI. It was never about running them more.

1,000 native runs
tonda, 100 schedules

02 / Evidence

The measurement, before the argument.

search-results.csv · 510 batches · 17 planted races · 2026-07-31
strategy batches schedules / batch unique schedules races found median time to first race
native repetition 510 1,000 0.1% 0 / 17
tonda 510 100 98.6% 17 / 17 4.0s

Method: seventeen race conditions were planted across a corpus of Node.js test files, each with a known-bad interleaving. Every batch ran both strategies against the same file on the same machine — native repetition at 1,000 runs per batch, Tonda at 100 schedules per batch — and we recorded how many distinct interleavings each reached and whether the planted race surfaced. Raw run folders and the CSV are in the repository.

.NETCoyote — systematic concurrency testing, running in production across Azure.
RustShuttle and Loom — schedule exploration in the standard toolbox.
Javajcstress — concurrency stress harness from the JDK team.
Go-race ships in the toolchain. One flag.
Node.jsFive research papers. Nothing you can install.

This is standard practice in every other ecosystem. Node.js — the platform with more asynchronous code than any of them — is the one that never got the tool.

Systematic testing gave coverage “much higher than even with days of stress testing.” — Microsoft, on running Coyote against Azure services

That is an organisation with more stress-testing budget than anyone reading this page, reaching the same conclusion independently: past a point, more runs stop buying you coverage. Choosing the orderings does.

03 / How it works

How Tonda finds a race condition, in four steps.

None of them touch your tests. Your existing Jest or Vitest assertions do the detecting; Tonda only decides what order the async work completes in.

  1. 01
    Take control.

    Registered async completions — timers, filesystem, loopback network, the clock, randomness — are held before your code ever sees them.

  2. 02
    Vary the order.

    Each schedule releases those completions in a different order. Seeded, so the same seed always produces the same order.

  3. 03
    Watch for failure.

    A schedule that breaks your test is a race condition. Your assertions are the oracle — there is nothing new to write.

  4. 04
    Hand back a seed.

    Eight bytes. Reproduces the failure 100 times out of 100, on your machine and on the reviewer's, forever.

Standard Node, no V8 fork. Tonda runs on the Node you already have, through documented interception points. Every prior attempt at this that required a patched runtime died of version rot within two releases; that is a mistake worth not repeating.

Labelled illustration / controlled event delivery 17c4e2aa
schedule 40
checkout.test.ts — “applies discount once” ✓ passed
Current illustrated schedule for checkout.test.ts
OperationSourceDelivery position
db.read(user)checkout.ts:4227%
db.read(cart)checkout.ts:4748%
setTimeout(0)checkout.ts:3116%
process.nextTickcart.ts:188%
promise resolvediscounts.ts:6468%
schedule 40 / passing run 1 · baseline

04 / What it can't do

The boundary, stated up front.

Everyone in this category overclaims. Here is exactly where Tonda stops, so you can decide whether the part that works is worth your fourteen seconds.

  • Microtask-level races are observed, never reordered. We control when completions are released into the loop, not the order V8 drains its microtask queue. That means a large class of races, not all of them.
  • Native database drivers, TLS, and external network calls cause a skip, not a guess. If a completion resolves outside JavaScript, we cannot hold it — so we say so, by name, and exclude the file.
  • No findings never means no races. It means none were found within this budget of schedules. A clean scan is evidence, not proof.
  • Coverage is reported on every run. How many files were analysed, how many were skipped, and how much of the schedule space was reached — on every scan, not just the good ones.

A tool that cannot tell you what it missed is a tool you cannot act on.

05 / FAQ

The questions worth answering first.

How is this different from running my flaky tests more?

Repetition explores about 0.1% of the unique schedules for a given Node.js test, because the event loop is deterministic on a fixed machine — a thousand runs is one ordering, sampled a thousand times. Tonda reached 98.6% on the same corpus and found all seventeen planted race conditions, where repetition found none.

We already use Trunk or Datadog for flaky tests.

Those tell you which test flakes and quarantine it for you. Tonda tells you why, with a reproducer. The two fit together: their list of flaky tests is the ideal input for our scan.

What's a seed?

Eight bytes that select a delivery order. No payloads, no arguments, no PII — just the scheduling choices. Paste it into an issue, or commit it as a regression test that fails until the race is actually fixed.

Will this find every race condition?

No. Microtask-level reordering is out of reach, native drivers cause a skip rather than a guess, and a clean scan only means nothing surfaced within the schedule budget. The full boundary is in section 04.

Does anything leave my machine?

No. No telemetry, no account, no cloud, no phone-home. The scan runs locally against the tests already on your disk, and the only artifact is a seed. Ever.

Is this a new idea?

No, and that is the point. Systematic schedule exploration is roughly ten years old and peer-reviewed — Node.fz in 2017, NodeRacer in 2020, further work at ECOOP in 2025. None of it was ever shipped as something you could install. That is the part we are doing.

Does it work with Jest and Vitest?

Both, unmodified, on Node.js 20 or later. No migration, no config file, no changes to the tests themselves — npx tonda scan reads the suite you already have.

Is it open source?

MIT. Free, and free in the way that does not have a pricing page attached to it later.

Find out in fourteen seconds.

GitHub ↗ Read the evidence