Under The Hood
backend rust Tokio 1.x

Tokio runtime: work stealing and task scheduling

Last updated
Prerequisites:
Rust async/await basics
Familiarity with Tokio's multi-threaded runtime
tokio::main macro usage
  • rust
  • tokio
  • async
  • runtime
  • work-stealing
  • scheduler

Read at your depth

The practical view

#[tokio::main] async fn main() starts a Tokio runtime. The multi-threaded runtime (default with the rt-multi-thread feature) spawns one worker thread per core, each with its own task queue, and work-stealing balances load. spawn(async { ... }) schedules a task; await points yield control back to the runtime; timers (tokio::time) and I/O (tokio::net, tokio::fs) integrate with the reactor. The single-threaded runtime (flavor = "current_thread") is the lightweight option for CPU-bound or simple I/O programs. Tasks are Send because the runtime may move them across worker threads — that is why spawned futures must be Send + 'static.

Legacy vs modern

Blocking a worker thread vs proper async spawn

Synchronous blocking inside async code stalls the whole worker; delegating the blocking call to spawn_blocking keeps the scheduler responsive.

before → after
Blocking inside async
async fn handle() {
  std::thread::sleep(Duration::from_millis(500)); // blocks the worker
  process(&data);
}
spawn_blocking delegation
let data = tokio::task::spawn_blocking(|| fs::read("big.bin")).await??;

Interview gotchas

Context

This is the first real-world Tokio pitfall — the interviewer wants the scheduler mechanics (worker starvation), not just 'don't block'.

The mechanical answer

A blocking call (std::fs::read, std::net::TcpStream read, thread::sleep, a lock held across an await) occupies the worker thread for its duration. Tokio cannot preempt the task — it is cooperative — so all other tasks scheduled on that worker stop making progress; with the default worker-per-core setup, N simultaneous blocking calls can stall the entire runtime (the reactor thread still processes events, but polled tasks starve). Tokio detects long-blocking threads (after a 100ms-ish threshold it can grow or emit a warning via the blocking pool heuristic) but it cannot fix it. The correct pattern is tokio::fs/tokio::net or spawn_blocking for CPU/blocking work.

Trap

The trap answer is 'the task will be moved to another thread'. Tasks are not migrated while running — they are only migrated between polls, and a blocked task cannot be polled. Another trap: 'std sleeps are fine because they're short' — any blocking in a hot task skews latency for co-located tasks. Candidates who name the cooperative-polling model and spawn_blocking show they understand why this is a scheduling property, not a style preference.

Under The Hood — a multi-depth technical interview hub.

Third-party licenses Press ⌘ K to search.