I've been using Tokio since the 0.1 days when async Rust was still experimental as hell. Back then, you had to pick between Tokio and async-std, and honestly, most of us went with Tokio because it had more ecosystem support. Now async-std is officially dead as of March 2025, so that choice got easier.
The core problem Tokio solves is that traditional threading is expensive. Java threads eat about a meg of memory each, so you hit limits around 30,000 threads on decent hardware. With Tokio, tasks are cheap - maybe 2KB each - so you can spawn hundreds of thousands without your server keeling over.
How Tokio Actually Works
The work-stealing scheduler sounds fancy but really just means your CPU cores stay busy. Each core gets its own task queue, and when one runs out of work, it steals from the others. Pretty clever, and it mostly works well in practice.
Under the hood, Tokio uses epoll on Linux, kqueue on macOS, and IOCP on Windows to efficiently wait for I/O events. This means one thread can monitor thousands of network connections without blocking. The reactor pattern isn't new - nginx has been doing this forever - but Tokio makes it accessible in Rust.
Real Performance in Production
Discord's blog post about switching from Go to Rust is worth reading for actual performance numbers, not marketing fluff. They were hitting Go's garbage collector limits with their message volume, and Tokio helped them handle millions of concurrent WebSocket connections.
I've personally seen Tokio handle 100K+ concurrent connections on a single server with 16GB RAM. The limiting factors are usually file descriptors (bump your ulimits) and network bandwidth, not Tokio's scheduler.
The Learning Curve Is Real
Here's what the docs don't tell you: async Rust has a steep learning curve. If you're coming from blocking I/O, you'll spend hours debugging why your code hangs. Async stack traces are garbage, and error messages often don't make sense until you understand the borrow checker's relationship with futures.
The `Send + Sync` requirements for spawned tasks will bite you. You'll try to share a `Rc
Ecosystem Lock-in (But It's Good)
Once you pick Tokio, you're pretty much locked into its ecosystem. Axum, Hyper, Tonic, Tower - they all assume Tokio. This isn't necessarily bad since the ecosystem is mature, but switching async runtimes later is a pain in the ass.
The good news is that Tokio's ecosystem actually works together. Unlike JavaScript where every HTTP client has different interfaces, Tokio crates generally play nice with each other.