If the dispatch installment examined “how calls are translated,” this time we examine “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 that changing property declaration order can change a struct’s size? This is the memory-layout installment of the deep-dive series.
This matters when designing models for hundreds of thousands of array elements, or when Instruments shows a memory graph that is fatter than expected.
It also answers the question deferred from the some·any installment: “How large is that box in reality?”
Three dimensions — size, stride, alignment
Swift provides standard tools for querying a type’s memory dimensions. They are 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, and each means something different.
size is the contiguous memory footprint of one value. It excludes tail padding for alignment at the end of the type.
alignment is the rule governing addresses where the type may be placed. CPUs efficiently read an 8-byte value from an address divisible by 8 (and some architectures require it).
Thus each type is constrained to an address that is a multiple of n. These figures use current 64-bit Apple platforms: Double has alignment 8 and Bool has alignment 1.
stride is the distance to the next element in an array. Alignment can create padding between elements, so stride ≥ size.
Here is the classic example showing their relationship.
struct Bad {
var flag: Bool // 1 bytes
var value: Double // 8 must be placed at addresses that are multiples of — 8 bytes
var count: Int32 // 4 bytes
}
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 changing the order saves 33% for an array. Bad needs 7 bytes of padding to align value after flag (Swift ABI Type Layout).
In practice, grouping properties with larger alignment is a common padding-reduction heuristic. It does not guarantee the optimum for every type, so measure the final result with MemoryLayout.
Swift’s current fragile struct layout algorithm places stored properties in declaration order. However, field order is not guaranteed by the language or ABI (Swift ABI · SE-0260). Code needing exact offsets must not depend on observations.
Second, this optimization matters only with many instances. Unlike 8 bytes in one settings object, 8 bytes per element in a one-million-element array totals 8 MB (Swift ABI Type Layout).
As always, measure first.
Enum magic — when Optional tags are free
Enum layout reveals the Swift compiler’s cleverness. A case tag is required, so it hides the tag in unused bit patterns (extra inhabitants) of the payload type.
The classic example is Bool?. On current 64-bit Apple platforms, MemoryLayout<Bool?>.size is 1.
Bool uses only two patterns in one byte (0 and 1), so a remaining pattern represents nil.
Reference-type optionals work similarly. Swift class pointers do not use null as an object, so nil is represented by 0. On current 64-bit systems, AnyObject? has size 8.
But not every Optional is free. Int uses all 8-byte bit patterns for values. Therefore, in the current environment, Int? has size 9 and stride 16 (Swift ABI).
“Safety is almost free,” as described in the Optional installment, is accurate for types whose spare representations can be reused. Optional syntax itself does not guarantee zero memory cost for every type.
Measuring the box — a single-protocol existential is five words
The some·any installment only said that “any is a box with a cost.” Now we can measure it.
protocol Shape {}
MemoryLayout<any Shape>.size // 40 — current 64-bit environment
A single class-unconstrained protocol any Shape is 40 bytes, or five words, on the current 64-bit environment: a three-word value buffer, a type metadata pointer, and one witness table pointer.
The key fork is whether the stored value’s size and alignment both fit the three-word inline buffer.
If they do not, the value is allocated separately and the buffer holds a pointer. Heap allocation, reference counting, and indirection may add costs (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 performs unexpectedly, consider removing the box with some·generics or reducing the stored type. Verify the actual effect with Instruments and benchmarks (SE-0335 Existential any).
Class type values have stride 8 on current 64-bit Apple platforms; this is the size of the reference, not the object.
Native Swift heap objects generally have a header containing a metadata pointer and inline reference-counting information, totaling 16 bytes on 64-bit systems. Actual allocation may be larger because of property alignment and allocator rounding (Swift HeapObject · Swift RefCount).
The hidden cost of reference types discussed in the ARC installment is this header plus allocation, deallocation, and counting operations. Making hundreds of thousands of small data pieces into classes can make management cost exceed data cost.
Practical toolbox — measure again, verify, question
Here are the practical tools for this layer.
Validate hypotheses with MemoryLayout. When designing mass-produced model structs, simply measuring stride once is a useful habit.
You can also add a budget to unit tests, such as #expect(MemoryLayout<Tick>.stride <= 32). A later property addition that exceeds the budget will immediately surface.
Inspect the heap with Instruments Allocations. If a struct-heavy design still has many heap allocations, the suspects are clear.
Any boxes, closure captures (the closure installment), CoW (copy-on-write) storage, and class instances. Allocations’ per-type totals show which one is responsible.
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 assume this number is a fixed ABI.
That is why a small stride for a struct containing an array does not prove that 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 installment’s knowledge is not used every day.
But the value of knowing “why” appears during problems. When a memory graph looks wrong, you can start with a list of suspects instead of guesses—the advantage of having gone one layer deeper.
Summary
- A type has three dimensions: size is its footprint excluding tail padding, alignment is its address rule, and stride is the spacing between consecutive elements. Grouping highly aligned properties often reduces padding in mass-produced structs, but always measure.
- An enum can hide its tag in the payload’s extra inhabitants. Bool? and reference optionals need no extra space, while types such as Int? need separate tag space.
- A single non-class protocol existential is five words on 64-bit systems. If the value’s size or alignment exceeds the three-word buffer, separate storage and indirection costs may appear.
- The default header of a native Swift object is generally 16 bytes on current 64-bit systems. Actual allocation may be larger depending on property and allocator alignment.
- The tools are MemoryLayout for design validation and Instruments Allocations for measurement. Layout optimization without measurement is premature optimization.
The next installment covers compile-time magic: Swift macros, the mechanism behind #Preview and @Observable that makes code write code.
Sources and verification criteria
- MemoryLayout — Apple Developer Documentation · checked 2026-08-19 · basis: API definitions for size, alignment, and stride
- Swift ABI Type Layout — Swift project · official documentation · checked 2026-08-19 · basis: struct, enum, and existential-container layout
- SE-0260 Library Evolution — Swift Evolution · checked 2026-08-19 · basis: language and ABI guarantees for struct field order
- SE-0335 Existential any — Swift Evolution · checked 2026-08-19 · basis: existential three-word inline buffer and dynamic memory costs
- Swift HeapObject · Swift RefCount — Swift runtime · checked 2026-08-19 · basis: native heap-object headers and inline reference counting

![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)