Skip to main content

Swift engineer coding interview questions

12 questions on coding for swift engineer candidates. Expect prompts such as “Reverse a singly linked list in Swift” and “Two Sum using a dictionary in Swift”, each with a worked answer outline and the follow-ups interviewers push on.

Questions

Showing 1 to 12 of 12 coding questions.

As asked

Given the definition of a singly linked list node in Swift, write a function that reverses the list in place and returns the new head. What is the time and space complexity?

Sample answer outline

A strong answer uses three pointers (prev, current, next), iterates once, reversing the next pointer at each node. Time O(n), space O(1). Candidate should handle nil input and single-node input without special cases.

Reference implementation (swift)

Swift
class ListNode {
    var val: Int
    var next: ListNode?
    init(_ val: Int) { self.val = val }
}

func reverseList(_ head: ListNode?) -> ListNode? {
    // implement here
}

Expect these follow-ups

  • How would you detect a cycle in the same list structure?
  • Can you write a recursive version and what are its stack depth risks for a long list?
linked-listalgorithmsswiftdata-structurescoding

As asked

Given an array of integers and a target, return the indices of the two numbers that add up to the target. You may assume exactly one solution exists. Write the solution in Swift using a dictionary and explain why it beats the brute-force approach.

Sample answer outline

A strong answer uses a [Int: Int] dictionary mapping value to index, iterates once, checks if target minus current element exists in the dictionary before inserting. Time O(n), space O(n). Candidate should explain that Swift Dictionary lookup is amortized O(1) and handle the edge case where the complement equals the current element (check before insert).

Reference implementation (swift)

Swift
func twoSum(_ nums: [Int], _ target: Int) -> [Int] {
    var seen = [Int: Int]()
    for (i, num) in nums.enumerated() {
        let complement = target - num
        if let j = seen[complement] {
            return [j, i]
        }
        seen[num] = i
    }
    return []
}

Expect these follow-ups

  • What if the array is sorted? How does that change your approach and complexity?
  • How would you return all pairs that sum to the target, not just the first?
hash-maparraysswiftalgorithmscoding

As asked

Given a string containing only '(', ')', '{', '}', '[', ']', write a Swift function that returns true if the brackets are valid (properly opened and closed in order). What edge cases should you test?

Sample answer outline

A strong answer pushes opening brackets onto a [Character] array (used as a stack), on closing bracket checks if the stack top matches, and returns true only if the stack is empty at the end. Edge cases: empty string (true), single character (false), string that is all opens with no closes, mismatched types like ( ].

Reference implementation (swift)

Swift
func isValid(_ s: String) -> Bool {
    var stack = [Character]()
    let match: [Character: Character] = [")": "(", "}": "{", "]": "["]
    for c in s {
        if "([{".contains(c) {
            stack.append(c)
        } else {
            if stack.isEmpty || stack.last != match[c] { return false }
            stack.removeLast()
        }
    }
    return stack.isEmpty
}

Expect these follow-ups

  • How would you extend this to report the index of the first unmatched bracket?
  • Can you solve it without an explicit stack data structure?
stackstringsalgorithmsswiftcoding

As asked

Implement binary search in Swift on a sorted integer array. Then extend it to find the first and last position of a target value that may appear multiple times. What boundary condition bugs commonly break these variants?

Sample answer outline

A strong answer provides the basic binary search, then two specialized versions: one biased to the left (when mid equals target, move right to left) and one biased right (move left to mid+1). Common bugs: off-by-one in mid calculation (use low + (high - low) / 2 to avoid overflow), using less-than vs less-than-or-equal for the while condition, and not checking that the result actually equals target before returning.

Reference implementation (swift)

Swift
func searchRange(_ nums: [Int], _ target: Int) -> [Int] {
    func findBound(_ leftBias: Bool) -> Int {
        var lo = 0, hi = nums.count - 1, bound = -1
        while lo <= hi {
            let mid = lo + (hi - lo) / 2
            if nums[mid] == target {
                bound = mid
                if leftBias { hi = mid - 1 } else { lo = mid + 1 }
            } else if nums[mid] < target { lo = mid + 1 }
            else { hi = mid - 1 }
        }
        return bound
    }
    return [findBound(true), findBound(false)]
}

Expect these follow-ups

  • How does Swift's built-in Collection.partition(by:) relate to binary search?
  • What is the complexity if the array fits in L1 cache versus when it is larger than L3 cache?
binary-searcharraysalgorithmsswiftcoding

As asked

Implement an LRU cache in Swift that supports get(key) and put(key, value) in O(1) time. Describe your data structure choice and walk me through the eviction logic.

Sample answer outline

A strong answer uses a doubly linked list for order (most recently used at head) plus a dictionary mapping keys to list nodes for O(1) access. On get: move the node to head, return value. On put: if key exists update and move to head; if at capacity remove tail node and its dictionary entry before inserting new head node. Candidate should note Swift has no built-in doubly linked list and write the Node class themselves.

Reference implementation (swift)

Swift
class LRUCache {
    private class Node {
        var key: Int, val: Int
        var prev: Node?, next: Node?
        init(_ k: Int, _ v: Int) { key = k; val = v }
    }
    private var capacity: Int
    private var map = [Int: Node]()
    private var head = Node(0,0), tail = Node(0,0)

    init(_ capacity: Int) {
        self.capacity = capacity
        head.next = tail; tail.prev = head
    }
    // implement get and put
}

Expect these follow-ups

  • How would you make this cache thread-safe for concurrent access from multiple async tasks?
  • What cache eviction policies other than LRU are common, and when would LFU be a better fit?
data-structureshash-maplinked-listswiftcoding

As asked

Write a Swift actor that caches UIImage objects by URL string, downloads missing images with URLSession, and ensures that two simultaneous requests for the same URL only trigger one network fetch. No external libraries.

Sample answer outline

A strong answer uses an actor with a [String: UIImage] cache dictionary and a [String: Task<UIImage, Error>] in-flight tasks dictionary. On request: if cached return immediately; if in-flight await the existing task; otherwise create a new Task, store it, await it, store result in cache and remove from in-flight. Actor isolation ensures the dictionary mutations are serialized. Candidate should handle cancellation and failure cleanup.

Reference implementation (swift)

Swift
actor ImageCache {
    private var cache = [String: UIImage]()
    private var inFlight = [String: Task<UIImage, Error>]()

    func image(for url: String) async throws -> UIImage {
        if let cached = cache[url] { return cached }
        if let task = inFlight[url] { return try await task.value }
        let task = Task<UIImage, Error> {
            // download and decode
            fatalError("implement")
        }
        inFlight[url] = task
        defer { inFlight[url] = nil }
        let img = try await task.value
        cache[url] = img
        return img
    }
}

Expect these follow-ups

  • How would you add a maximum cache size with LRU eviction to this actor?
  • What happens if the caller Task is cancelled while awaiting the shared fetch Task?
actorsconcurrencynetworkingcachingswift

As asked

Given an array of interval pairs [[start, end]] in Swift, merge all overlapping intervals and return the result sorted. For example [[1,3],[2,6],[8,10],[15,18]] returns [[1,6],[8,10],[15,18]]. Walk me through your approach and edge cases.

Sample answer outline

A strong answer sorts by start time, iterates keeping a running merged interval, and extends it when the next interval's start is within the current end. Edge cases: empty input, single interval, all intervals disjoint, all intervals fully overlapping. Time O(n log n) for sort, O(n) merge. Candidate should handle integer tuple representation in Swift and prefer a clean imperative loop over nested conditions.

Reference implementation (swift)

Swift
func merge(_ intervals: [[Int]]) -> [[Int]] {
    guard !intervals.isEmpty else { return [] }
    let sorted = intervals.sorted { $0[0] < $1[0] }
    var result = [sorted[0]]
    for interval in sorted.dropFirst() {
        if interval[0] <= result.last![1] {
            result[result.count - 1][1] = max(result.last![1], interval[1])
        } else {
            result.append(interval)
        }
    }
    return result
}

Expect these follow-ups

  • How would you use this in a calendar app to show free/busy blocks?
  • What changes if intervals can have open or closed endpoints?
arrayssortingalgorithmsswiftcoding

As asked

Without using Combine or any third-party library, write a Swift class that debounces a closure: calling it multiple times in quick succession should only invoke the closure once, after a specified delay since the last call. The delay should be cancellable.

Sample answer outline

A strong answer uses a stored DispatchWorkItem, cancels the previous one on each call, then schedules the new item after the delay on DispatchQueue.main (or an injected queue). Candidate should show the class, a method call(_ action: @escaping () -> Void) that creates, stores, and schedules the work item. Bonus: make it generic or async/await compatible using Task with Task.sleep.

Reference implementation (swift)

Swift
class Debouncer {
    private var workItem: DispatchWorkItem?
    private let queue: DispatchQueue
    private let delay: TimeInterval

    init(delay: TimeInterval, queue: DispatchQueue = .main) {
        self.delay = delay
        self.queue = queue
    }

    func call(_ action: @escaping () -> Void) {
        workItem?.cancel()
        let item = DispatchWorkItem(block: action)
        workItem = item
        queue.asyncAfter(deadline: .now() + delay, execute: item)
    }
}

Expect these follow-ups

  • How does this differ from throttle, and when would you use throttle over debounce in a search field?
  • How would you rewrite this using async/await and Task cancellation instead of DispatchWorkItem?
concurrencygcdalgorithmsswiftcoding

As asked

Given an array of strings, group the anagrams together and return an array of groups. For example ["eat","tea","tan","ate","nat","bat"] returns [["eat","tea","ate"],["tan","nat"],["bat"]]. Write the Swift solution and discuss whether sorting or a character frequency map is the better key.

Sample answer outline

A strong answer uses a [String: [String]] dictionary, keys each word by its sorted characters (O(k log k) per word) or by a 26-element frequency tuple. Sorting is simpler but frequency array is O(k) per key. Total time O(n * k log k) sorting approach, O(n * k) frequency approach. Candidate should note Swift String sorting is Unicode-aware and may need to work with ASCII characters explicitly for the frequency map approach.

Reference implementation (swift)

Swift
func groupAnagrams(_ strs: [String]) -> [[String]] {
    var groups = [String: [String]]()
    for s in strs {
        let key = String(s.sorted())
        groups[key, default: []].append(s)
    }
    return Array(groups.values)
}

Expect these follow-ups

  • How would you handle Unicode strings where characters are multi-scalar?
  • What is the time complexity of using a character frequency array versus character sort as the key?
hash-mapstringsalgorithmsswiftcoding

As asked

Given an array of integers and a window size k, write a Swift function that returns the maximum sum of any contiguous subarray of length k. Explain why the sliding window approach is better than recalculating from scratch each step.

Sample answer outline

A strong answer initializes the first window sum, then slides by subtracting the element leaving the window and adding the new one, updating max each step. Time O(n), space O(1), versus the naive O(n*k). Candidate should handle edge cases: k greater than array length (return 0 or error), empty array, negative numbers do not break the algorithm.

Reference implementation (swift)

Swift
func maxSumSubarray(_ nums: [Int], _ k: Int) -> Int {
    guard nums.count >= k else { return 0 }
    var windowSum = nums.prefix(k).reduce(0, +)
    var maxSum = windowSum
    for i in k..<nums.count {
        windowSum += nums[i] - nums[i - k]
        maxSum = max(maxSum, windowSum)
    }
    return maxSum
}

Expect these follow-ups

  • How would you extend this to find the subarray itself (not just the sum)?
  • What if k varies per call and you need to answer queries efficiently?
sliding-windowarraysalgorithmsswiftcoding

As asked

Given a binary tree node definition in Swift, write a function that returns true if the tree is height-balanced (every node's left and right subtrees differ in height by at most one). What is the time complexity of your solution?

Sample answer outline

A strong answer uses a single DFS that returns -1 on detecting imbalance or the height otherwise, avoiding the naive O(n^2) approach of computing height separately for every node. Time O(n), space O(h) for the call stack where h is tree height. Candidate should handle nil nodes (height 0) and describe why the early-exit on -1 matters for performance.

Reference implementation (swift)

Swift
class TreeNode {
    var val: Int
    var left: TreeNode?
    var right: TreeNode?
    init(_ val: Int) { self.val = val }
}

func isBalanced(_ root: TreeNode?) -> Bool {
    func height(_ node: TreeNode?) -> Int {
        guard let node = node else { return 0 }
        let l = height(node.left)
        if l == -1 { return -1 }
        let r = height(node.right)
        if r == -1 { return -1 }
        if abs(l - r) > 1 { return -1 }
        return max(l, r) + 1
    }
    return height(root) != -1
}

Expect these follow-ups

  • What is the difference between a balanced BST and a complete binary tree?
  • How would you balance an unbalanced BST to get an AVL tree?
treesrecursionalgorithmsswiftcoding

As asked

Write a Swift function that takes an array which may contain integers or other nested arrays at any depth and returns a flat [Int]. The input is represented as an enum. What recursive and iterative approaches exist and what are their tradeoffs?

Sample answer outline

A strong answer defines an enum NestedInt with cases .int(Int) and .list([NestedInt]), writes a recursive function that switches on each element appending integers and recursing into lists. The iterative approach uses a stack for explicit DFS to avoid stack overflow on very deep nesting. Candidate should note Swift's recursion depth is limited by the system stack and that the iterative version is safer for untrusted input.

Reference implementation (swift)

Swift
indirect enum NestedInt {
    case int(Int)
    case list([NestedInt])
}

func flatten(_ nested: NestedInt) -> [Int] {
    switch nested {
    case .int(let v): return [v]
    case .list(let arr): return arr.flatMap { flatten($0) }
    }
}

Expect these follow-ups

  • How would you write this as a lazy sequence to avoid materializing the entire flat array?
  • What changes if the nesting can be infinitely deep (circular reference)?
recursionarraysalgorithmsswiftcoding