Toolmingo
Guides22 min read

50 Swift Interview Questions (With Answers)

Top Swift interview questions with clear answers and code examples — covering optionals, memory management, protocols, concurrency, SwiftUI, and iOS architecture patterns.

Swift interviews test your depth in optionals, value vs reference types, memory management (ARC), protocols, async/await concurrency, and SwiftUI. This guide covers 50 common questions — with concise answers and code examples.

Quick reference

Topic Most asked questions
Basics let/var, type inference, optionals, String interpolation
Optionals Optional binding, guard, nil coalescing, force unwrap
Types Value vs reference, struct vs class vs enum, protocols
Memory ARC, strong/weak/unowned, retain cycles
Closures Capture lists, @escaping, trailing closure, @autoclosure
Concurrency async/await, actors, Task, structured concurrency
Protocols Protocol-oriented programming, Codable, Hashable
Error handling throws, do-catch, Result, try? / try!
SwiftUI @State, @Binding, @ObservedObject, @EnvironmentObject
Advanced Property wrappers, result builders, Sendable, @MainActor

Basics

1. What is the difference between let and var?

let name = "Alice"   // constant — immutable
var count = 0        // variable — mutable
count = 1            // OK
name = "Bob"         // compile error

let enforces immutability at the binding level. For value types (struct, enum), the entire value is frozen. For reference types (class), the reference is frozen but the object's properties can still change (unless also declared let).


2. How does Swift's type inference work?

let x = 42           // inferred as Int
let y = 3.14         // inferred as Double
let flag = true      // inferred as Bool
let name = "Swift"   // inferred as String
let nums = [1, 2, 3] // inferred as [Int]

The compiler determines the type from the initial value. Explicit annotation is required when inference is ambiguous or you want a different type:

let score: Float = 3.14   // explicitly Float, not Double
let empty: [String] = []  // can't infer from empty literal

3. What are optionals and why does Swift have them?

var name: String? = "Alice"  // can hold String or nil
name = nil                   // valid

var required: String = "Bob" // can NEVER be nil — compile error if assigned nil

Optionals make the absence of a value explicit at the type level, eliminating the null pointer exceptions common in ObjC and Java. The compiler forces you to handle nil before using the value.


4. What are the ways to unwrap an optional?

var value: String? = "hello"

// 1. Optional binding (safe)
if let v = value {
    print(v)
}

// 2. Guard (safe — exits scope on nil)
guard let v = value else { return }
print(v)

// 3. Nil coalescing (provides default)
let result = value ?? "default"

// 4. Optional chaining (propagates nil)
let count = value?.count   // Int? — nil if value is nil

// 5. Force unwrap (unsafe — crashes if nil)
let forced = value!

// 6. Implicit unwrapping (use sparingly)
var implicit: String! = "hello"
print(implicit)   // unwrapped automatically
Method Safe? Use when
if let Yes Need conditional branch
guard let Yes Want to exit on nil
?? Yes Have a sensible default
?. Yes Chaining optional access
! No 100% certain it's non-nil
String! Risky IBOutlets, deferred init

5. What is guard and when do you use it over if let?

func process(name: String?) {
    guard let name = name else {
        print("no name")
        return   // must exit scope
    }
    // name is non-optional from here on
    print("Hello, \(name)")
}

guard is preferred for early exit / precondition checking at the top of a function. The unwrapped binding stays in scope for the rest of the function. if let is for branching where both paths are valid.


6. What is the nil coalescing operator ???

let username: String? = nil
let display = username ?? "Anonymous"  // "Anonymous"

// Chains
let a: Int? = nil
let b: Int? = nil
let c: Int? = 42
let result = a ?? b ?? c ?? 0   // 42

Returns the left side if non-nil, otherwise evaluates and returns the right side. Short-circuits — right side is not evaluated if left is non-nil.


Value Types vs Reference Types

7. What is the difference between struct and class in Swift?

Feature struct class
Type Value Reference
Memory Stack (usually) Heap
Inheritance No Yes
ARC No Yes
Mutability Needs mutating Methods can mutate freely
Copying Deep copy Shallow (shared reference)
deinit No Yes
Memberwise init Auto-generated Not generated
struct Point { var x: Int; var y: Int }
var a = Point(x: 0, y: 0)
var b = a         // copy
b.x = 10
print(a.x)        // 0 — a is unchanged

class Node { var value: Int; init(_ v: Int) { value = v } }
var n1 = Node(1)
var n2 = n1       // same reference
n2.value = 99
print(n1.value)   // 99 — both point to same object

Use struct for data models, coordinates, configurations. Use class for identity-based objects, shared state, UIKit components.


8. What is Copy-on-Write (CoW)?

Swift's standard collections (Array, Dictionary, Set, String) use lazy copying: the buffer is shared until a mutation occurs.

var a = [1, 2, 3]
var b = a         // same buffer, no copy yet
b.append(4)       // copy triggered here — a still [1,2,3]

CoW gives value semantics without performance penalty for read-only copies.


9. When should you use enum vs struct vs class?

// enum: finite set of cases, often with associated values
enum Result<T> {
    case success(T)
    case failure(Error)
}

// struct: data model, no identity needed
struct User { let id: UUID; var name: String }

// class: shared mutable state, reference identity matters
class NetworkManager {
    static let shared = NetworkManager()
    private init() {}
}

Swift enums are powerful — they can have methods, computed properties, and associated values.


Protocols

10. What is protocol-oriented programming (POP)?

POP is Swift's answer to the limitations of class inheritance. Protocols define capabilities; protocol extensions provide default implementations.

protocol Drawable {
    func draw()
}

extension Drawable {
    func draw() { print("default draw") }     // default implementation
    func highlight() { print("highlight") }   // free behaviour for all conformers
}

struct Circle: Drawable { }
struct Square: Drawable {
    func draw() { print("drawing square") }   // overrides default
}

Structs can conform to multiple protocols, enabling composition over inheritance.


11. What is Codable and how does it work?

Codable is a type alias for Encodable & Decodable. The compiler auto-synthesises conformance when all properties are themselves Codable.

struct User: Codable {
    let id: Int
    let name: String
    let email: String
}

// Decode from JSON
let json = """{"id":1,"name":"Alice","email":"a@b.com"}""".data(using: .utf8)!
let user = try JSONDecoder().decode(User.self, from: json)

// Encode to JSON
let data = try JSONEncoder().encode(user)

// Custom keys
struct Product: Codable {
    let productName: String
    enum CodingKeys: String, CodingKey {
        case productName = "product_name"   // snake_case ↔ camelCase
    }
}

12. What is the Hashable protocol?

struct Point: Hashable {
    let x: Int
    let y: Int
    // Hashable auto-synthesised when all stored properties are Hashable
}

let points: Set<Point> = [Point(x: 0, y: 0), Point(x: 1, y: 2)]
let dict: [Point: String] = [Point(x: 0, y: 0): "origin"]

Types conforming to Hashable can be used as Set elements or Dictionary keys. Auto-synthesis works when all stored properties are Hashable.


13. What is Equatable?

struct Money: Equatable {
    let amount: Decimal
    let currency: String
    // auto-synthesised == when all stored properties are Equatable
}

let a = Money(amount: 10, currency: "EUR")
let b = Money(amount: 10, currency: "EUR")
print(a == b)   // true

Custom implementation needed when you want to ignore some properties:

struct User: Equatable {
    let id: UUID
    var lastSeen: Date
    static func == (lhs: User, rhs: User) -> Bool { lhs.id == rhs.id }
}

Closures & Functions

14. What is a closure in Swift?

A closure is a self-contained block of code that captures values from its surrounding scope.

let greet = { (name: String) -> String in
    return "Hello, \(name)!"
}

// Shorthand forms
let double: (Int) -> Int = { $0 * 2 }
let sum: (Int, Int) -> Int = { $0 + $1 }

// Trailing closure syntax
[1, 2, 3].map { $0 * 10 }

15. What is @escaping and when is it needed?

var completions: [() -> Void] = []

func register(completion: @escaping () -> Void) {
    completions.append(completion)  // outlives the function call → @escaping
}

// Non-escaping (default): closure called before function returns
func perform(action: () -> Void) {
    action()  // called synchronously — not @escaping
}

@escaping tells the compiler the closure outlives the function, enabling the compiler to skip certain optimisations and require explicit self capture.


16. What is a capture list and why is it important?

class ViewModel {
    var count = 0
    
    func startTimer() {
        Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in
            guard let self else { return }
            self.count += 1
        }
    }
}

Without [weak self], the closure holds a strong reference to self, creating a retain cycle — the ViewModel is never released.

Capture Behaviour
[weak self] Optional reference — self can become nil
[unowned self] Non-optional — crashes if self is deallocated
Default (no list) Strong reference — can cause retain cycles

Use [weak self] for most async closures. Use [unowned self] only when you're certain the object outlives the closure.


17. What is @autoclosure?

func assert(_ condition: @autoclosure () -> Bool, _ message: String) {
    if !condition() { print(message) }
}

assert(x > 0, "x must be positive")  // x > 0 wrapped in closure automatically
// Caller writes expression, not { x > 0 }

@autoclosure wraps the argument expression in a closure automatically, enabling lazy evaluation and cleaner call sites.


Memory Management

18. How does ARC (Automatic Reference Counting) work?

ARC tracks the reference count of each class instance. When count drops to zero, the instance is deallocated.

class Dog {
    let name: String
    init(_ name: String) { self.name = name; print("\(name) init") }
    deinit { print("\(name) deinit") }
}

var d1: Dog? = Dog("Rex")   // count = 1
var d2 = d1                 // count = 2
d1 = nil                    // count = 1
d2 = nil                    // count = 0 → deinit called

ARC is compile-time, not garbage collection — no pause, deterministic cleanup.


19. What is a retain cycle and how do you break it?

class Owner {
    var pet: Pet?
}

class Pet {
    var owner: Owner?  // strong → retain cycle!
}

// Fix: use weak
class Pet {
    weak var owner: Owner?   // won't keep Owner alive
}

A retain cycle occurs when two objects hold strong references to each other, preventing deallocation. Fix with weak or unowned.


20. What is the difference between weak and unowned?

weak unowned
Optional Yes (T?) No (T)
Becomes nil Yes, when object deallocated No — crashes if accessed after dealloc
Use when Object can become nil during lifetime Object guaranteed to outlive the reference
// weak: delegate pattern
protocol Delegate: AnyObject { }
class View {
    weak var delegate: Delegate?  // View doesn't own the delegate
}

// unowned: child doesn't outlive parent
class Country {
    var currency: Currency!
}
class Currency {
    unowned let country: Country   // always exists
    init(_ country: Country) { self.country = country }
}

Error Handling

21. How does Swift's error handling work?

enum NetworkError: Error {
    case invalidURL
    case timeout
    case serverError(Int)
}

func fetchData(from url: String) throws -> Data {
    guard url.hasPrefix("https") else { throw NetworkError.invalidURL }
    // ...
    return Data()
}

// Caller must handle
do {
    let data = try fetchData(from: "https://api.example.com")
} catch NetworkError.invalidURL {
    print("Bad URL")
} catch NetworkError.serverError(let code) {
    print("Server error: \(code)")
} catch {
    print("Unknown: \(error)")
}

// Alternatives
let data = try? fetchData(from: url)    // returns Optional<Data>
let data2 = try! fetchData(from: url)  // crashes if throws

22. What is the Result type?

func fetchUser(id: Int, completion: @escaping (Result<User, NetworkError>) -> Void) {
    // ...
    completion(.success(User(id: id, name: "Alice")))
    // or
    completion(.failure(.timeout))
}

fetchUser(id: 1) { result in
    switch result {
    case .success(let user): print(user.name)
    case .failure(let error): print(error)
    }
}

Result<Success, Failure> is useful for callback-based async APIs, making success/failure explicit without try-catch.


Concurrency

23. What is async/await in Swift?

func fetchUser(id: Int) async throws -> User {
    let url = URL(string: "https://api.example.com/users/\(id)")!
    let (data, _) = try await URLSession.shared.data(from: url)
    return try JSONDecoder().decode(User.self, from: data)
}

// Calling async code
Task {
    do {
        let user = try await fetchUser(id: 1)
        print(user.name)
    } catch {
        print(error)
    }
}

async marks a function that can suspend. await suspends execution until the async operation completes without blocking the thread.


24. What is a Task in Swift concurrency?

// Unstructured task — runs concurrently
let task = Task {
    let result = await fetchData()
    return result
}
let value = await task.value

// Detached task — no actor inheritance
Task.detached {
    // runs without inheriting actor context
}

// Task cancellation
task.cancel()

// Checking cancellation inside task
Task {
    try Task.checkCancellation()
    // or
    guard !Task.isCancelled else { return }
}

25. What is structured concurrency and async let?

// Sequential — one after the other
let users = try await fetchUsers()
let posts = try await fetchPosts()

// Concurrent — both start immediately
async let users = fetchUsers()
async let posts = fetchPosts()
let (u, p) = try await (users, posts)  // waits for both

Structured concurrency ensures child tasks are always awaited before the parent scope exits — no fire-and-forget leaks.


26. What is an actor in Swift?

actor BankAccount {
    private var balance: Double = 0
    
    func deposit(_ amount: Double) {
        balance += amount
    }
    
    func withdraw(_ amount: Double) throws {
        guard balance >= amount else { throw AccountError.insufficientFunds }
        balance -= amount
    }
    
    var currentBalance: Double { balance }
}

let account = BankAccount()
await account.deposit(100)
let bal = await account.currentBalance  // always accessed via await

Actors provide data race protection — only one task can access the actor's mutable state at a time. await at call sites signals potential suspension.


27. What is @MainActor?

@MainActor
class ViewModel: ObservableObject {
    @Published var items: [Item] = []
    
    func load() async {
        let data = await fetchFromNetwork()  // runs on background
        items = data                          // back on main — safe UI update
    }
}

// Single function on main
@MainActor
func updateUI() {
    label.text = "Done"
}

@MainActor guarantees code runs on the main thread. SwiftUI views are implicitly @MainActor.


SwiftUI

28. What is @State in SwiftUI?

struct CounterView: View {
    @State private var count = 0   // owns the truth
    
    var body: some View {
        VStack {
            Text("Count: \(count)")
            Button("Increment") { count += 1 }
        }
    }
}

@State is owned by the view. SwiftUI manages storage and triggers re-renders on change. Always private.


29. What is @Binding?

struct Toggle: View {
    @Binding var isOn: Bool   // reference to external state
    var body: some View {
        Button(isOn ? "ON" : "OFF") { isOn.toggle() }
    }
}

struct Parent: View {
    @State private var enabled = false
    var body: some View {
        Toggle(isOn: $enabled)  // $ passes Binding
    }
}

@Binding creates a two-way connection to state owned elsewhere. Changes propagate in both directions.


30. What is @ObservedObject vs @StateObject?

class CartViewModel: ObservableObject {
    @Published var items: [Item] = []
    func add(_ item: Item) { items.append(item) }
}

// @StateObject: view owns the object — created once
struct CartView: View {
    @StateObject private var vm = CartViewModel()
    var body: some View { /* ... */ }
}

// @ObservedObject: object passed in — not owned by this view
struct CartItemsView: View {
    @ObservedObject var vm: CartViewModel
    var body: some View { /* ... */ }
}
Property wrapper Who owns it Re-created on re-render?
@StateObject This view No — persists
@ObservedObject Parent / injected Yes — risky if created inline

31. What is @EnvironmentObject?

class AuthState: ObservableObject {
    @Published var isLoggedIn = false
}

@main
struct MyApp: App {
    @StateObject var auth = AuthState()
    var body: some Scene {
        WindowGroup {
            ContentView().environmentObject(auth)  // inject at root
        }
    }
}

struct ProfileView: View {
    @EnvironmentObject var auth: AuthState   // read from environment
    var body: some View {
        Text(auth.isLoggedIn ? "Logged in" : "Guest")
    }
}

@EnvironmentObject provides a dependency injection mechanism — pass data deep into the view hierarchy without explicit parameter passing. Crashes at runtime if not injected.


32. What is the @Published property wrapper?

class ViewModel: ObservableObject {
    @Published var name: String = ""   // emits change before value is set
    @Published var isLoading = false
}

@Published automatically emits objectWillChange through ObservableObject when the value is about to change, triggering SwiftUI re-renders.


Generics

33. How do generics work in Swift?

func swap<T>(_ a: inout T, _ b: inout T) {
    let temp = a; a = b; b = temp
}

struct Stack<Element> {
    private var items: [Element] = []
    mutating func push(_ item: Element) { items.append(item) }
    mutating func pop() -> Element? { items.popLast() }
}

var stack = Stack<Int>()
stack.push(1)
stack.push(2)

Generics enable type-safe reusable code without sacrificing performance (monomorphisation at compile time).


34. What are generic constraints?

// Constraint: T must be Comparable
func max<T: Comparable>(_ a: T, _ b: T) -> T {
    return a > b ? a : b
}

// Multiple constraints
func merge<T: Hashable & Codable>(_ a: [T], _ b: [T]) -> Set<T> {
    return Set(a + b)
}

// Where clause
func allEqual<T: Equatable>(_ array: [T]) -> Bool where T: Hashable {
    return Set(array).count <= 1
}

35. What is some vs any in Swift?

protocol Shape {
    func area() -> Double
}

// some: opaque type — concrete type fixed at compile time, hidden from caller
func makeCircle() -> some Shape { Circle(radius: 5) }

// any: existential — type-erased, heterogeneous collections
func draw(_ shapes: [any Shape]) {
    shapes.forEach { print($0.area()) }
}

// In SwiftUI: body is 'some View'
var body: some View { Text("Hello") }

some is more performant (no box allocation). any enables heterogeneous collections at the cost of indirection. Swift 5.7+ requires any keyword for existentials.


Collections

36. What is the difference between map, filter, reduce, and compactMap?

let numbers = [1, 2, 3, 4, 5]

let doubled = numbers.map { $0 * 2 }           // [2, 4, 6, 8, 10]
let evens = numbers.filter { $0.isMultiple(of: 2) } // [2, 4]
let sum = numbers.reduce(0) { $0 + $1 }         // 15 (or reduce(0, +))

let strings = ["1", "two", "3", "four"]
let ints = strings.compactMap { Int($0) }        // [1, 3] — removes nils

// flatMap: flatten nested collections
let nested = [[1,2],[3,4]]
let flat = nested.flatMap { $0 }                 // [1, 2, 3, 4]

37. What are lazy sequences?

let expensive = (1...1_000_000)
    .lazy
    .filter { $0.isMultiple(of: 3) }
    .map { $0 * $0 }
    .prefix(5)

// Only computes 5 results, not all 1,000,000
print(Array(expensive))   // [9, 36, 81, 144, 225]

.lazy defers evaluation until elements are consumed. Avoids creating intermediate arrays for large sequences.


Advanced Patterns

38. What is a property wrapper?

@propertyWrapper
struct Clamped<T: Comparable> {
    private var value: T
    let range: ClosedRange<T>
    
    var wrappedValue: T {
        get { value }
        set { value = min(max(newValue, range.lowerBound), range.upperBound) }
    }
    
    init(wrappedValue: T, _ range: ClosedRange<T>) {
        self.range = range
        self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
    }
}

struct Settings {
    @Clamped(0...100) var volume: Int = 50
}

var s = Settings()
s.volume = 150    // clamped to 100
print(s.volume)   // 100

Property wrappers add reusable behaviour around stored properties. Built-in examples: @Published, @State, @AppStorage, @UserDefault.


39. What is a result builder (@resultBuilder)?

@resultBuilder
struct HTMLBuilder {
    static func buildBlock(_ components: String...) -> String {
        components.joined(separator: "\n")
    }
}

@HTMLBuilder
func page() -> String {
    "<html>"
    "<body>Hello</body>"
    "</html>"
}

Result builders power SwiftUI's @ViewBuilder — the DSL that lets you write VStack { Text("A"); Text("B") } without array literals.


40. What is @discardableResult?

@discardableResult
func save() -> Bool {
    // save logic
    return true
}

save()            // no warning about unused return value
let ok = save()   // still works with return value

Without the attribute, ignoring a function's return value produces a warning.


iOS Architecture

41. What is MVVM in iOS development?

// Model
struct Article: Codable { let id: Int; let title: String; let body: String }

// ViewModel — presentation logic, no UIKit/SwiftUI imports needed
@MainActor
class ArticleViewModel: ObservableObject {
    @Published var articles: [Article] = []
    @Published var isLoading = false
    
    private let service: ArticleService
    init(service: ArticleService = .live) { self.service = service }
    
    func load() async {
        isLoading = true
        defer { isLoading = false }
        articles = (try? await service.fetchArticles()) ?? []
    }
}

// View — purely declarative
struct ArticleListView: View {
    @StateObject var vm = ArticleViewModel()
    var body: some View {
        List(vm.articles, id: \.id) { Text($0.title) }
            .task { await vm.load() }
    }
}

42. What is dependency injection in Swift?

protocol HTTPClient {
    func fetch(_ url: URL) async throws -> Data
}

// Live implementation
struct URLSessionClient: HTTPClient {
    func fetch(_ url: URL) async throws -> Data {
        let (data, _) = try await URLSession.shared.data(from: url)
        return data
    }
}

// Mock for testing
struct MockHTTPClient: HTTPClient {
    var stubbedData: Data = Data()
    func fetch(_ url: URL) async throws -> Data { stubbedData }
}

class DataService {
    private let client: HTTPClient
    init(client: HTTPClient = URLSessionClient()) { self.client = client }
}

Dependency injection via protocols enables easy test mocking without subclassing or singletons.


43. What is the Coordinator pattern?

protocol Coordinator: AnyObject {
    var childCoordinators: [Coordinator] { get set }
    func start()
}

class AppCoordinator: Coordinator {
    var childCoordinators: [Coordinator] = []
    private let window: UIWindow
    
    init(window: UIWindow) { self.window = window }
    
    func start() {
        let nav = UINavigationController()
        window.rootViewController = nav
        let child = HomeCoordinator(nav: nav)
        childCoordinators.append(child)
        child.start()
    }
}

The Coordinator pattern removes navigation logic from ViewControllers, keeping them focused on their view. Each coordinator owns its flow.


Testing

44. How do you write unit tests in Swift?

import XCTest

class CalculatorTests: XCTestCase {
    var sut: Calculator!   // System Under Test
    
    override func setUp() {
        sut = Calculator()
    }
    
    override func tearDown() {
        sut = nil
    }
    
    func test_add_twoPositiveNumbers_returnsSum() {
        let result = sut.add(2, 3)
        XCTAssertEqual(result, 5)
    }
    
    func test_divide_byZero_throwsError() {
        XCTAssertThrowsError(try sut.divide(10, by: 0)) { error in
            XCTAssertEqual(error as? CalcError, .divisionByZero)
        }
    }
}

45. How do you test async code in Swift?

final class UserServiceTests: XCTestCase {
    func test_fetchUser_returnsExpectedName() async throws {
        let mock = MockHTTPClient(stubbedData: testUserJSON)
        let service = UserService(client: mock)
        
        let user = try await service.fetchUser(id: 1)
        
        XCTAssertEqual(user.name, "Alice")
    }
    
    func test_fetchUser_onNetworkError_throws() async {
        let mock = FailingHTTPClient()
        let service = UserService(client: mock)
        
        await XCTAssertThrowsErrorAsync(try await service.fetchUser(id: 1))
    }
}

Use async throws test methods — XCTest supports async natively from Swift 5.5+.


Common Pitfalls

46. What are common memory leak patterns in Swift?

// Pattern 1: Strong self in closure
class Timer {
    var onTick: (() -> Void)?
    func start() { /* calls onTick periodically */ }
}

class Screen {
    let timer = Timer()
    func setup() {
        timer.onTick = { [weak self] in  // MUST be weak
            self?.update()
        }
    }
}

// Pattern 2: Delegate without weak
protocol ViewDelegate: AnyObject { }
class SomeView {
    weak var delegate: ViewDelegate?   // MUST be weak
}

// Pattern 3: NotificationCenter without removeObserver
// In deinit: NotificationCenter.default.removeObserver(self)

47. What is the difference between == and ===?

struct Point: Equatable { var x, y: Int }
class Box { var value: Int; init(_ v: Int) { value = v } }

let p1 = Point(x: 1, y: 1)
let p2 = Point(x: 1, y: 1)
p1 == p2   // true — same value (Equatable)

let b1 = Box(1)
let b2 = Box(1)
let b3 = b1

b1 == b2   // true if Equatable implemented; false if default
b1 === b2  // false — different instances
b1 === b3  // true — same instance (identity)

== checks value equality (Equatable). === checks reference identity (class instances only).


48. Anti-patterns to avoid in Swift

Anti-pattern Problem Fix
Force unwrap ! everywhere Crashes on nil Optional binding / guard
[unowned self] carelessly Crash if object deallocated Use [weak self] unless 100% certain
Strong delegate Retain cycle weak var delegate: Protocol?
Massive ViewController Untestable, hard to maintain MVVM, Coordinator, extract logic
Sync code on main thread Freezes UI Task.detached or DispatchQueue.global()
UserDefaults for complex data Brittle, no migration Core Data, SQLite, or file storage
Singleton abuse Hidden dependencies Dependency injection
Ignoring Sendable warnings Data races in concurrency Mark types Sendable or use actors

Comparison Tables

49. Swift vs Kotlin

Feature Swift Kotlin
Null safety Optional<T> (String?) Nullable types (String?)
Closures { param in body } { param -> body }
Coroutines async/await + actors suspend + coroutines
Data classes Structs with memberwise init data class
Sealed types enum with associated values sealed class
Extension functions Yes (extension Type {}) Yes (fun Type.foo())
Property wrappers @propertyWrapper None (but by delegation)
Compile target Darwin (Apple platforms) JVM, Android, KMP
UI framework SwiftUI / UIKit Jetpack Compose / XML

50. Swift vs Objective-C

Feature Swift Objective-C
Optional safety Type-safe Optional<T> Any pointer can be nil (runtime crash)
Syntax Concise, modern Verbose, C-based
Closures First-class Blocks (complex syntax)
Generics Full support Limited (lightweight generics)
Enums Powerful with associated values Basic C enums
Structs First-class value types Rarely used (C structs)
Error handling throws/do-catch NSError out-parameter
Interop Full ObjC interop N/A
Performance Comparable or faster Comparable
Dynamic dispatch Opt-in (@objc, dynamic) Default

FAQ

Q: Should I use SwiftUI or UIKit for new iOS projects? SwiftUI for new projects targeting iOS 16+. It's Apple's direction, offers better concurrency integration, and less boilerplate. Use UIKit when you need fine-grained control, target older OS versions, or wrap existing UIKit components.

Q: What is the difference between didSet and willSet? willSet is called before the value changes (receives newValue). didSet is called after (receives oldValue). Use didSet for triggering side effects after a property changes.

var temperature: Double = 0 {
    willSet { print("About to change to \(newValue)") }
    didSet  { print("Changed from \(oldValue)") }
}

Q: What is @objc and when do you need it? @objc exposes Swift declarations to Objective-C runtime. Required for: selectors (#selector), delegates using @objc optional protocol methods, and integration with ObjC frameworks.

Q: What is Sendable in Swift concurrency? Sendable marks types that are safe to pass across concurrency boundaries (between tasks/actors). Value types are implicitly Sendable. Classes must be manually marked (final class Foo: Sendable) and must have only immutable or thread-safe properties.

Q: What is the difference between async let and TaskGroup? async let is for fixed number of concurrent operations known at compile time. TaskGroup handles dynamic fan-out (unknown number of tasks at runtime, e.g., processing an array of items concurrently).

Q: How do you prevent a retain cycle with @escaping closures? Use a capture list [weak self]. Always guard the optional: guard let self else { return } (or guard let strongSelf = self else { return }). Never use [unowned self] with escaping closures unless the object is guaranteed to outlive all calls.

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools