Where is a value stored when you declare a variable?
Answering “in memory” is only half right. Within memory, values placed on the stack and those placed on the heap follow completely different lifecycles.
Stack overflow errors, the performance difference between classes and structs, and why ARC is needed—all of these require understanding the two areas.
This article explains how the stack and heap work, why their speeds differ, and where things go in Swift.
This refers to the memory areas. The LIFO data structure is covered separately in Swift Stack and Queue, while the tree data structure used to implement priority queues is covered in Heap and Priority Queue.
Let’s start with the key summary.
- Stack: An automatically managed area that grows with function calls and disappears on return. Fast.
- Heap: A dynamic area borrowed at runtime in the required size. Flexible, but it incurs management costs.
- The essence of the speed difference is “how allocation and deallocation work” and “who performs cleanup.”
- In Swift, structs generally live on the stack, while class instances live on the heap.
Stack: A Stack of Plates
The stack stores function-call information. As its name suggests, it resembles a stack of plates: a LIFO structure where the last plate placed is the first removed.
When a function is called, its local variables, parameters, and return address are pushed together as a stack frame. When the function returns, the entire frame disappears.
This is where the stack’s strengths appear.
Allocation is fast. Move the stack pointer upward and you are done. There is no need to search for free space.
Deallocation is free. When the function ends, simply move the pointer back. There is no cleanup decision to make.
There are constraints, however. The size must be known at compile time, the data always disappears when the function ends, and the total size limit is small (usually a few MB). If a recursive function keeps calling itself without a termination condition, its frames exceed the limit. That error is stack overflow.
Heap: A Spacious but Managed Warehouse
The heap is an area borrowed at runtime as needed. Its size need not be known in advance, it survives after a function ends, and it is much larger.
The trade-off is management overhead.
Allocation is relatively slow. The runtime must find free space of the requested size, and coordinate simultaneous requests from multiple threads.
Someone must take responsibility for deallocation. Because heap data does not disappear when a function ends, someone must determine when it is no longer used. C requires programmers to call free directly, Java periodically uses a garbage collector, and Swift uses ARC to count references and deallocate when the count reaches zero.
A mistake here causes two problems: freeing too late causes a memory leak, while freeing too early causes access to freed memory (a dangling pointer).
What Goes Where in Swift?
Swift documentation and WWDC sessions repeatedly emphasize this basic model: value types (structs and enums) are generally allocated on the stack, while reference types (classes) are allocated on the heap.
struct PointStruct { var x, y: Double }
class PointClass { var x = 0.0, y = 0.0 }
func run() {
let a = PointStruct(x: 1, y: 2) // Entirely inside the stack frame
let b = PointClass() // Allocated on the heap; only a reference on the stack
}
The value of struct a is stored entirely in the stack frame. When the function ends, it disappears with the frame, so reference counting is unnecessary.
Class instance b resides on the heap, while the stack holds only its address. Because it can be referenced from multiple places, ARC must count the references.
This is the performance rationale behind Apple’s recommendation to consider structs first. Heap allocation, reference counting, and locking costs are all avoided.
However, “struct = always stack” is incorrect. A struct stored as a class property lives on the heap with that class, and types such as String and Array are structurally structs but keep their actual data buffers on the heap. The accurate view is that structs meet the conditions needed to be placed on the stack.
Common Follow-up Interview Questions
“Why is the stack faster than the heap?” — Stack allocation and deallocation finish with pointer movement, while the heap requires free-space searches and cleanup management (reference counting and GC).
“Why does stack overflow occur?” — The stack limit is small, so deep recursion or huge local variables can make frames exceed it.
“Are local variables always on the stack?” — No. If a local variable is a reference type, its object is on the heap. Only the reference is on the stack.
Summary
- The stack grows and disappears automatically per function call. It is fast because it is managed through pointer movement.
- The heap is borrowed at a runtime-determined size. It is flexible, but allocation searches and deallocation management cost time.
- Deallocation responsibility: automatic for the stack; language-dependent for the heap (manual in C, GC in Java, ARC in Swift).
- Stack overflow occurs when deep recursion or similar behavior exceeds the stack limit.
- In Swift, structs generally use the stack and classes the heap. This supports the performance case for preferring value types.
- However, structs inside classes live on the heap, and String and Array keep their internal buffers there.

