See which thread runs what.
Concurrency in Java, Python, JavaScript, C#, Kotlin, Go and Rust, animated on real thread lanes: threads, locks, the GIL, channels and async tasks.
- Verified, not generated. Every timeline is checked against a real program on every change, on two JDKs, three Python builds, two Node.js versions, two .NETs, Kotlin, two Go releases and Rust. How we verify
- Predict, then watch. Key scenarios ask what the threads will do before you see it. Skip it any time.
- Stuck in production? Paste a Java thread dump to see who holds which lock and jump to the matching animation.
Java 19 scenarios
Basics
- 01Starting a threadstart() runs download() on a new thread, and main carries on at once. The JVM waits for worker before it exits.What-if: run() by mistake, daemon thread
- 02Stopping a threadinterrupt() asks a thread to stop. A sleeping thread answers at once, because sleep() throws InterruptedException.What-if: busy loop checks the flag, exception swallowed
- 03wait() vs sleep()napper sleeps inside synchronized. It stops running but keeps the lock, so other is BLOCKED the whole time.What-if: wait() releases it
Shared state
- 04Why count++ loses updatesTwo threads each run count++ once. The result can be 1, not 2.What-if: synchronized, AtomicInteger
- 05Why a thread never sees your flagmain sets stop = true, but worker keeps spinning forever. Without volatile, Java doesn't promise worker ever sees the write.What-if: volatile
- 06DeadlockT1 holds A and wants B. T2 holds B and wants A. Each waits for the other, forever.What-if: same lock order
- 07Thread-safe collectionsTwo threads add to one ArrayList. Both write into the same slot, and one element is lost.What-if: synchronizedList, ConcurrentHashMap
- 08Why ConcurrentModificationException happens with one threadA for-each loop removes bob from the list it is walking. The remove works; the loop's next step throws.What-if: removeIf, another thread adds, CopyOnWriteArrayList
Executors
- 09How a thread pool runs tasksA fixed pool runs as many tasks as it has threads. The rest wait in a queue for the next free thread.What-if: 4 threads
- 10Future.get() makes you waitsubmit() gives you a Future right away. get() trades it for the result, and blocks until the result exists.What-if: get() too early
- 11Why your Java program never exits (ExecutorService)main submits three tasks and returns without shutdown(). The tasks run, but the pool's threads wait for more work forever.What-if: shutdown(), shutdownNow()
CompletableFuture
- 12Which thread runs each step?supplyAsync → thenApply → thenAccept. Each step starts when the one before it finishes, and gets its result.What-if: thenApply vs thenApplyAsync
- 13Combining futuresDependent calls run one after another. Independent calls run side by side, then merge.What-if: allOf fan-out
- 14When a step throwsAn exception skips the next steps until something handles it. exceptionally runs only on failure; handle always runs.What-if: nobody handles it
Coordination
- 15Wait for N things with CountDownLatchmain waits until three loaders have each counted down once. Then the latch opens.What-if: a loader throws
- 16Limit concurrency with a SemaphoreFour threads want to call an API that allows two at a time. A Semaphore with 2 permits keeps the rest waiting.What-if: 1 permit
- 17Producer and consumer with a BlockingQueueA fast producer and a slow consumer share a queue with room for 2. The queue makes each one wait for the other.What-if: unbounded
Advanced
- 18When parallelStream starves the common poolA parallel stream of blocking calls fills the common pool. An unrelated quick task waits in line behind it.What-if: its own executor
- 19Virtual threadsThree virtual threads share two real (carrier) threads. When one blocks, it steps off its carrier so another can run.What-if: 2 platform threads
Python 4 scenarios
Choosing a model
Threads
Processes
Async
JavaScript 3 scenarios
The event loop
One thread
Combining promises
C# 3 scenarios
async and threads
Deadlocks
Combining tasks
Kotlin 3 scenarios
Coroutines and threads
Dispatchers
Cancellation
Go 7 scenarios
Basics
Shared state
- 02Why count++ loses updates in goroutinesTwo goroutines each run count++ once. The result can be 1, not 2.What-if: sync.Mutex, atomic.Int64
- 07fatal error: concurrent map writes, explainedTwo goroutines write one plain map at the same time. The Go runtime notices and stops the whole program.What-if: sync.Mutex
Channels
- 03Does a channel send wait for the receiver?On an unbuffered channel, main's send waits until the receiver is ready to take the value.What-if: buffer of 1
- 04A worker pool on a channelTwo workers range over a jobs channel. close(jobs) tells them no more jobs are coming.What-if: forgot close
- 05A select timeout that leaks a goroutineThe timeout wins, but slowCall's goroutine is left stuck on its send, forever.What-if: buffer of 1, context
Cancellation
Rust 5 scenarios
Threads
Async
- 02Tokio tasks switch only at .awaitTwo tasks share one thread. They take turns, and every switch happens at an .await.What-if: no .await
- 03Blocking in async code stalls every taskmain calls a blocking sleep on the runtime's only thread. The ticker can't tick until it ends.What-if: spawn_blocking, sleep().await
- 04join! vs awaiting one by oneEach .await finishes before the next line runs, so fetch("b") starts only after fetch("a") is done.What-if: join!
- 05future cannot be sent between threads safely, explainedA spawned task can resume on another worker thread after any .await, so a std MutexGuard has to be dropped first.What-if: tokio::sync::Mutex
Compare across languages
- Concurrency vs parallelismTaking turns on one thread vs running at the same instant on two cores, in Rust, Python and free-threaded Python.
- Mutex vs semaphoreOne thread at a time vs N at a time, with a Go mutex and Java semaphores of 2 and 1 permits.
- Process vs threadThreads share memory and can race on it; processes each get their own copy. Java threads next to Python processes.