Skip to Content
Course content

131: Common Swift Interview Questions on Concurrency

Click on the "Edit" button in the top corner of the screen to edit your slide content.

If you're prepping for a senior Swift role, you can bet your life that concurrency is going to come up. Interviewers love it because it's where most developers' mental models break. They aren't just looking for you to know the syntax of async/await; they want to see if you understand why we moved away from Grand Central Dispatch (GCD) and how the compiler actually protects your data.

Let's start with a snippet I see all the time. It looks perfectly fine at a glance, but it'll trigger a runtime warning (or a crash) the moment you run it in a real app.

class UserProfileViewModel: ObservableObject {
    @Published var username: String = "Guest"
    let service = NetworkService()

    func updateUsername() {
        Task {
            // Fetching data from a remote API
            let newName = await service.fetchUsername()
            
            // This is where the wheels fall off
            self.username = newName 
        }
    }
}

The Background Thread UI Violation

The bug here is a classic: updating a @Published property—which triggers a UI update—from a background thread. You might think, "But I'm using Task, isn't that handled?" Not necessarily. While Task inherits the actor context of the place it's created, if updateUsername() is called from a non-isolated context or a background queue, that assignment to self.username happens off the main thread. SwiftUI will scream at you, and your app's behavior becomes unpredictable.

Enforcing Main-Thread Execution with @MainActor

In the old days, we would have wrapped that assignment in DispatchQueue.main.async { ... }. That worked, but it was "opt-in" safety—you had to remember to do it every single time. The modern way is to make the safety "opt-out" using @MainActor.

@MainActor
class UserProfileViewModel: ObservableObject {
    @Published var username: String = "Guest"
    let service = NetworkService()

    func updateUsername() {
        Task {
            let newName = await service.fetchUsername()
            // Now the compiler guarantees this happens on the main thread
            self.username = newName 
        }
    }
}

By marking the entire class as @MainActor, you're telling Swift that every property and method in this class must be accessed on the main thread. If you try to call a @MainActor method from a background context, the compiler will force you to use await, making the thread jump explicit and safe.

The "GCD vs. Swift Concurrency" Interview Question

You'll likely be asked: "Why use async/await over GCD?" Don't just say "it's cleaner." Talk about Structured Concurrency. In GCD, when you dispatch a block of code, it's "fire and forget." You lose the relationship between the caller and the called. If the caller is cancelled, the GCD block keeps running in the background, wasting resources (a "leak" of work).

With Swift Concurrency, tasks have a hierarchy. If a parent task is cancelled, that cancellation propagates down to all child tasks automatically. This prevents the "zombie" network requests that used to plague iOS apps for years.

Explaining Actors and Data Races

Another favorite: "What is an Actor and when should you use one?" I usually explain it as a "synchronized class." An actor ensures that only one task can access its mutable state at a time. This eliminates data races—where two threads try to change the same variable simultaneously, leading to memory corruption.

  • Class: Reference type, not thread-safe. You'd need a NSLock or a private serial queue to make it safe.
  • Actor: Reference type, thread-safe by design. The compiler manages the locking mechanism for you.

If the interviewer asks for a use case, suggest a DatabaseManager or an ImageCache. These are shared resources that multiple parts of an app hit at once. Using an actor there ensures that your cache doesn't crash when five different views try to save an image at the exact same millisecond.

Task vs. Task.detached

Watch out for this nuance. A standard Task { ... } inherits the current actor's context and priority. If you're in a @MainActor class, the Task starts on the main actor. Task.detached { ... }, however, creates a completely new top-level task that inherits nothing. Use detached only when you have a heavy computational job (like processing a large image) that absolutely must not block the main thread, even for a fraction of a second.




📋 Practical Task

Implement a Thread-Safe Download Coordinator

You are building a download manager that tracks the status of multiple file downloads. If multiple threads update the status of the same file simultaneously, the app will crash due to a data race.

Your Goal: Create an actor called DownloadCoordinator that manages a dictionary of download statuses. Implement the following:

  1. A private dictionary statuses: [URL: String] to track the status (e.g., "Downloading", "Completed", "Failed").
  2. A function updateStatus(for url: URL, to status: String) that updates the dictionary.
  3. A function getStatus(for url: URL) -> String that returns the current status or "Unknown" if the URL isn't in the dictionary.

Then, write a small piece of code using a Task to simulate updating a status and printing it back. Remember that because DownloadCoordinator is an actor, you must use await when calling its methods from outside.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.