Part 8 · 1 chapters · ~8 min
One Problem in Five Languages
A concurrent, rate-limited fetcher (at most 10 requests in flight, a per-request timeout, results collected, cancellation on first fatal error) written in Node, Go, Rust, Java and Python, compared for correctness, ergonomics and the concurrency model each language encourages.
13
The same fetcher, five ways
code
// Node: a small pool of async workers pulling from a shared index
async function fetchAll(urls, limit = 10) {
const out = new Array(urls.length); let i = 0;
const worker = async () => { while (i < urls.length) { const k = i++; out[k] = await fetch(urls[k], { signal: AbortSignal.timeout(5000) }).then(r => r.status); } };
await Promise.all(Array.from({ length: limit }, worker));
return out;
}
// Go: errgroup with a limit and a context that cancels on the first error
g, ctx := errgroup.WithContext(ctx); g.SetLimit(10)
for i, u := range urls { g.Go(func() error { s, err := get(ctx, u, 5*time.Second); out[i] = s; return err }) }
err := g.Wait()
// Rust (Tokio): buffer_unordered caps concurrency at 10
let out: Vec<_> = stream::iter(urls).map(|u| async move { timeout(Duration::from_secs(5), client.get(u).send()).await })
.buffer_unordered(10).collect().await;
// Java 21: virtual threads + a semaphore
try (var ex = Executors.newVirtualThreadPerTaskExecutor()) {
var sem = new Semaphore(10);
var fs = urls.stream().map(u -> ex.submit(() -> { sem.acquire(); try { return get(u); } finally { sem.release(); } })).toList();
for (var f : fs) results.add(f.get());
}
# Python asyncio: a semaphore and gather
async def fetch_all(urls, limit=10):
sem = asyncio.Semaphore(limit)
async def one(u):
async with sem: return await asyncio.wait_for(get(u), 5)
return await asyncio.gather(*(one(u) for u in urls))| language | model encouraged | notable |
|---|---|---|
| Node | single-threaded async | no data races, but await interleavings (part 0) |
| Go | goroutines + context + errgroup | cancellation by convention through context |
| Rust | async futures, checked at compile time | data races impossible in safe code; Send/Sync bounds |
| Java | virtual threads (blocking style, cheap threads) | no function colouring; structured scopes emerging |
| Python | asyncio for IO; processes for CPU | the GIL (and free-threaded builds from 3.13) matter for CPU work |