Swift
programming
function delay
code duplication
iOS development

Delaying function in swift

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Delaying work in Swift is common for UI transitions, retries, debouncing, and scheduling follow-up actions. The right tool depends on whether you need a one-time delay, a repeating timer, or structured concurrency that can be cancelled cleanly. In most UIKit and SwiftUI code, the two main answers are DispatchQueue.asyncAfter and Task.sleep.

One-Time Delay with DispatchQueue.asyncAfter

For a simple delayed action, Grand Central Dispatch is the classic option.

swift
1import Foundation
2
3DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
4    print("Ran after two seconds")
5}

Use .main when the delayed work updates UI. Use a background queue when the work is non-UI and CPU- or I/O-bound.

swift
DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 1.0) {
    print("Background work")
}

Modern Structured Concurrency with Task.sleep

If your code already uses async and await, Task.sleep is usually a better fit.

swift
1import Foundation
2
3func delayedMessage() async {
4    try? await Task.sleep(nanoseconds: 2_000_000_000)
5    print("Finished sleeping")
6}
7
8Task {
9    await delayedMessage()
10}

This integrates naturally with Swift concurrency and supports cancellation in a predictable way.

Why Task.sleep Is Often Better in Async Code

With DispatchQueue.asyncAfter, the delayed block is scheduled and detached from the logical async flow. With Task.sleep, the delay is part of the task itself, so structured cancellation and error propagation remain easier to reason about.

Example with cancellation:

swift
1import Foundation
2
3let task = Task {
4    do {
5        try await Task.sleep(nanoseconds: 5_000_000_000)
6        print("This may never print")
7    } catch {
8        print("Task was cancelled")
9    }
10}
11
12task.cancel()

That is much harder to model cleanly if you schedule detached delayed blocks everywhere.

Repeating Delays with Timer

If the action should repeat, use Timer rather than recursively scheduling asyncAfter.

swift
1import Foundation
2
3let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
4    print("Tick")
5}
6
7RunLoop.current.run(until: Date().addingTimeInterval(3.2))
8timer.invalidate()

In app code, remember to invalidate repeating timers when the owning object goes away.

Delaying UI Work

UI updates should happen on the main thread. For example:

swift
1DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
2    // update a label, dismiss a banner, trigger animation, and so on
3    print("UI-safe delayed action")
4}

Even if the delay is simple, using a background queue for UI work is a bug waiting to happen.

Debounce-Like Behavior

Sometimes the real need is not “delay this function” but “only run this after the user stops typing.” That is debouncing, and repeated delayed calls need cancellation or replacement.

Simple dispatch-based pattern:

swift
1import Foundation
2
3var pendingWork: DispatchWorkItem?
4
5func scheduleSearch() {
6    pendingWork?.cancel()
7
8    let work = DispatchWorkItem {
9        print("Run search now")
10    }
11
12    pendingWork = work
13    DispatchQueue.main.asyncAfter(deadline: .now() + 0.4, execute: work)
14}
15
16scheduleSearch()

Without cancellation, every keystroke schedules another delayed call and defeats the purpose.

Avoid Blocking Delays

Do not use sleep() on the main thread just to delay work.

Bad:

swift
// sleep(2)

That blocks the thread and freezes UI. Delays in application code should almost always be scheduled asynchronously.

Choosing the Right Tool

Use DispatchQueue.asyncAfter when:

  • the codebase is not using async and await
  • the delay is simple and fire-and-forget
  • you are scheduling a UI or queue-bound closure

Use Task.sleep when:

  • the surrounding code is already async
  • cancellation matters
  • you want delay to stay inside structured concurrency

Use Timer when:

  • the work repeats on an interval

Common Pitfalls

The main mistake is blocking a thread with sleep() instead of scheduling asynchronous delay. Another is updating UI from a background queue after the delay fires. Developers also often use Timer for one-shot async work when asyncAfter or Task.sleep would be simpler. Finally, delayed work that is not cancellable can easily outlive the screen or object that scheduled it, which causes stale updates and subtle bugs.

Summary

  • Use DispatchQueue.asyncAfter for simple one-time delays.
  • Use Task.sleep in async and await code, especially when cancellation matters.
  • Use Timer for repeating work.
  • Keep UI updates on the main thread.
  • Avoid blocking calls such as sleep() in app code.

Course illustration
Course illustration

All Rights Reserved.