After seeing how calls are translated in Dispatch, this time we’ll see how values are laid out.
This continues from the previous article Swift Deep Dive #5.
Is a struct’s size the sum of its properties? No.
Would you believe changing property declaration order can change a struct’s size? This is the deep-dive episode on memory layout.
This matters when designing models stored by the hundreds of thousands in arrays, or when Instruments shows an unexpectedly large memory graph.
It also answers the question deferred from some·any: “How large is that box in reality?”
Three dimensions — size, stride, alignment
Swift provides standard tools for querying a type’s memory dimensions: MemoryLayout.
MemoryLayout<Int>.size // 8
MemoryLayout<Bool>.size // 1
struct Point { var x: Double; var y: Double }
MemoryLayout<Point>.size // 16
There are three dimensions, each with a different meaning.
size is one value’s contiguous memory footprint. It excludes tail padding for the type’s alignment.
alignment is the address rule for placing the type. Reading an 8-byte value at an address divisible by 8 is efficient for the CPU, and may be required by the architecture.
Thus each type requires an address divisible by n. These figures target current 64-bit Apple platforms: Double aligns to 8 and Bool to 1.
stride is the distance to the next array element. Alignment can create padding, so stride ≥ size.
This classic example shows their relationship.
struct Bad {
var flag: Bool // 1bytes
var value: Double // 8must be placed at addresses divisible by — 8 bytes
var count: Int32 // 4bytes
}
MemoryLayout<Bad>.stride // 24
struct Good {
var value: Double // 8
var count: Int32 // 4
var flag: Bool // 1
}
MemoryLayout<Good>.stride // 16
The contents are identical, but reordering saves 33% in an array. Bad needs 7 bytes of padding to align value after flag (Swift ABI Type Layout).
Grouping highly aligned properties first is a common padding-reduction heuristic. It is not universally optimal; measure the final result with MemoryLayout.
Swift’s current fragile-struct algorithm places stored properties in declaration order. Field order is not guaranteed by the language or ABI (Swift ABI · SE-0260). Do not rely on observations when exact offsets matter.
This optimization matters only at scale. Unlike 8 bytes in one settings object, 8 bytes per element across one million elements is 8 MB (Swift ABI Type Layout).
As always, measure first.
Enum magic — when Optional’s tag is free
Enum layout reveals the Swift compiler’s ingenuity: it hides case tags in unused bit patterns, or extra inhabitants, of the payload type.
The canonical example is Bool?. On current 64-bit Apple platforms, MemoryLayout<Bool?>.size is 1.
Bool uses only two patterns, 0 and 1, in one byte, so another pattern can represent nil.
Reference-type Optionals work similarly. Swift class pointers do not use null as an object, so nil is represented by 0. On 64-bit systems, AnyObject? has size 8.
Not every Optional is free. Int uses all 8-byte patterns for values. Therefore Int? has size 9 and stride 16 here (Swift ABI).
“Safety is nearly free” is accurate when spare representations can be reused. Optional syntax does not guarantee zero memory cost for every type.
Measuring the box — a single protocol existential is five words
The some·any episode said only that any is a box with a cost. Now we can measure it.
protocol Shape {}
MemoryLayout<any Shape>.size // 40 — current 64bit environment
A single non-class-constrained protocol any Shape is 40 bytes, or 5 words, in the current 64-bit environment: a 3-word value buffer, a type metadata pointer, and a witness table pointer.
The key fork is whether the value’s size and alignment both fit the 3-word inline buffer.
If they exceed the limit, the value is allocated separately and the buffer stores a pointer. Heap allocation, reference counting, and indirection may add cost (Swift ABI · SE-0335).
Five words is not the fixed size of every any type. Protocol compositions can add witness-table pointers.
Class-only protocol existentials use a smaller layout without a value buffer or separate metadata pointer.
If protocol-oriented code is unexpectedly slow, consider removing the box with some·generics or reducing the stored type. Verify effects with Instruments and benchmarks (SE-0335 Existential any).
A class type value has stride 8 on current 64-bit Apple platforms: this is the reference size, not the object size.
Native Swift heap objects generally have a header containing metadata and inline reference-count information; on 64-bit systems it is 16 bytes. Actual allocation can be larger because of property alignment and allocator rounding (Swift HeapObject · Swift RefCount).
The hidden cost of reference types discussed in ARC is this header plus allocation, deallocation, and counting. Hundreds of thousands of tiny class instances can cost more to manage than to store.
Practical toolbox — measure again, verify, question assumptions
Here are the practical tools for this layer.
Validate hypotheses with MemoryLayout. Measuring stride once is often enough when designing mass-produced model structs.
You can add a budget to unit tests, such as #expect(MemoryLayout<Tick>.stride <= 32). A later property that exceeds it becomes immediately visible.
Inspect the heap with Instruments Allocations. If a struct-heavy design has many heap allocations, the suspects are clear.
The any box, closure captures, CoW (copy-on-write) storage, and class instances. Allocations’ type breakdown shows which one dominates.
The copy-on-write collection illusion: on current 64-bit Apple platforms, MemoryLayout<[Int]>.stride is 8. An Array value is a small struct indirectly referencing heap storage, but do not treat this as a fixed ABI.
That is why a small stride for a struct containing an array does not prove it is lightweight.
The real weight is in heap storage, which Allocations shows. The CoW principle is covered in a separate article.
Finally, keep perspective: this knowledge is not used every day.
Its value appears when problems arise. An abnormal memory graph can be investigated from a suspect list instead of guesswork—the benefit of looking one layer deeper.
Summary
- Types have three dimensions: size is the footprint excluding tail padding, alignment is the address rule, and stride is the spacing between consecutive elements. Grouping highly aligned properties often reduces padding, but always measure.
- Enums can hide tags in extra inhabitants. Bool? and reference Optionals need no extra space, while types such as Int? need separate tag space.
- A 64-bit single non-class protocol existential is 5 words. Values exceeding the 3-word buffer in size or alignment may incur separate storage and indirection.
- The default header of a native 64-bit Swift object is generally 16 bytes. Actual allocation may be larger because of property and allocator alignment.
- Use MemoryLayout for design validation and Instruments Allocations for measurement. Layout optimization without measurement is premature optimization.
The next episode covers compile-time magic: Swift macros, which write code behind #Preview and @Observable.
Sources and verification criteria
- MemoryLayout — Apple Developer Documentation · verified 2026-08-19 · basis: API definitions of size, alignment, and stride
- Swift ABI Type Layout — Swift Project · official documentation · verified 2026-08-19 · basis: struct, enum, and existential container layouts
- SE-0260 Library Evolution — Swift Evolution · verified 2026-08-19 · basis: language and ABI guarantees for struct field order
- SE-0335 Existential any — Swift Evolution · verified 2026-08-19 · basis: existential 3-word inline buffer and dynamic memory cost
- Swift HeapObject · Swift RefCount — Swift Runtime · verified 2026-08-19 · basis: native heap-object header and inline reference counting
Continue reading
Swift Deep Dive Series
- Previous episode: [Swift Deep Dive #5] Swift dispatch’s three forms: why final is a performance keyword
- Previous episode: [Swift Deep Dive #4] Structured concurrency: why you should not create Tasks indiscriminately
- Previous episode: [Swift Deep Dive #3] Sendable and Swift 6 concurrency-error migration

![Cover image for [Swift Deep Dive #6] Swift Memory Layout: Property Order Changes Size](/assets/images/posts/dfd50aa0-6496-4f00-a59f-21b04c18ed87/swift-memory-layout-size-stride-alignment.jpg)