Stack vs Heap in Swift: interview essentials


Greetings, traveler!

Questions about stack and heap come up regularly when discussing Swift memory model. The topic looks simple on the surface, yet many explanations stop at oversimplified rules that break down as soon as real code enters the picture.

The most common shortcut sounds familiar: structs live on the stack, classes live on the heap. It is easy to remember, but it does not describe how Swift actually behaves. A more reliable way to reason about memory in Swift starts from semantics and only then moves to memory layout.

Value and reference semantics first

Swift separates types into two groups:

  • value types: struct, enum, tuple
  • reference types: class, actor, closures

The difference shows up in how values are passed and modified.

struct Point {
    var x: Int
    var y: Int
}

var a = Point(x: 10, y: 20)
var b = a
b.x = 99

print(a.x) // 10
print(b.x) // 99

Each assignment produces an independent copy. This is the behavior you rely on when working with value types.

Now compare it with a class:

final class User {
    var name: String

    init(name: String) {
        self.name = name
    }
}

let first = User(name: "Artem")
let second = first
second.name = "Bob"

print(first.name)  // Bob
print(second.name) // Bob

Both variables refer to the same instance. Changes are visible through any reference. This distinction carries far more weight than the question of where memory is allocated.

What stack and heap actually represent

Stack and heap are two different strategies for managing memory.

The stack works well for short-lived data with a predictable lifetime. Allocation is close to a pointer move. Deallocation happens automatically when the current scope ends.

The heap supports values that outlive a single function call or need to be shared. Allocation involves a memory manager. Deallocation depends on reference counting in Swift.

In practice, stack allocation tends to be cheaper. Heap allocation provides flexibility. Most real applications use both constantly.

Why “struct in stack” is misleading

A value type does not carry a guarantee about where it will be stored.

Consider a struct inside a class:

struct Address {
    var city: String
    var zipCode: String
}

final class Customer {
    var name: String
    var address: Address

    init(name: String, address: Address) {
        self.name = name
        self.address = address
    }
}

Customer is a class, so its instance lives on the heap. The address field becomes part of that instance. Even though Address is a value type, its data ends up inside a heap allocation.

The semantics remain value-based. The storage location depends on context.

Heap usage inside value types

Some value types rely on heap storage internally. Standard collections are a good example.

var a = [1, 2, 3]
var b = a

b.append(4)

print(a) // [1, 2, 3]
print(b) // [1, 2, 3, 4]

From the outside, this behaves like a regular value. Under the hood, arrays use Copy-on-Write. The buffer is shared until mutation happens. At that point, a new storage is created.

This design keeps value semantics while avoiding unnecessary copying. It also means that these types can involve heap allocations and reference counting.

Reference types and ARC

Classes almost always imply heap allocation. Their lifetime is not tied to a single scope, and multiple references can point to the same instance.

Swift uses Automatic Reference Counting to manage these objects.

final class Resource {
    deinit {
        print("Released")
    }
}

func test() {
    let resource = Resource()
}

When test finishes, the reference count drops to zero and deinit runs. This predictable behavior is one of the advantages of ARC.

Reference types make shared mutable state possible. They also introduce the need to reason about ownership, especially when closures are involved.

Closures and hidden references

Closures deserve special attention because they often introduce reference cycles.

final class ViewModel {
    var onUpdate: (() -> Void)?

    func setup() {
        onUpdate = {
            self.handleUpdate()
        }
    }

    func handleUpdate() {}
}

The closure captures self. If onUpdate is retained by the same instance, the cycle keeps both objects alive.

A capture list breaks the cycle:

onUpdate = { [weak self] in
    self?.handleUpdate()
}

This pattern appears frequently in UIKit and SwiftUI codebases.

Where stack still matters

Even though Swift does not guarantee placement for every value, stack usage remains relevant.

Local variables inside functions often live on the stack. Small value types benefit from this naturally. Short-lived temporary values also tend to stay there.

From a performance perspective, the difference becomes visible in tight loops or high-frequency allocations. Most application code does not need micro-optimizations here, but understanding the cost model helps when profiling.

A concise way to explain it

A clear explanation usually starts with semantics:

Value types copy their data on assignment. Reference types share a single instance through references.

Then it moves to memory:

Stack and heap are implementation details. Classes typically allocate on the heap and use ARC. Value types may live on the stack, inside heap objects, or use heap storage internally depending on context.

That framing keeps the answer grounded and avoids the usual pitfalls.

Conclusion

Once you stop tying types directly to stack or heap, the model becomes easier to reason about. Semantics define how data behaves. Memory layout follows from usage patterns and compiler decisions.