iosNudge

What is the difference between stack and heap memory in Swift?

MediumMemory & ARCPerformance 3 min read

TL;DR

The stack is fast memory for local data. It is freed automatically when a function returns. The heap is memory for data that must live longer or has a size known only at run time. It needs allocation and ARC. "Structs go on the stack, classes go on the heap" is a good start, but not the full truth. Interviewers like it when you know the exceptions.

The stack

Each thread has its own stack. Calling a function pushes a frame with its local values. Returning pops it.

func area(width: Double, height: Double) -> Double {
    let result = width * height   // stored in this function's frame
    return result
}                                 // frame popped, memory free
  • Allocation is just moving a pointer. Very fast.
  • No locking. Each thread has its own stack.
  • Size is limited. Main thread: about 1 MB on iOS by default; secondary threads: 512 KB. Deep recursion can overflow it.

The heap

Shared by all threads. Used for data that must outlive the function or that is too big or dynamic.

final class User {
    var name: String
    init(name: String) { self.name = name }
}

func makeUser() -> User {
    User(name: "Ana")    // allocated on the heap, returned by reference
}
  • Allocation must find a free block. It is thread-safe, so it has a cost.
  • ARC counts references, and frees the memory at zero.
  • Data can live as long as needed.

Comparison

StackHeap
SpeedVery fast (move a pointer)Slower (find a block, thread safety)
LifetimeUntil the function returnsUntil the last strong reference goes away
Managed byCompiler automaticallyARC (for class instances)
Shared between threadsNoYes
SizeSmall, fixed per threadLarge

What actually goes where

ThingUsually
Local Int, Double, small structStack (or CPU registers)
Class or actor instanceHeap
Array, Dictionary, Set elementsHeap (the struct itself is small)
StringInline if small (up to 15 UTF-8 bytes), heap if larger
Value captured by an escaping closureHeap (in a box)
Large value in any ProtocolHeap (more than 3 words does not fit the inline buffer)
indirect enum caseHeap

Exceptions you should mention

A struct can live on the heap

struct Point { var x = 0.0, y = 0.0 }

let points = [Point(), Point()]   // the Points are in the array's heap buffer
final class Shape { var origin = Point() }  // stored inside a heap object

Captured variables move to the heap

func makeCounter() -> () -> Int {
    var count = 0                // captured, so Swift boxes it on the heap
    return {
        count += 1
        return count
    }
}

count must outlive makeCounter, so it cannot stay on the stack.

A class can live on the stack

If the optimizer proves an object never escapes the function, it can allocate it on the stack. This is called stack promotion.

func compute() -> Int {
    let helper = Helper()    // may be stack-promoted if it never escapes
    return helper.run()
}

Why it matters for performance

Heap allocation and reference counting are among the most common costs in Swift code. To reduce them:

  • Prefer structs with simple stored properties for small models.
  • Avoid any Protocol in hot loops. Use generics or some.
  • Reserve capacity: array.reserveCapacity(1_000) avoids repeated reallocations.
  • Mark classes final. It helps the optimizer.
  • Profile with Instruments (Allocations) before you optimize.

A struct with many class references (or Strings and arrays) is not free to copy. Every copy retains each reference.

Common follow-up questions

  • What is a stack overflow? The stack runs out of space, usually from infinite or very deep recursion. The app crashes.
  • Are value types always faster? No. A large struct with many references can cost more to copy than passing one class reference.
  • What is the inline buffer of an existential? any P reserves three words for the value. Bigger values are boxed on the heap.
  • Can you choose stack or heap yourself? Not directly. You choose value vs reference types and the compiler decides. withUnsafeTemporaryAllocation gives stack memory when possible.
  • Does Swift 6 add anything here? Yes. InlineArray (Swift 6.2) is a fixed-size array stored inline, with no heap buffer. Span gives safe access to memory without copying.