December Code

Swift closures interview questions, with answers

Closures are everywhere in Swift: completion handlers, SwiftUI view builders, sort orders and collection transformations all take them. Interviewers ask about closures to check that a candidate can read the compact syntax and knows how closures capture the variables around them, which is where memory leaks and surprising values come from.

The answers below cover closure syntax, trailing closures and shorthand arguments, capturing, passing named functions, functional collection methods and nested functions, with code compiled with swiftc 6.4. Then take the free Swift diagnostic — ten questions across every Swift topic in the bank — to see which of these you can explain but not yet predict.

The questions, with answers

  1. 1.What is a closure in Swift?

    In short: A self-contained block of code that can be stored and passed around like a value and that captures variables from its context.

    A closure expression is written in braces: the parameters and return type come first, then the keyword in, then the body, and a single-expression body returns its value implicitly. Closures have function types, such as (Int, Int) -> Int, so they can be assigned to variables, passed as arguments and returned from functions. Global and nested functions are special cases of closures with names. Swift infers types from context, so closures passed to functions such as map rarely need their parameter types written out.

    let add = { (a: Int, b: Int) in
        a + b
    }
    print(add(2, 3))
    // 5
  2. 2.What are trailing closures and shorthand argument names in Swift?

    In short: A closure passed as the last argument can follow the call's parentheses, and $0, $1 name its parameters without declaring them.

    When a function's last parameter is a closure, the closure can be written after the parentheses, and if it is the only argument the parentheses can be dropped, which is why n.filter { ... } reads naturally. Inside a closure, Swift provides shorthand names for the parameters, $0 for the first and $1 for the second, so the parameter list and in can be omitted. Several trailing closures can follow one call, each labelled after the first. Short names suit one-line closures; longer ones read better with named parameters.

    let n = [1, 2, 3, 4]
    let big = n.filter { $0 > 2 }
    print(big)
    // [3, 4]
  3. 3.How do Swift closures capture values?

    In short: A closure keeps references to the variables it uses from the surrounding scope, so it can read and change them even after that scope ends.

    A closure captures the variables it refers to, not copies of their values. The captured variables stay alive as long as the closure does, so counter below returns a closure that keeps incrementing its own n long after counter has returned, and each call to counter creates a separate n. Because captures are by reference, the closure sees later changes and its own changes are visible outside. A capture list, written in brackets before in, copies values at creation instead, and is also where weak or unowned captures are declared.

    func counter() -> () -> Int {
        var n = 0
        return {
            n += 1
            return n
        }
    }
    let next = counter()
    _ = next()
    print(next())
    // 2
  4. 4.Can you pass a named function where Swift expects a closure?

    In short: Yes: a function whose parameter and return types match the closure type can be passed by name, as in map(square).

    Functions and closures share the same function types in Swift, so anywhere a closure of type (Int) -> Int is expected, a function with that signature can be passed by writing its name without parentheses. map(square) below applies square to every element exactly as a closure would. Initializers and methods can be passed the same way, as in map(String.init). Passing a named function keeps long logic out of the call site and lets it be reused and tested on its own, while short one-off logic reads better as an inline closure.

    func square(_ x: Int) -> Int {
        x * x
    }
    print([1, 2, 3].map(square))
    // [1, 4, 9]
  5. 5.How does reduce work with closures in Swift?

    In short: reduce starts from an initial value and combines it with each element using a closure or an operator, returning one result.

    reduce(initial, combine) walks the collection with an accumulator: it passes the running result and the next element to the closure and uses the return value as the new running result. Because operators are functions in Swift, an operator such as + can be passed directly, as below, instead of { $0 + $1 }. reduce(into:) is a variant for building collections, where the closure mutates the accumulator in place instead of returning a new one. Together with map and filter, reduce replaces most loops that transform or summarise data.

    let prices = [40, 25, 10]
    let total = prices.reduce(0, +)
    print(total)
    // 75
  6. 6.What is the difference between a nested function and a closure in Swift?

    In short: A nested function is a named closure declared inside another function, and it captures that function's variables the same way.

    Swift treats functions as closures with names. A function declared inside another function can use and change the outer function's local variables, exactly like a closure expression, which is why bump below updates x. Nested functions suit helper logic that is too long for an inline closure or needs to call itself recursively, while keeping it private to the outer function. They can also be returned or passed as values, since they have ordinary function types.

    func outer() -> Int {
        var x = 1
        func bump() {
            x += 10
        }
        bump()
        return x
    }
    print(outer())
    // 11

How the diagnostic asks it

One question from the Swift bank, exactly as a sitting would show it. The bank has 3 on closures and 30 across Swift.

Closures · mediumSWIFT-010

What does this Swift code print?

var x = 1
let a = { print(x) }
let b = { [x] in print(x) }
x = 2
a()
b()
  1. 12, then 1correct
  2. 21, then 1
  3. 32, then 2
  4. 41, then 2

Swift closures capture variables by reference: a keeps a reference to x and reads its current value when called, which is 2 by then. A capture list, [x] in, instead copies the value at the moment the closure is created, so b holds 1 regardless of later changes. The output is 2, then 1. 1, then 1 assumes every closure copies its captures. 2, then 2 ignores the capture list. 1, then 2 swaps the behaviours. Capture lists are also where [weak self] goes to avoid reference cycles.

Measure it

Reading answers tells you what’s true. A diagnostic tells you what you get wrong.

10 Swift questions across its topics, easy to hard, about fifteen minutes. You get a readiness figure with the arithmetic shown, the topics you missed named, and a practice set sized for today. Free: 1 diagnostic a month and 15 problems a day. No card.

What the readiness test measures · how the score is computed

By Harshit · updated