01 · INTRODUCTION
What Is Task Priority in Swift?
The short answer
Task priority is scheduling information that communicates the relative urgency of a Swift task. Executors can use it when deciding which eligible job should receive execution resources first.
CODE EXAMPLE
let refreshTask = Task(priority: .userInitiated) {
try await launchManager.fetchUpcomingLaunches()
}
The priority says that this refresh is important to an operation the user is actively waiting for. It does not reserve a thread, guarantee an immediate start or promise that this task will finish before every lower-priority task.
The central rule
Task priority influences scheduling. It does not define execution order.
02 · TUTORIAL
Why Swift Tasks Carry Priority
The previous article followed task jobs into executors. When several jobs are eligible to run, an executor needs scheduling information that helps it allocate limited execution resources.
Not all work has the same urgency:
• refreshing the screen after a user taps a button is time-sensitive;
• preparing data likely to appear soon is useful but less urgent;
• cleaning an old cache can normally wait.
Task priority allows these operations to carry that intent through the concurrency runtime.
Conceptual model:
EXECUTION DIAGRAM
Eligible task jobs
├─ user refresh high urgency
├─ launch prefetch medium urgency
└─ cache cleanup low urgency
│
▼
Executor considers priority
with its other scheduling rules
│
▼
Available execution resources
This is a teaching model. It does not represent one universal priority queue inside every executor. The exact treatment of priority depends on the platform and executor implementation.
03 · TUTORIAL
Priority Is a Scheduling Hint
A task's priority can influence how and when its jobs are scheduled. An executor will typically attempt to favour higher-priority eligible work over lower-priority work.
The word typically matters. Priority operates inside a real scheduling system with dependencies, actor isolation, already-running work and finite resources.
CODE EXAMPLE
let low = Task(priority: .low) {
print("Low-priority task")
}
let high = Task(priority: .high) {
print("High-priority task")
}
Do not assert that high prints first. The low-priority task may have begun before the high-priority task was created. The tasks may use different executors, and synchronous work already executing is not rewound merely because higher-priority work appears.
Important Terminology
A scheduling hint gives the runtime information it can use when making scheduling decisions. It is not a deterministic ordering instruction.
04 · TUTORIAL
The Standard Task Priorities
Swift provides platform-independent task priorities, ordered from higher to lower urgency:
Priority
.high
Typical intent
Important work that should make timely progress
Priority
.medium
Typical intent
Ordinary work without a stronger urgency requirement
Priority
.low
Typical intent
Deferrable work that is not immediately user-critical
Priority
.background
Typical intent
Maintenance work with the lowest standard urgency
On Apple platforms, familiar intent-based names are also available:
• .userInitiated is an alias for high priority;
• .utility is an alias for low priority.
The intent-based names can make application code easier to read:
CODE EXAMPLE
let refreshTask = Task(priority: .userInitiated) {
try await refreshVisibleLaunches()
}
let cleanupTask = Task.detached(priority: .utility) {
try await removeExpiredCacheFiles()
}
These names describe relative urgency. They do not promise a completion deadline or map the task permanently to one operating-system thread priority.
05 · TUTORIAL
Task { } Usually Inherits Priority
The earlier task-inheritance article established that Task { } begins with context from its creation point. Priority is part of that context.
CODE EXAMPLE
func refreshLaunches() async {
print("Current:", Task.currentPriority)
let indexingTask = Task {
print("Inherited:", Task.currentPriority)
await rebuildIndex()
}
await indexingTask.value
}
When the initializer is called from an existing task and no explicit priority is supplied, the new unstructured task inherits the current task's priority.
EXECUTION DIAGRAM
Current task priority
│
│ Task { operation }
▼
New task begins with inherited priority
This propagation is usually more useful than assigning a priority at every layer. A user-initiated feature operation can call several async functions and create related work without losing the urgency of the action that began it.
If Task { } is created where no current task exists, Swift derives an appropriate initial priority from the surrounding execution context. Application code should not depend on one universal fallback value.
06 · TUTORIAL
You Can Request an Explicit Priority
The Task initializer accepts an optional priority:
CODE EXAMPLE
let task = Task(priority: .utility) {
await launchCache.preloadImages()
}
An explicit value overrides the priority that would otherwise be inherited at creation.
Use this when the new operation genuinely has different urgency from the task creating it. For example, a user refresh might begin a cache-maintenance operation that should not compete with the visible result at the same urgency.
Do not treat priority as decoration:
CODE EXAMPLE
// Weak design: every task is declared urgent.
Task(priority: .high) { await refreshLaunches() }
Task(priority: .high) { await prefetchImages() }
Task(priority: .high) { await cleanCache() }
If everything is high priority, the value communicates very little. It can also make genuinely urgent work harder to distinguish from work that could wait.
Prefer inherited priority when the work belongs to the current operation.
Override it only when the new work has a meaningfully different urgency.
07 · TUTORIAL
Task.detached Does Not Inherit Priority
A detached task starts independently of the current task context. This includes priority.
CODE EXAMPLE
Task(priority: .userInitiated) {
let inherited = Task {
print(Task.currentPriority)
// Begins with the surrounding task's priority.
}
let detached = Task.detached(priority: .utility) {
print(Task.currentPriority)
// Begins with the explicitly requested priority.
}
await inherited.value
await detached.value
}
If detached work has an important urgency requirement, state it explicitly. Passing no priority does not preserve the creator's current task priority.
This does not make detachment the tool for lowering every piece of work. Task.detached also gives up inherited actor isolation and task-local values. Choose it only when that full independence matches the architecture.
08 · TUTORIAL
Priority Does Not Select an Executor or Thread
Priority and isolation answer different questions.
Concept
Actor isolation
Question answered
Which isolated state may this code access?
Concept
Executor
Question answered
Which scheduling service accepts this job?
Concept
Task priority
Question answered
How urgent is this task relative to other work?
Concept
System thread
Question answered
Which operating-system resource executes this job now?
A high-priority task created inside MainActor-isolated code remains main-actor-isolated:
CODE EXAMPLE
@MainActor
func refresh() {
Task(priority: .high) {
// Still MainActor-isolated.
isLoading = true
}
}
The high priority does not move the closure to the global concurrent executor. It does not make synchronous work safe to perform on the main thread. Isolation determines where the job is eligible to execute; priority can inform scheduling within that system.
09 · TUTORIAL
Priority Is Not FIFO Ordering
Executors are not required to run jobs in submission order. Priority is one reason a later job may be favoured, but priority itself still does not establish deterministic ordering.
CODE EXAMPLE
let first = Task(priority: .low) {
await record("first")
}
let second = Task(priority: .high) {
await record("second")
}
The names first and second describe creation order only. They do not prove start order or completion order.
If one operation must happen before another, express a dependency:
CODE EXAMPLE
let launches = try await fetchLaunches()
let index = await buildIndex(from: launches)
Do not attempt to create correctness by giving fetchLaunches() a higher priority and hoping it finishes first.
Priority is for urgency. Dependencies are for order.
10 · TUTORIAL
What Is Priority Inversion?
A priority inversion occurs when high-priority work cannot proceed because it depends on lower-priority work.
EXECUTION DIAGRAM
High-priority task
│
│ awaits result
▼
Low-priority task still performing required work
Without intervention, unrelated medium-priority work could repeatedly receive resources while the low-priority dependency makes slow progress. The high-priority task would remain waiting even though the result is urgent.
Swift's task model exposes dependency information to the runtime. That makes priority elevation possible.
11 · TUTORIAL
How Priority Escalation Works
Suppose a low-priority task begins preparing a cached launch catalogue:
CODE EXAMPLE
let catalogueTask = Task(priority: .low) {
try await buildLaunchCatalogue()
}
Later, a high-priority task needs that exact result:
CODE EXAMPLE
let visiblePageTask = Task(priority: .high) {
let catalogue = try await catalogueTask.value
return makeVisiblePage(from: catalogue)
}
When higher-priority work awaits a lower-priority task's value, the runtime can elevate the awaited task until it completes. This helps the dependency finish with the urgency of the work waiting for it.
EXECUTION DIAGRAM
catalogueTask: low priority
│
│ high-priority task awaits value
▼
catalogueTask receives priority elevation
│
▼
result completes ──▶ high-priority task continues
Actors have a related protection. If higher-priority work is enqueued on an actor while that actor is executing lower-priority work, the currently executing work can be treated temporarily with elevated priority. This helps the actor reach the higher-priority job without creating a priority inversion.
Priority elevation is runtime scheduling support. It is not cancellation, preemption or proof that the lower-priority operation will complete immediately.
12 · TUTORIAL
Reading the Current Priority
Code running as part of a task can inspect its priority:
CODE EXAMPLE
func logCurrentTask() {
print("Task priority:", Task.currentPriority)
}
This can help with diagnostics, experiments and understanding inherited context.
Do not continuously branch business logic on the current priority:
CODE EXAMPLE
// Fragile design.
if Task.currentPriority == .high {
return detailedResult()
} else {
return incompleteResult()
}
Priority can be elevated, and executor behavior differs across platforms. The correctness and meaning of a result should not depend on an assumed scheduling category.
If an operation needs an explicit quality setting—such as image resolution or result depth—pass that requirement as ordinary application data.
13 · TUTORIAL
A Complete Launch-Feature Example
The launch feature below uses priority to communicate relative urgency while preserving explicit ownership and isolation.
CODE EXAMPLE
struct Launch: Sendable {
let id: UUID
let missionName: String
}
protocol LaunchAPI: Sendable {
func fetchUpcomingLaunches() async throws -> [Launch]
}
protocol MissionImageCache: Sendable {
func prefetch(_ launchIDs: [UUID]) async
func removeExpiredEntries() async
}
actor LaunchRepository {
private let launchAPI: any LaunchAPI
private let imageCache: any MissionImageCache
init(
launchAPI: any LaunchAPI,
imageCache: any MissionImageCache
) {
self.launchAPI = launchAPI
self.imageCache = imageCache
}
func fetchVisibleLaunches() async throws -> [Launch] {
try await launchAPI.fetchUpcomingLaunches()
}
func prefetchMissionImages(for launches: [Launch]) async {
await imageCache.prefetch(launches.map(\.id))
}
func removeExpiredCacheEntries() async {
await imageCache.removeExpiredEntries()
}
}
@MainActor
final class LaunchFeature {
private(set) var launches: [Launch] = []
private(set) var isLoading = false
private(set) var errorMessage: String?
private let repository: LaunchRepository
private var refreshTask: Task<Void, Never>?
private var maintenanceTask: Task<Void, Never>?
init(repository: LaunchRepository) {
self.repository = repository
}
func refresh() {
refreshTask?.cancel()
refreshTask = Task(priority: .userInitiated) {
isLoading = true
defer { isLoading = false }
do {
let loaded = try await repository.fetchVisibleLaunches()
try Task.checkCancellation()
launches = loaded
errorMessage = nil
Task(priority: .utility) {
await repository.prefetchMissionImages(for: loaded)
}
} catch is CancellationError {
// A newer refresh owns the visible result.
} catch {
errorMessage = "Could not load launches."
}
}
}
func beginMaintenance() {
maintenanceTask?.cancel()
maintenanceTask = Task(priority: .background) {
await repository.removeExpiredCacheEntries()
}
}
}
The visible refresh is user-initiated because the user is waiting for the screen to update. Image prefetching is useful but less urgent, so it explicitly requests utility priority. Cache cleanup uses background priority because it is maintenance work.
All three tasks are created inside a MainActor-isolated feature, so their closures inherit MainActor even when their priorities differ. Priority does not change isolation. Each repository call crosses to the repository actor and can suspend the feature task.
The example retains handles for feature-owned refresh and maintenance lifecycles. The nested prefetch is intentionally fire-and-forget for illustration; in a production feature, its ownership and cancellation policy should also be explicit if completion matters.
The scheduling intent is now visible:
CODE EXAMPLE
Visible refresh .userInitiated
Image prefetch .utility
Cache maintenance .background
Priority describes urgency.
Actor isolation still describes access and execution context.
14 · TUTORIAL
When Should You Set Priority?
Set priority when all of the following are true:
• you are intentionally creating a new task;
• its urgency differs meaningfully from the surrounding operation;
• the difference is understandable in product terms;
• and you are not using priority to simulate execution order.
Otherwise, allow priority inheritance to carry the initiating operation's intent.
Useful product language includes:
• “The user is waiting for this refresh.”
• “This prefetch improves a likely next screen.”
• “This cleanup may happen when resources are available.”
Weak reasoning sounds like:
• “High should make it fast.”
• “Background means a background thread.”
• “Low guarantees it runs last.”
15 · TUTORIAL
The Complete Mental Model
Conceptual model:
EXECUTION DIAGRAM
Product urgency
│
▼
Task priority
│
│ scheduling information
▼
Executor considers eligible jobs,
dependencies and runtime conditions
│
▼
Execution resources
Priority can be inherited or explicitly requested.
Dependencies can cause elevation.
Completion order is never guaranteed by priority alone.
16 · TUTORIAL
What to Remember
• Every Swift task carries a priority.
• Priority communicates relative urgency to executors.
• Priority can influence scheduling but does not define execution order.
• The standard priorities are .high, .medium, .low and .background.
• .userInitiated aliases high priority, and .utility aliases low priority.
• Task { } normally inherits the current task's priority.
• An explicit priority replaces the priority that would otherwise be inherited at creation.
• A detached task does not inherit the creator's task priority.
• Priority does not select an actor, executor or dedicated thread.
• Priority does not interrupt synchronous work already executing.
• Use dependencies—not priority—to establish ordering.
• Priority elevation helps prevent high-priority work waiting indefinitely behind a low-priority dependency.
• Prefer inheritance unless new work has a genuinely different product-level urgency.
17 · TUTORIAL
Frequently Asked Questions
Does a high-priority task always run first?
No. Executors can consider priority when scheduling eligible jobs, but creation timing, dependencies, isolation and work already executing all affect progress. Priority is not a deterministic order.
Does high priority make a task faster?
It may help the task receive execution resources sooner when competing with lower-priority eligible work. It does not make its instructions cheaper, improve an algorithm or increase network speed.
Does background priority mean a background thread?
No. It describes low scheduling urgency. Actor isolation and executors determine where jobs are eligible to execute; priority does not select a particular thread.
What priority does Task { } use?
When created from an existing task without an explicit priority, it inherits the current task's priority. When no current task exists, Swift derives an initial priority from the surrounding execution context.
Does Task.detached inherit priority?
No. Detached tasks do not inherit the creator's task priority. Supply an explicit priority when detached work has a clear urgency requirement.
What is priority escalation?
It is runtime support that treats a task with greater urgency when higher-priority work depends on it. For example, awaiting a lower-priority task's value can elevate that task until it completes.
Should I assign a priority to every Task?
No. Inheritance usually preserves the correct urgency for related work. Specify a priority when the newly created task has meaningfully different urgency from the operation creating it.
18 · TUTORIAL
References
19 · TUTORIAL
Continue Learning
Priority inheritance becomes especially useful when one operation creates structured work beneath it. The next article in the learning sequence, What Is a Child Task in Swift?, will explain the exact parent-child contract behind async let and task groups: bounded lifetime, inherited context, cancellation and result flow.
20 · TUTORIAL
Download Xcode Playgrounds
Use Understanding Task Priority.playground to observe inherited and explicit priorities, compare ordinary and detached tasks and model priority escalation without assuming output order. Then use Task Priority Challenges.playground to assign product-level urgency to a launch feature and repair code that incorrectly uses priority as an ordering mechanism.
