How to Pass a Swift Interview: Top Advanced Questions
An interview for an iOS developer position is not just about testing knowledge of UIKit and SwiftUI. At an advanced level, recruiters and technical leads want to ensure you understand how memory works, how class differs from struct in the context of reference counting, how to properly build architecture using protocols, and how to manage asynchronicity. In this article, we will break down key topics that are often encountered in Swift interviews at the Middle and Senior levels.
1. Memory Management and ARC (Automatic Reference Counting)
ARC is one of the most common topics in interviews. You need not only to know the theory but also to be able to explain how to avoid memory leaks.
1.1. How does ARC work and what are strong reference cycles?
ARC automatically counts the number of strong references to a class object. Once the counter reaches zero, the object is deallocated from memory. A problem arises when two objects strongly reference each other — a cycle (retain cycle) is formed.
class Parent { var child: Child?}
class Child { var parent: Parent? // strong reference back}
var parent: Parent? = Parent()var child: Child? = Child()parent?.child = childchild?.parent = parent// Both objects will never be deallocated1.2. How to break the cycle?
Use weak or unowned. weak is an optional reference that automatically becomes nil when the object is deallocated. unowned is non-optional but assumes the object will live at least as long as the referencing object.
class Child { weak var parent: Parent? // weak reference}1.3. How does ARC work with closures?
Closures capture external variables. If a closure is stored as a property of a class and uses self inside it, a cycle occurs. The solution is to use [weak self] or [unowned self].
class NetworkManager { var completionHandler: (() -> Void)? func fetchData() { completionHandler = { [weak self] in guard let self = self else { return } self.processData() } } func processData() { }}2. Closures and escaping vs non-escaping
Understanding the difference between escaping and non-escaping closures is a mandatory item for any advanced position.
2.1. What is an escaping closure?
A closure is called escaping if it can be called after the function returns. By default, all closures are non-escaping — the compiler knows they will be executed within the function body. For asynchronous operations (e.g., network requests), you need to explicitly specify @escaping.
func performAsyncTask(completion: @escaping (String) -> Void) { DispatchQueue.global().async { // Simulating a long task sleep(2) completion("Done") }}2.2. Why are non-escaping closures faster?
The compiler optimizes non-escaping closures — it may not copy them to the heap but store them on the stack. It can also avoid reference counting since the closure's lifetime is limited to the function call.
3. Generics and Protocols with Associated Types (PAT)
Generics are the foundation of Swift. In an interview, you may be asked to write a generic structure or explain how protocols with associated types work.
3.1. Write a generic Queue
struct Queue<T> { private var elements: [T] = [] mutating func enqueue(_ value: T) { elements.append(value) } mutating func dequeue() -> T? { guard !elements.isEmpty else { return nil }