Async Models — Detailed#
The spectrum#
flowchart TB
subgraph Sync[Blocking I/O]
T1[1 thread per request<br/>simple but expensive]
end
subgraph Cb[Callbacks]
T2[Non-blocking I/O<br/>callback hell, hard errors]
end
subgraph Promises[Futures and Promises]
T3[Chainable .then catch<br/>composable]
end
subgraph Aw[async / await]
T4[Linear code over promises<br/>Python, JS, C#, Rust, Kotlin]
end
subgraph Lightweight[Coroutines / fibers / virtual threads]
T5[Cheap stacks<br/>Go goroutines, Java loom, Kotlin]
end
subgraph Msg[Actor / channel]
T6[Isolated state + messages<br/>Erlang, Akka, Go channels]
end
subgraph React[Reactive streams]
T7[Backpressure + composition<br/>RxJS, Project Reactor]
end
classDef s fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
class T1,T2,T3,T4,T5,T6,T7 s;
classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
classDef edge fill:#cffafe,stroke:#0e7490,stroke-width:1px,color:#0f172a;
classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
classDef datastore fill:#fee2e2,stroke:#991b1b,stroke-width:1px,color:#0f172a;
classDef cache fill:#fed7aa,stroke:#9a3412,stroke-width:1px,color:#0f172a;
classDef queue fill:#ede9fe,stroke:#5b21b6,stroke-width:1px,color:#0f172a;
classDef compute fill:#d1fae5,stroke:#065f46,stroke-width:1px,color:#0f172a;
classDef storage fill:#e5e7eb,stroke:#374151,stroke-width:1px,color:#0f172a;
classDef external fill:#fce7f3,stroke:#9d174d,stroke-width:1px,color:#0f172a;
classDef obs fill:#f3e8ff,stroke:#6b21a8,stroke-width:1px,color:#0f172a;
class T1,T2,T3,T4,T5,T6,T7 service;
Cheat sheet#
| Model | Stack cost | Sync-style code | Cancellation | Backpressure | Examples |
|---|---|---|---|---|---|
| Thread + blocking | MBs / thread | yes | interrupt | OS-level | Java pre-loom, classic C |
| Callback | none | no | manual | manual | Node 0.x, browser APIs |
| Future / Promise | small | partial | manual | manual | Java CompletableFuture, JS Promise |
| async / await | tiny | yes | structured | manual | Python asyncio, JS, C#, Rust |
| Coroutine / fiber / virtual thread | tiny | yes | structured | OS-level | Go, Kotlin coroutines, Loom |
| Actor | small | partial | mailbox | mailbox | Erlang, Akka |
| Channel | small | yes | close | bounded buf | Go, Kotlin Channel, CSP |
| Reactive streams | small | DSL | dispose | built-in | RxJava, Project Reactor, RxJS |
Why "callback hell" happened#
fs.readFile(a, (err, da) => {
if (err) return cb(err);
fs.readFile(b, (err, db) => {
if (err) return cb(err);
fetch(url, (err, body) => {
// 3 levels deep, errors scattered
});
});
});
Promises chain it linearly; async/await reads like sync.
async/await in two languages#
# Python
async def fetch_user(id):
user = await db.get(id)
profile = await http.get(f"/profiles/{user.id}")
return {"user": user, "profile": profile}
// Kotlin
suspend fun fetchUser(id: Int): UserView {
val user = db.get(id)
val profile = http.get("/profiles/${user.id}")
return UserView(user, profile)
}
Same shape: each await is a suspension point where the runtime can schedule other work.
Goroutines vs threads#
In Go, a goroutine starts at 2 KB stack and grows on demand. Java Loom achieves the same with virtual threads as of Java 21+.
Actor model#
flowchart LR
M[Mailbox]
A1([Actor A<br/>handler])
A2([Actor B<br/>handler])
A1 -->|send msg| M --> A2
classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
classDef edge fill:#cffafe,stroke:#0e7490,stroke-width:1px,color:#0f172a;
classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
classDef datastore fill:#fee2e2,stroke:#991b1b,stroke-width:1px,color:#0f172a;
classDef cache fill:#fed7aa,stroke:#9a3412,stroke-width:1px,color:#0f172a;
classDef queue fill:#ede9fe,stroke:#5b21b6,stroke-width:1px,color:#0f172a;
classDef compute fill:#d1fae5,stroke:#065f46,stroke-width:1px,color:#0f172a;
classDef storage fill:#e5e7eb,stroke:#374151,stroke-width:1px,color:#0f172a;
classDef external fill:#fce7f3,stroke:#9d174d,stroke-width:1px,color:#0f172a;
classDef obs fill:#f3e8ff,stroke:#6b21a8,stroke-width:1px,color:#0f172a;
class M,A1,A2 service;
Each actor: - Owns private state, no shared memory. - Processes one message at a time from its mailbox. - Can spawn / kill / restart other actors.
Erlang OTP uses this for fault tolerance ("let it crash + supervisor restarts").
Channels (CSP)#
ch := make(chan int, 100) // bounded buffer
go func() { for v := range ch { handle(v) } }()
ch <- 42 // sends; blocks if full → backpressure
Producers/consumers communicate by sending through channels. No shared mutable state needed.
Reactive streams#
Flux.fromIterable(orders)
.flatMap(this::enrich) // async, with backpressure
.filter(o -> o.total > 100)
.buffer(Duration.ofSeconds(5))
.subscribe(batch -> persist(batch));
Operators compose; the spec guarantees demand signalling (consumer requests N items, producer respects).
Picking a model#
| Need | Pick |
|---|---|
| Server that fans out to many backends | async/await or coroutines |
| Highly concurrent connection-per-user (chat, WS) | coroutines or virtual threads |
| Pipeline with backpressure | reactive streams |
| Isolated, supervised processes | actors |
| Simple CRUD with low concurrency | threads + blocking is fine |
Pitfalls#
- Blocking inside async:
Thread.sleep()inside a coroutine starves the event loop. Usedelay()/await asyncio.sleep(). - Forgotten cancellation: child tasks outlive their parent's request. Use structured concurrency (Kotlin
coroutineScope, JavaStructuredTaskScope). - Reactive flat-Map exploding:
flatMapwith unbounded concurrency = OOM. UseflatMap(fn, concurrency=8). - Goroutine leaks: spawn without close path. Tools:
goleakpackage. - Promise.all rejection: one failure rejects whole batch; use
Promise.allSettledwhen partial success matters.
Glossary & fundamentals#
Concepts referenced in this design. Each row links to its canonical page; the tag column shows whether it is a high-level (HLD) or low-level (LLD) concept.
| Tag | Concept | What it is | Page |
|---|---|---|---|
HLD |
Resilience patterns | timeout, retry, breaker, bulkhead, backpressure | resilience-patterns |
LLD |
Async models | futures / async-await / coroutines / actors | async-models |
LLD |
Threading & deadlocks | thread states, Coffman, lock ordering | threading-and-deadlocks |