-
Notifications
You must be signed in to change notification settings - Fork 0
04 — Sync vs Async
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.
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.
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)
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.
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 afterawaitdoesn't run until the awaited work finishes — you get sequential, readable order. - Unlike
.sync,awaitdoesn'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.
Sync waits at the counter; async gets a buzzer.
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
.syncon 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
.syncdoesn'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.
When would you actually choose .sync over .async? What are you giving up — and what are you gaining — by making the caller wait?