Skip to content

04 — Sync vs Async

rebeloper edited this page Jul 12, 2026 · 2 revisions

🍽️ Intuition

Not every customer waits for their order the same way.

Some customers stand at the counter — they placed the order, and they're not moving until it's in their hands. They can't do anything else in the meantime; they're just standing there.

Other customers get a buzzer 🔔. They place the order, walk off to find a table, check their phone, chat with a friend — and only come back when the buzzer goes off.

  • Stand at the counter, do nothing until the order arrives → sync
  • Take the buzzer, walk away, come back when it goes off → async

👉 Both customers get their order. The difference is whether they're free to do anything else while it's being made.


🧠 What It Means In Swift

DispatchQueue gives you two ways to submit a block, and they behave completely differently:

  • .sync — submits the block and blocks the calling thread until that block finishes. Whatever called .sync — some chef 🧑‍🍳 elsewhere in the kitchen — cannot move on to its next line of code until the work is done. That chef is frozen, standing at the counter, not chopping, not cooking, just waiting.
  • .async — submits the block and returns immediately. The calling chef's thread keeps going with its next line of code right away; the submitted block runs later, on whichever chef the queue hands it to, whenever it gets around to it.
queue.sync { ... }   // caller waits here, standing at the counter
queue.async { ... }  // caller gets a buzzer and walks away

👉 .sync and .async don't change what work gets done — they change whether the caller waits for it.

One thing to be precise about: this is separate from serial vs. concurrent from Chapter 3. Serial vs. concurrent is about the queue — how many blocks it lets run at once. Sync vs. async is about the caller — whether it waits around for the block to finish. You can .sync onto a concurrent queue, and you can .async onto a serial one.


📊 ASCII Diagram

SYNC — caller waits at the counter 🧍

Caller: ──[submit]──[.......waiting.......]──[continues]──>
Queue:            └──> 🧑‍🍳 cooks the order ──┘


ASYNC — caller takes the buzzer and walks away 🔔

Caller: ──[submit]──[continues immediately]──────────────>
Queue:            └──> 🧑‍🍳 cooks the order ──> 🔔 (done, whenever)

💻 Tiny Swift Example

queue.sync {
    print("This finishes before the next line runs")
}
print("Runs after the sync block")

queue.async {
    print("This may run before or after the next line")
}
print("Runs immediately, doesn't wait for async block")

👉 The .sync block is guaranteed to print before "Runs after the sync block." The .async block might print before or after "Runs immediately" — you genuinely don't know, and you don't need to.


🔄 vs. Swift Concurrency

await in Swift Concurrency looks like .sync — the code underneath it reads top-to-bottom, in order, as if it's waiting right there. But it behaves like neither .sync nor .async on its own:

  • Like .sync, the code after await doesn't run until the awaited work finishes — you get sequential, readable order.
  • Unlike .sync, await doesn't block the chef's thread while it waits. It suspends the current task, freeing that chef to go help with other work, and resumes later — possibly handed to a different chef — when the result is ready.

👉 Think of await as a customer with a buzzer who chooses to stand right next to the pickup counter instead of wandering off — they still get notified the instant it's ready, but the chef isn't frozen on their account; that chef can go help another station while the buzzer is pending.


🧸 Memory Sentence

Sync waits at the counter; async gets a buzzer.


⚠️ Analogy Limit

A real buzzer is harmless — worst case, your table gets cold food a minute later. Nobody else in the restaurant is affected by you standing at the counter, either.

.sync is not so forgiving:

  • If a chef calls .sync on a queue, and that queue is already busy running the exact serial queue that chef is currently executing on, the chef ends up waiting for a block that can only run after the chef finishes — but the chef can't finish because it's stuck waiting. Everyone waits on everyone else, forever — a deadlock. Chapter 8 covers this in full.
  • A misused .sync doesn't just make one customer wait a little longer — it can freeze the queue, and everything behind it, indefinitely.

So "standing at the counter" captures the waiting, but not the fact that a badly placed .sync call can lock up your entire app, not just one customer's afternoon.


✅ Check Your Understanding

When would you actually choose .sync over .async? What are you giving up — and what are you gaining — by making the caller wait?

Clone this wiki locally