How does Swift handle memory for value types internally?
The starting point: value semantics ≠ literal copying
Swift’s contract is that value types (struct, enum, tuples, and things like Array/Dictionary/String) have value semantics — assigning or passing one gives you a logically independent copy. But the compiler doesn’t naively memcpy the whole thing on every assignment; what actually happens depends on the type’s storage.
1. Stack vs. heap allocation
Simple value types with fixed, known size (an Int, a struct Point { var x, y: Double }) live on the stack when possible — no heap allocation, no reference counting, no ARC traffic.
struct Point { var x: Double; var y: Double }
var p1 = Point(x: 0, y: 0)
var p2 = p1 // full stack copy, no heap involved
p2.x = 5 // p1 is untouched
2. Copy-on-write (COW) for types with heap-backed storage
For Array, Dictionary, Set, String — the struct itself is small, and the actual buffer lives on the heap, managed via COW.
var a = [1, 2, 3]
var b = a // no copy yet — both point to the same heap buffer, refcount 2
b.append(4) // mutation triggers a uniqueness check via isKnownUniquelyReferenced
// if refcount > 1: allocate new buffer, copy, then mutate
Reads never trigger a copy, only writes do — hence “copy-on-write.”
3. String’s specific twist — small string optimization
Short strings (up to 15 bytes on 64-bit) are stored inline in the struct itself, no heap allocation at all (SSO). Only strings exceeding that threshold get a heap-allocated COW buffer.
4. Value types inside classes
If a struct is a stored property of a class, its storage lives inline inside the class instance’s heap allocation.
5. Existentials and generics — where it gets less free
When a value type is used as an existential (Any, any Shape), and its size exceeds 3 words (24 bytes on 64-bit), Swift heap-allocates a box to hold it. Generic functions operating on unspecialized type parameters may box values similarly unless the compiler can specialize.
6. ARC still applies, just less often
Value types themselves aren’t reference-counted, but if a value type contains a class reference (or a COW buffer reference), copying that value type still triggers retain/release traffic on that contained reference.
Interview-ready summary table
| Scenario | Storage | Copy cost |
|---|---|---|
| Simple struct (fixed size, no heap members) | Stack / inline | Full bitwise copy, cheap, no ARC |
Array/Dictionary/Set | Struct wrapper + heap buffer | Pointer copy + retain; real copy deferred until mutation (COW) |
Short String (≤15 bytes) | Inline in struct | Bitwise copy, no heap |
Long String | Heap buffer, COW | Same as Array |
| Struct as class property | Inline inside class’s heap block | Copied as part of copying the containing bytes |
Large struct in any Protocol / Any | Heap-boxed existential container | Heap allocation on assignment |
One-liner if pressed: “Value types copy their value semantics, not necessarily their bytes — small fixed-size structs live on the stack and copy for free, while types like Array and String defer the actual copy via copy-on-write until a mutation happens on a shared buffer, and only fall back to heap allocation for large structs stored as existentials.”