Skip to content

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#

// 10k concurrent goroutines, each ~few KB
for i := 0; i < 10000; i++ {
  go work(i)
}

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. Use delay() / await asyncio.sleep().
  • Forgotten cancellation: child tasks outlive their parent's request. Use structured concurrency (Kotlin coroutineScope, Java StructuredTaskScope).
  • Reactive flat-Map exploding: flatMap with unbounded concurrency = OOM. Use flatMap(fn, concurrency=8).
  • Goroutine leaks: spawn without close path. Tools: goleak package.
  • Promise.all rejection: one failure rejects whole batch; use Promise.allSettled when 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