Answer
The core idea
On any DispatchQueue, .async and .sync differ in what happens to the calling thread.
.asyncschedules the work and returns immediately. The caller continues executing..syncschedules the work and blocks the caller until that work has finished.
The short interview answer is: .async means "run this asynchronously and don't wait," while .sync means "run this and don't continue until it finishes."
The same rule applies to DispatchQueue.main, with one important exception to remember: calling .sync on the main queue from the main thread causes a deadlock.
1. Sync versus async
Consider work submitted to a dispatch queue:
q.async {
print("async work")
}
print("caller continues immediately")
q.sync {
print("sync work")
}
print("caller continues after sync work finishes")The difference is entirely about the caller:
.asyncis fire and continue..syncis fire and wait.
Neither method guarantees a background thread. They only determine whether the caller waits for the submitted work to complete.
2. Using the main queue
DispatchQueue.main is the serial queue responsible for executing UI work.
After completing work on a background queue, the usual pattern is to hop back to the main queue asynchronously:
DispatchQueue.global(qos: .userInitiated).async {
let text = loadText()
DispatchQueue.main.async {
print(text)
// Update UIKit / SwiftUI
}
}Using .async lets the background thread continue immediately after scheduling the UI update.
Calling .sync on the main queue means the calling thread waits until the main queue executes the submitted block. That can be valid when called from a background thread and you truly need the UI work to finish before continuing, but it should be used sparingly.
3. The main-thread deadlock
This code is intentionally incorrect:
// Already running on the main thread
DispatchQueue.main.sync {
print("never executes")
}This causes a deadlock.
Here's why:
- The main thread submits work to the main queue using
.sync. - Because it's a synchronous call, the main thread waits for that work to finish.
- The submitted block cannot begin because the main queue is already busy executing the current work on the main thread.
- The main thread waits forever, and the queued block never starts.
This circular wait is the classic DispatchQueue.main.sync deadlock.
4. Why it matters
Using .sync incorrectly can freeze your application by blocking the caller or causing a deadlock. Using .async avoids blocking and is the standard way to schedule UI updates from background work.
The key mental model is simple:
.asyncaffects scheduling..syncaffects the caller by blocking it.
The destination queue determines where the work executes, while async versus sync determines whether the caller waits.
Interview angle
Start with the general rule: .async schedules work and returns immediately, while .sync blocks the caller until the work completes.
Then apply that rule to DispatchQueue.main, showing the common pattern of hopping back to the main queue with .async after background work.
Finish with the classic interview question: calling DispatchQueue.main.sync from the main thread deadlocks because the main queue cannot execute the submitted block while the main thread is blocked waiting for it. The DispatchQueue documentation is the canonical reference.