Concurrency in Go, Java, Python, Node.js and Rust
Every major runtime gives you cheap concurrency, but the unit, the safety net and the failure modes differ. Know which runtimes run threads in parallel, how each one shares data safely, and how each cancels work, because interviews probe exactly where they diverge.
Key points
- 1
Go: goroutines on an M:N scheduler; channels for communication (send on closed or close twice panics, nil channels block forever);
sync.WaitGroup.Addmust happen beforeWait; never copy async.Mutex; cancellation travels throughcontext.Context. - 2
Java: since Java 21, virtual threads are cheap; create one per task and never pool them, and limit scarce resources with a
Semaphore. On JDK 21, blocking insidesynchronizedpins the carrier; JDK 24 (JEP 491) removed that. - 3
Java futures:
submithides exceptions untilget();join()throwsCompletionException; usethenComposefor functions that return futures;supplyAsyncwithout an executor sharesForkJoinPool.commonPool(). - 4
Rust:
Send(move to another thread) andSync(share&T) are checked at compile time, so data races are impossible in safe code. Deadlocks and logic races are not. UseArc<Mutex<T>>for shared mutable state, and don't hold astd::sync::MutexGuardacross.await. - 5
Python and Node.js: standard CPython runs one thread of bytecode at a time (the GIL), and Node runs JavaScript on one event loop. Both are cooperative: a CPU loop that never yields blocks everything else. Parallelism needs processes (Python) or
worker_threads(Node). - 6
C#:
async voidand.Result/.Wait()cause deadlocks under a SynchronizationContext, and thread-pool starvation even without one; go async all the way and useConfigureAwait(false)in library code. - 7
Erlang/Elixir: isolated processes, copied messages, preemption by reductions, and supervisors (
one_for_one,one_for_all,rest_for_one) that recover by restarting; always handle unexpected messages so selective receive stays fast.
Common traps
Cancellation is cooperative everywhere:
ctx.Done(),Thread.interrupt(),Task.cancel()andAbortSignalonly signal, and code that never checks keeps running.volatilein Java gives visibility, not atomicity:count++on a volatile field still loses updates.A plain
sync.Oncetreats a panicking function as done and never retries;CompletableFuture.allOfnever cancels the other futures when one fails.
Test yourself on Concurrency in Go, Java, Python, Node.js and Rust
Ten questions, with the answer and explanation after each one.