December Code

Swift structs and classes interview questions, with answers

Swift leans on value types far more than most languages: String, Array and Dictionary are structs, and Apple recommends structs for most models. Interviews test whether a candidate understands what that means for copying, mutation and identity, and where classes still fit.

The answers below cover value and reference semantics, mutating methods, identity, inout parameters, class initializers, computed properties and property observers, 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 the difference between a struct and a class in Swift?

    In short: A struct is a value type copied on assignment; a class is a reference type, so variables share one instance.

    Assigning or passing a struct copies its value, so each variable has an independent copy, and changing one leaves the other alone. Assigning a class instance copies only the reference, so both names point to the same object and see each other's changes, as r1 does below. Classes also support inheritance, deinitializers and identity comparison, while structs get a memberwise initializer for free and are safer to share between threads. Apple's guidance is to start with a struct and use a class when shared, mutable identity is actually needed.

    struct Tag {
        var n = 0
    }
    final class Ref {
        var n = 0
    }
    var t1 = Tag()
    var t2 = t1
    t2.n = 1
    let r1 = Ref()
    let r2 = r1
    r2.n = 1
    print(t1.n, r1.n)
    // 0 1
  2. 2.What does the mutating keyword do in Swift?

    In short: It marks a struct or enum method that changes self, which is only allowed when the value is stored in a var.

    Methods on value types cannot change the value's properties by default, because self is treated as a constant inside them. Marking a method mutating allows it to modify properties, or even assign a whole new value to self, and the caller's stored value is updated when the method returns. Classes never need the keyword: their methods can always change properties, because the method works on the shared instance rather than on a value.

    struct Steps {
        var n = 0
        mutating func add() {
            n += 1
        }
    }
    var s = Steps()
    s.add()
    print(s.n)
    // 1
  3. 3.What is the difference between == and === in Swift?

    In short: == compares values using Equatable; === checks whether two class references point to the same instance.

    == asks whether two values are equal, as defined by the type's Equatable conformance, and it works for structs, enums and classes that conform. === is the identity operator and exists only for class instances: it is true when both references point to exactly the same object. In the code, b is another reference to the object in a, so a === b is true, while c is a separate object, so a === c is false even though the two are indistinguishable. Value types have no identity, so === does not apply to them.

    final class User {}
    let a = User()
    let b = a
    let c = User()
    print(a === b, a === c)
    // true false
  4. 4.What are inout parameters in Swift?

    In short: An inout parameter lets a function modify the caller's variable; the caller passes it with & to make that visible.

    Function parameters are constants, and for value types they are copies. Marking a parameter inout changes that: the function receives the caller's variable, can modify it, and the modified value is written back when the function returns. The caller must pass a variable, not a constant or literal, prefixed with &, which marks the call site as one that can change the variable. Swift's own swap(&a, &b) works this way. Returning a new value is usually clearer, so inout is best kept for in-place algorithms.

    func double(_ x: inout Int) {
        x *= 2
    }
    var v = 5
    double(&v)
    print(v)
    // 10
  5. 5.What are designated and convenience initializers in Swift?

    In short: A designated initializer fully initializes a class's properties; a convenience initializer must delegate to another initializer of the same class.

    Every stored property must have a value before an instance can be used, and a class's designated initializers are the ones responsible for that, also calling a superclass initializer when there is one. A convenience initializer is a secondary, more convenient entry point: it must call another initializer of the same class with self.init before doing anything else, as init(rs:) below converts rupees to paise and delegates. These rules exist only for classes, because of inheritance; structs have no such split and get a memberwise initializer.

    class Money {
        var p: Int
        init(p: Int) {
            self.p = p
        }
        convenience init(rs: Int) {
            self.init(p: rs * 100)
        }
    }
    print(Money(rs: 5).p)
    // 500
  6. 6.What is a computed property in Swift?

    In short: A property that stores nothing and calculates its value from other properties each time it is read.

    A stored property keeps a value in memory, while a computed property provides a getter, and optionally a setter, that runs every time the property is accessed. area below is always derived from w and h, so it can never fall out of date. A read-only computed property can omit the get keyword, and its body can be a single expression. Computed properties are allowed in extensions, unlike stored properties, which is how extensions add derived values to existing types. For expensive work, a lazy stored property computes once instead.

    struct Rect {
        var w = 2.0, h = 3.0
        var area: Double { w * h }
    }
    print(Rect().area)
    // 6.0
  7. 7.What do willSet and didSet do in Swift?

    In short: They are property observers that run just before and just after a stored property's value changes.

    Property observers let code react to changes without writing a setter. willSet runs before the new value is stored and receives it as newValue, and didSet runs afterwards with the previous value available as oldValue, as the code shows. They can be attached to stored properties of structs and classes, to properties inherited from a superclass, and to global or local variables. Observers are not called while an initializer sets the property's initial value. A common use is updating a user interface or validating and saving a setting whenever it changes.

    var level = 1 {
        didSet {
            print(oldValue, level)
        }
    }
    level = 3
    // 1 3

How the diagnostic asks it

One question from the Swift bank, exactly as a sitting would show it. The bank has 5 on value & reference types and 30 across Swift.

Value & Reference Types · mediumSWIFT-006

What does this Swift code print?

class Box {
    var v = 1
}

let a = Box()
let b = a
b.v = 9
print(a.v)
  1. 11
  2. 29correct
  3. 3It does not compile: b is a let constant
  4. 4It does not compile: a is a let constant

Box is a class, a reference type, so let b = a copies the reference and both constants point to the same object. let makes the reference constant, so b cannot be pointed at another Box, but the object's var properties can still change, and b.v = 9 is visible through a. The output is 9. 1 is what a struct would print, since b would be a copy. The two compile errors apply to structs: a struct held in a let cannot have any of its properties changed, because the whole value is constant.

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