🏁

RaceForge

Two tasks, one shared counter — what could go wrong?

▶ Open the concurrency console →

What a data race actually is

When two tasks run at the same time and both touch the same variable, a single innocent-looking line — counter += 1 — is really three steps: read the counter into a private copy, add one to the copy, write the copy back. If the two tasks' steps interleave badly, they can both read the same old value and both write the same new one — and one of the additions simply disappears. That is a data race: a program whose answer depends on timing you cannot control.

  1. Predict. Given two tasks adding to a shared counter, what is the smallest value it could reach?
  2. Reveal. The engine enumerates every possible interleaving and shows the exact one that loses an update.
  3. Fix it. Move the counter inside an actor — each add becomes atomic — and watch the whole range of outcomes collapse to one.

Everything runs on your device: RaceForge literally enumerates the interleavings, so nothing here is hand-waved. This is exactly the class of bug Swift 6's strict concurrency checking catches at compile time.

The pieces of a race

  • Shared state — one variable two tasks can both read and write. The moment it is shared and mutable, a race is possible.
  • The non-atomic step+= 1 is read-modify-write, three operations that can be split apart by another task landing in the middle.
  • The interleaving — the specific order the two tasks' steps happen to run in. There are many; some are fine, some lose updates.
  • The actor — an isolation boundary that lets only one task touch the state at a time, making each operation atomic. The race can no longer be expressed.

A text-forward console (ages 15–18) — no cast art here by design.