How we verify every timeline
A wrong thread assignment teaches a wrong mental model. So no timeline on this site is drawn from memory: each one is checked against a real program. Below is the latest run: every fact we check, its result on each JDK or compiler release, and what each program actually printed.
The latest run
2 Nov 2026, on GitHub Actions. 257 facts across 103 timelines, all passing on:
- JDK 21 21.0.13+9-LTS · Eclipse Adoptium · Linux amd64 · 2 CPUs · 110 passed
- JDK 25 25.0.5+8-LTS · Eclipse Adoptium · Linux amd64 · 2 CPUs · 110 passed
- Python 3.14 3.14.9 · python.org · Linux amd64 · 2 CPUs · 25 passed
- Python 3.15 3.15.1 · python.org · Linux amd64 · 2 CPUs · 25 passed
- Python 3.15t 3.15.2 free-threaded · python.org · Linux amd64 · 2 CPUs · 26 passed
- Node.js 22 v22.23.4 · Node.js · Linux amd64 · 2 CPUs · 13 passed
- Node.js 24 v24.22.1 · Node.js · Linux amd64 · 2 CPUs · 13 passed
- .NET 8 8.0.6 · Microsoft · Linux amd64 · 2 CPUs · 18 passed
- .NET 10 10.0.8 · Microsoft · Linux amd64 · 2 CPUs · 18 passed
- Kotlin 2.4 Kotlin 2.4.21, coroutines 1.11.0, JDK 21.0.13+9-LTS · Eclipse Adoptium · Linux amd64 · 2 CPUs · 12 passed
- Go 1.26 go1.26.10 · Go team · Linux amd64 · 2 CPUs · 48 passed
- Go 1.27 go1.27.3 · Go team · Linux amd64 · 2 CPUs · 48 passed
- Rust 1.99 rustc 1.99.1, Tokio 1.54.1 · Rust project · Linux amd64 · 2 CPUs · 27 passed
Refreshed every week. The data is written by the CI run itself and committed as-is; nobody edits it.
A runnable twin for every timeline
Every scenario and every what-if variant has a Java, Python, JavaScript, C#, Kotlin, Go or Rust program that does exactly what the timeline shows, with the same code lines, in the same order. A test fails the build if a timeline has no twin, or if its code drifts from the twin.
Facts, not timings
Threads are scheduled differently on every machine, so the tests never check how long something took. They check facts that must hold on any machine: which thread ran a callback, which thread was BLOCKED while another held a lock, which goroutine a goroutine dump shows in [chan send], that two Python threads never use more than one CPU between them while the GIL is on, that Rust refuses to compile a shared counter without a Mutex. Gates and latches force the interesting interleaving, so a test can't pass by luck.
For example, "a non-async thenApply runs on the thread that completed the future":
var f = CompletableFuture.supplyAsync(() -> {
Facts.await(gate);
return "fresh";
}, ioPool);
var ranOn = f.thenApply(r -> Thread.currentThread().getName());
gate.countDown();
assertEquals("io-1", ranOn.join());
Two versions where it matters
GitHub Actions runs every test on every change, and again every week: Java on JDK 21 and 25, Python on Python 3.14, 3.15 and free-threaded 3.15t, JavaScript on Node.js 22 and 24, C# on .NET 8 and 10, Kotlin on Kotlin 2.4 with kotlinx.coroutines 1.11, Go on Go 1.26 and 1.27, Rust on Rust 1.99 with Tokio 1.54. Some behavior really differs between versions: a virtual thread blocking inside synchronized is pinned to its carrier on JDK 21 but not on JDK 25 (JEP 491), and the tests check both.
Does your Java version matter?
Mostly not. In October 2026 we ran every program on JDK 8, 11, 17, 21, 25 and 27. Every program that compiles on a version behaved the same there, apart from three things:
| JDK 8, 11, 17 | JDK 21 | JDK 25 | JDK 27 |
|---|
Thread.stop() | Kills the thread (up to JDK 19) | Throws UnsupportedOperationException (from JDK 20) | Removed |
|---|
supplyAsync() with 2 CPUs | A new thread per task | The common pool's single thread |
|---|
Virtual thread blocking in synchronized or wait() | No virtual threads | Pins its carrier | Frees its carrier |
|---|
The scenarios these affect say so in their notes.
Gotchas are claims too
The warnings under each timeline are tested as well: that newFixedThreadPool's queue is unbounded, that throwing InterruptedException clears the interrupt flag, that go run -race reports the lost update, that a Rust mutex is poisoned after a panic.
About Threadline
Threadline is an independent site that teaches concurrency by showing which thread runs what. It's free, with no sign-up and no ads. We count page views, without cookies, to see which scenarios people use.
Back to the scenarios