Yunus Emre Alpak

Stack and Heap: How a Program's Memory Actually Works

2026 · 05 · 119 min read

Stack and heap are the two rooms every program ignores — until a crash, a GC pause, or a performance issue knocks on the door. This essay opens them first language-agnostically, then ties them to Swift and Dart, from allocation cost to the value-vs-reference trade.

Memory map of a running program: stack, heap, data, and code regions

Two rooms every program ignores

It doesn't matter what language you write in: while it runs, your program splits its memory into two fundamentally different regions — stack and heap. Most of the time you don't think about them, until performance, an unexpected crash, or the behavior of the garbage collector forces you to. And yet, really understanding the two explains a lot at once: why some allocations are "free" while others trigger the GC, why deep recursion crashes, why the value/reference distinction matters. The topic is language-agnostic — same RAM, same rules. We'll tie it back to Swift and Dart along the way.

First the map: how a program divides memory

When a program starts, the operating system hands it an address space and carves it into regions (going from high to low addresses):

The critical point: stack and heap live in the same physical RAM. The difference between them isn't hardware, it's how they're managed.

Stack: fast, automatic, disciplined

The stack is where function calls do their work. When a function is called, a stack frame is pushed onto it: locals, the return address ("where do I jump back when I'm done"), and a link to the previous frame. When the function returns, that frame is popped — and most of the time that pop isn't a physical erase, it's just moving the stack pointer back. The structure is LIFO (last-in-first-out).

Properties:

The stack trace you see in a crash is, in fact, a snapshot of this stack at that moment: a chain of frames saying "who called me."

Heap: large, flexible, but with a cost

The heap is for data whose size or lifetime you can't know at compile time, or that needs to outlive the function that created it.

Properties:

Stack vs heap, at a glance

Stack vs heap comparison: allocation, lifetime, speed, ownership, and cleanup

StackHeap
Allocationmove the pointer (one op)ask the allocator (expensive)
Lifetimeends when the function returns (auto)until you/GC release it
Speedvery fast (cache-friendly)slower (scattered)
Sizesmall (~MB)large
Ownershipper-threadshared

In short: the stack carries control flow; the heap stores data.

How languages use this: value or reference?

Memory layout in Swift and Dart: struct inline, class and reference on the heap, Smi inline in Dart

The real lever a language gives you is this: can you hold a value inline (embedded in place, usually on the stack), or only behind a reference (a pointer, on the heap)?

Swift gives you both. A struct is a value type: its data is copied and stored inline; small structs can live on the stack or be embedded inside another object without any heap allocation. A class is a reference type: the object lives on the heap, and a small reference to it sits on the stack or in a register. Heap objects are managed by ARC (automatic reference counting) — deterministic, but every retain/release is an atomic operation. A small trap: Array, String, Dictionary are value types, but they use a heap buffer + reference counting underneath (copy-on-write).

struct Point { var x, y: Int }    // value     → inline
class  Node  { var next: Node? }  // reference  → heap

let p = Point(x: 1, y: 2)          // data lives right here (stack)
let n = Node()                     // object on the heap, reference on the stack

Dart has no struct — every class is a reference type. So the body of your objects lives on the heap, with references on the stack.

class Point { int x, y; Point(this.x, this.y); }

var p = Point(1, 2);  // object on the heap, reference on the stack
var n = 42;           // small int → inline (Smi), never hits the heap

The "everything's on the heap" misconception

Two things to clarify here so you don't drift into a wrong mental model.

First, "everything is an object/reference" ≠ "everything is on the heap." Dart keeps small integers (Smi — small integer) directly inside the pointer via pointer tagging; they never go to the heap. In optimized code doubles can stay in registers (unboxed), and escape analysis can sometimes eliminate allocations entirely.

Second, and more importantly: every language uses the call stack, regardless of the value/reference divide. Even in Dart, function calls, return addresses, and the references themselves live on the stack. The recursion of a deep widget tree's build() runs entirely on the call stack (and can overflow it). Reference types move the body of an object to the heap; they don't eliminate the stack. So "Dart only uses the heap" is wrong: object data leans on the heap (because there are no structs), but flow, references, and small values use the stack at full speed.

So does this determine performance?

Don't overstate it. Value types reduce heap allocation and GC pressure on hot paths, and give you control — a real advantage in tight loops, particle systems, audio/signal processing. But they aren't "always faster": large structs are expensive to copy; copy-on-write hides a sneaky heap allocation plus atomic reference counting; ARC's atomic retain/release isn't free; Dart's generational GC, meanwhile, allocates short-lived objects very cheaply (almost a pointer bump in the young generation). On top of that, in real apps performance is usually decided not by a language's stack/heap leanings but by architecture, I/O, and rendering discipline. "More control" is not the same thing as "always faster."

A mental model

If you have to squeeze it into one sentence: the stack is a fast, automatic, per-thread work area for calls (flow + locals + references); the heap is a large, shared, managed store for long-lived and dynamic data. Languages diverge on how much of the data you can hold inline (value) vs. behind a reference (heap) — Swift gives you the choice (struct/class), Dart leans on references. But both use both regions; the stack never disappears. Knowing which is which explains allocation cost, crashes, GC, and value-vs-reference trade-offs all under one model.