01 · INTRODUCTION
What Does async Mean in Swift?
The short answer
The async keyword marks a function whose execution may be suspended while it waits for asynchronous work.
It changes the function's contract. A caller must enter an asynchronous context and use await when calling it because the function may not return its result in one uninterrupted period of execution.
CODE EXAMPLE
func loadRocketLaunches() async throws -> [Launch] {
// This function is allowed to suspend.
}
async does not create a task, create a thread or make the function run concurrently with anything else.
💡 Important Terminology
Synchronous function = returns or throws before its caller can continue.
Asynchronous function = may suspend and allow its task to continue the function later.
async = the keyword that declares this asynchronous function contract.
02 · TUTORIAL
Begin With a Synchronous Function
A normal Swift function has a synchronous contract.
CODE EXAMPLE
func loadSavedLaunches() throws -> [Launch] {
let data = try Data(contentsOf: launchesFileURL)
return try JSONDecoder().decode([Launch].self, from: data)
}
The caller enters the function and cannot continue until the function returns or throws.
CODE EXAMPLE
let launches = try loadSavedLaunches()
show(launches)
EXECUTION DIAGRAM
SYNCHRONOUS CALL
Caller begins
│
▼
Enter loadSavedLaunches()
│
│ function occupies the current
│ path of execution
▼
Return [Launch]
│
▼
Caller continues to show(launches)
The implementation might finish quickly or take several seconds. The signature does not give the function permission to suspend its task while waiting.
If the function blocks, its thread remains occupied until the call completes.
03 · TUTORIAL
An Asynchronous Result May Arrive Later
A network response is not normally available at the instant we request it.
Before Swift Concurrency, an asynchronous API often delivered that later result through a completion handler:
CODE EXAMPLE
func loadRocketLaunches(
completion: @escaping (Result<[Launch], Error>) -> Void
) {
URLSession.shared.dataTask(with: launchesURL) { data, _, error in
// Decode the response and call completion later.
}.resume()
}
The function starts the request and returns before the launches exist.
The continuation of the operation is moved into another closure:
CODE EXAMPLE
loadRocketLaunches { result in
switch result {
case .success(let launches):
show(launches)
case .failure(let error):
show(error)
}
}
This is asynchronous code, but its control flow is divided between the original call and a callback that runs later.
04 · TUTORIAL
async Makes the Asynchronous Contract Part of the Function
Swift places that asynchronous behaviour directly into the function declaration:
CODE EXAMPLE
func loadRocketLaunches() async throws -> [Launch] {
let (data, _) = try await URLSession.shared.data(
from: launchesURL
)
return try JSONDecoder().decode([Launch].self, from: data)
}
The async keyword appears after the parameter list and before throws.
EXECUTION DIAGRAM
func loadRocketLaunches() async throws -> [Launch]
// │ │ │
// │ │ └── success value
// │ └── may produce an error
// └── may suspend
The function can now express a result that will be produced later without accepting a completion-handler parameter.
Its return type is still [Launch]. The asynchronous behaviour belongs to the function's contract rather than being wrapped inside its result value.
05 · TUTORIAL
async Means May Suspend
An asynchronous function may divide its execution into separate periods.
EXECUTION DIAGRAM
ASYNC FUNCTION — CONCEPTUAL MODEL
Enter loadRocketLaunches()
│
▼
Start URLSession request
│
▼
Possible suspension
│
│ task waits without reserving
│ its current thread
▼
Response becomes available
│
▼
Continue the function
│
▼
Decode and return [Launch]
The function still has one logical beginning and one eventual result.
What changes is that its task may stop executing the function while the awaited operation is incomplete and resume it later.
The complete function no longer needs to occupy one thread from entry to return.
💡 Remember
async does not mean the function will suspend.
It means the function is allowed to contain potential suspension points.
06 · TUTORIAL
An async Function Does Not Suspend Everywhere
Declaring a function async does not make every instruction interruptible.
Consider this deliberately bad example:
CODE EXAMPLE
@MainActor
func calculateLaunchWindows() async -> Int {
var result = 0
for value in 1...500_000_000 {
result &+= value
}
return result
}
The function is asynchronous, but its loop contains no potential suspension point.
Once the main actor begins executing the loop, the loop continues synchronously until it finishes. Writing async in the declaration does not make expensive CPU work inexpensive or move it away from the main actor.
EXECUTION DIAGRAM
MAIN-ACTOR ASYNC FUNCTION
Enter function
│
▼
████████ long synchronous loop ████████
│
▼
Return result
No suspension opportunity exists inside the loop.
Asynchronous functions suspend only at explicit potential suspension points in their call path. They do not spontaneously surrender their thread between arbitrary Swift instructions.
07 · TUTORIAL
Calling async Requires await
The caller must acknowledge that control may be suspended during an asynchronous call:
CODE EXAMPLE
let launches = try await loadRocketLaunches()
async belongs to the declaration.
await belongs to the call site.
EXECUTION DIAGRAM
DECLARATION
func loadRocketLaunches() async throws -> [Launch]
▲
│
this function may suspend
CALL SITE
let launches = try await loadRocketLaunches()
▲
│
suspension may occur here
The full behaviour of await deserves its own article. For this article, the important relationship is simple: an async function must be called from a context capable of awaiting it.
08 · TUTORIAL
Synchronous Code Cannot Directly Call async Code
This call does not compile inside an ordinary synchronous function:
CODE EXAMPLE
func refresh() {
let launches = try await loadRocketLaunches()
// Compiler error: 'async' call in a function
// that does not support concurrency
}
The problem is structural.
If loadRocketLaunches() suspends, the caller must also be capable of suspending until the result becomes available.
One solution is to make the caller asynchronous:
CODE EXAMPLE
func refresh() async throws {
let launches = try await loadRocketLaunches()
show(launches)
}
The asynchronous effect has moved one level upward into refresh().
EXECUTION DIAGRAM
loadRocketLaunches() is async
│
▼
refresh() calls it with await
│
▼
refresh() must also be async
│
▼
its caller must enter an async context
This is sometimes called colouring the function, but it is better understood as an honest contract. Every caller can see that the operation may suspend.
09 · TUTORIAL
How Does Asynchronous Code Begin?
Eventually the call chain reaches a synchronous boundary such as a button action.
CODE EXAMPLE
Button("Refresh") {
Task {
try await refresh()
}
}
The synchronous button closure cannot suspend.
Task {} creates an asynchronous context in which refresh() can be awaited.
EXECUTION DIAGRAM
SYNCHRONOUS UI EVENT
│
▼
Create Task
│
▼
Enter asynchronous context
│
▼
await refresh()
│
▼
await loadRocketLaunches()
The task is the asynchronous operation's lifetime. The async functions describe suspendible work performed inside that task.
Creating and owning tasks is covered in What Is a Task in Swift?.
10 · TUTORIAL
async Does Not Mean Concurrent
An asynchronous function can be called sequentially.
CODE EXAMPLE
func loadDashboard() async throws -> Dashboard {
let launches = try await loadRocketLaunches()
let agencies = try await loadSpaceAgencies()
return Dashboard(
launches: launches,
agencies: agencies
)
}
The second operation does not begin until the first call has produced its result.
EXECUTION DIAGRAM
SEQUENTIAL ASYNC CALLS
loadRocketLaunches()
████████████████
loadSpaceAgencies()
████████████████
Both functions are asynchronous because each may suspend.
They are not concurrent in this example because the calls are deliberately ordered.
Starting independent child tasks is a separate decision covered by structured concurrency. The presence of async alone does not make two operations overlap.
11 · TUTORIAL
async Does Not Mean Background Thread
The keyword does not select a thread or executor.
CODE EXAMPLE
@MainActor
func updateLaunchStatus() async {
status = "Preparing"
}
This function is both asynchronous and isolated to the main actor.
Its isolated work belongs to the main actor's executor. The fact that the function is async does not make its instructions background work.
Likewise, an asynchronous function with no actor isolation is not promising one particular background thread. Swift schedules eligible work according to its execution context.
💡 Important
async describes the possibility of suspension.
It does not describe concurrency, parallelism, actor isolation or thread selection.
12 · TUTORIAL
async Changes Application Architecture
Once a low-level operation becomes asynchronous, its callers must decide how that asynchronous contract moves through the feature.
CODE EXAMPLE
struct LaunchAPI {
func loadLaunches() async throws -> [Launch] {
let (data, _) = try await URLSession.shared.data(
from: launchesURL
)
return try JSONDecoder().decode(
[Launch].self,
from: data
)
}
}
The feature exposes its own asynchronous operation:
CODE EXAMPLE
@MainActor
final class LaunchListFeature: ObservableObject {
@Published private(set) var launches: [Launch] = []
private let api: LaunchAPI
init(api: LaunchAPI) {
self.api = api
}
func refresh() async {
do {
launches = try await api.loadLaunches()
} catch {
launches = []
}
}
}
The API owns the asynchronous network capability.
The feature owns the application operation named refresh(). Its signature tells every caller that refreshing may suspend.
A view can create a task at the synchronous UI boundary:
CODE EXAMPLE
Button("Refresh") {
Task {
await feature.refresh()
}
}
The asynchronous contract travels upward through the codebase until it reaches the place that owns the operation's lifetime.
This is why adopting Swift Concurrency is architectural rather than cosmetic. Adding async to one low-level function reveals which higher-level operations also need permission to suspend.
13 · TUTORIAL
The Complete Mental Model
CODE EXAMPLE
SYNCHRONOUS FUNCTION
One uninterrupted call contract
Caller waits for return or throw
ASYNC FUNCTION
Function contract permits suspension
Caller marks the call with await
Task may continue the function later
TASK
Provides the lifetime in which
the async function executes
EXECUTOR AND THREADS
Provide the resources on which
eligible task instructions run
async is part of a function's type and declaration.
It tells the compiler and every caller that executing the function may involve a suspension point.
It does not start the function, create its task or choose its thread.
It gives the function permission to participate in an asynchronous call chain.
That is what async means in Swift.
14 · TUTORIAL
What to Remember
💡 What to Remember
1. async marks a function that may suspend while executing.
2. The keyword forms part of the function's declaration and function type.
3. An async function can return an ordinary value after its asynchronous work completes.
4. Declaring a function async does not guarantee that it will suspend.
5. An asynchronous function does not spontaneously suspend between arbitrary instructions.
6. Calls to asynchronous functions are marked with await.
7. A synchronous function cannot directly call and await an asynchronous function.
8. async does not create a task or a thread.
9. async does not automatically make operations concurrent.
10. The asynchronous contract propagates upward until the code reaches a task-owning boundary.
15 · TUTORIAL
Frequently Asked Questions
What does async do in Swift?
async marks a function or function type whose execution may suspend. Its caller must use await from an asynchronous context because the result may be produced after one or more periods of suspension.
Does an async function always run asynchronously?
It always has an asynchronous function contract, but it does not necessarily suspend during every call. Whether suspension occurs depends on the asynchronous operations reached and whether their results are already available.
Does async create a background thread?
No. async does not create or select a thread. It permits suspension. Executors and the runtime arrange where eligible task work executes according to isolation and scheduling requirements.
Does async make a function concurrent?
No. Several asynchronous calls may still execute sequentially. Concurrency requires the program to structure independent work so that their lifetimes can overlap.
Can a synchronous function call an async function?
It cannot directly await one. The caller must itself become asynchronous or create an asynchronous context—usually by creating a task at an appropriate synchronous boundary.
What is the difference between async and await?
async declares that a function may suspend. await marks a call site where execution may suspend while calling an asynchronous function.
16 · TUTORIAL
Continue Learning Swift Concurrency
Read What Is a Task in Swift? to understand the operation in which asynchronous functions execute.
Continue with What Does await Mean in Swift? to examine exactly what happens at a possible suspension point.
Swift's official Concurrency documentation defines the language rules for asynchronous functions and calls.
17 · TUTORIAL
Download Xcode Playground
The accompanying What Does async Mean in Swift? Xcode playground can turn the function contract into visible execution.
It should compare a synchronous function with an asynchronous function, expose the compiler error produced by calling async code from a synchronous context, propagate async through several callers and finish with the complete LaunchListFeature.
A final experiment should place a long synchronous loop inside an async main-actor function to prove that the keyword alone does not create suspension or background execution.
The article explains the contract. The playground will show where that contract changes the code.
