Swift MemoryLayout and Alignment: Size, Stride, and Padding

Swift’s MemoryLayout type tells you how much memory a type uses, how its values are aligned, and how much space each value occupies in an array. These details matter when you work with unsafe memory, performance-sensitive code, binary data, or interoperability with lower-level APIs.

Quick answer: MemoryLayout gives you three important measurements: size is the actual bytes used by a value, stride is the spacing between consecutive values in an array, and alignment is the byte boundary the type prefers. stride is often larger than size because of padding.

Difficulty: Intermediate

You'll understand this better if you know: basic Swift structs, arrays, stored properties, and the difference between value types and reference types.

1. What Is MemoryLayout?

MemoryLayout is a generic Swift standard library type that describes how a type is represented in memory. It is most often used to inspect the memory characteristics of structs, enums, tuples, and primitive types such as Int and Double.

These values are not always the same. In particular, the stride can include extra padding bytes so that the next value in an array is correctly aligned.

2. Why MemoryLayout Matters

Most Swift code never needs to think about raw bytes, but MemoryLayout becomes important when you:

It also helps explain why two types that appear similar can have very different memory costs. A struct with a small size can still have a larger stride because Swift must insert padding to keep values aligned efficiently.

3. Basic Syntax or Core Idea

You use MemoryLayout<Type> to inspect a type at compile time. The three most common properties are static and easy to read.

Reading size, stride, and alignment

struct Pixel {
    var red: UInt8
    var green: UInt8
    var blue: UInt8
}

print(MemoryLayout<Pixel>.size)
print(MemoryLayout<Pixel>.stride)
print(MemoryLayout<Pixel>.alignment)

This example shows the basic pattern. You provide a type inside angle brackets, then ask for the measurement you need.

Value-level size

Swift also provides a slightly different API for instances:

let pixel = Pixel(red: 255, green: 128, blue: 64)
print(MemoryLayout.size(ofValue: pixel))

For most layout questions, the type-based form is the one you want because it describes the type itself rather than one specific instance.

4. Step-by-Step Examples

Example 1: A simple struct with no padding surprises

Start with three one-byte fields. This is a good baseline because the values are already naturally compact.

struct RGB {
    var r: UInt8
    var g: UInt8
    var b: UInt8
}

print(MemoryLayout<RGB>.size)      // 3
print(MemoryLayout<RGB>.stride)    // 3 or more, depending on alignment needs
print(MemoryLayout<RGB>.alignment) // typically 1

Because each property is a UInt8, the struct stores exactly three bytes of data. The stride is often the same here, because the alignment requirement is small.

Example 2: A struct with padding

Now add a larger type. This is where padding becomes visible.

struct Record {
    var flag: UInt8
    var count: Int
}

print(MemoryLayout<Record>.size)
print(MemoryLayout<Record>.stride)
print(MemoryLayout<Record>.alignment)

The Int property usually needs a stricter alignment than UInt8. Swift may insert padding between flag and count so that count starts on a suitable boundary.

Example 3: Reordering properties to reduce padding

Property order can affect memory layout. Putting larger fields first often reduces wasted space.

struct PoorLayout {
    var a: UInt8
    var b: Int
    var c: UInt8
}

struct BetterLayout {
    var b: Int
    var a: UInt8
    var c: UInt8
}

print(MemoryLayout<PoorLayout>.stride)
print(MemoryLayout<BetterLayout>.stride)

This does not change what the struct means semantically, but it can change how efficiently it fits in memory. In large collections, that difference can matter.

Example 4: Arrays use stride, not size, between elements

Arrays store values back-to-back using the type’s stride. That means the amount of memory used by an array is closer to count × stride than count × size.

struct Item {
    var id: UInt8
    var value: Int
}

let items: [Item] = [
    Item(id: 1, value: 100),
    Item(id: 2, value: 200)
]

let estimatedBytes = items.count * MemoryLayout<Item>.stride
print(estimatedBytes)

This example shows why stride is the more useful number when you want to estimate the storage of an array of values.

5. Practical Use Cases

In real code, you usually use MemoryLayout for inspection and planning, not as part of every day business logic.

6. Common Mistakes

Mistake 1: Using size when you need stride

Beginners often multiply size by a collection count and assume that gives the total storage used. That ignores padding between elements.

Problem: The calculation underestimates memory because the array steps through elements using stride, not size.

struct Entry {
    var tag: UInt8
    var amount: Int
}

let count = 10
let wrongBytes = count * MemoryLayout<Entry>.size

Fix: Use stride for storage estimates involving repeated values.

let correctBytes = count * MemoryLayout<Entry>.stride

The corrected version matches how Swift spaces values in memory.

Mistake 2: Assuming property order never matters

Swift does not sort stored properties for you. If you place small fields between large ones, you can increase padding.

Problem: This layout can waste memory because the compiler must insert extra bytes to align the Int value.

struct Wasteful {
    var a: UInt8
    var b: Int
    var c: UInt8
}

Fix: Group larger alignment requirements first when layout efficiency matters.

struct Tighter {
    var b: Int
    var a: UInt8
    var c: UInt8
}

The corrected version often reduces padding while preserving the same semantic model.

Mistake 3: Treating MemoryLayout as a promise for all platforms

The exact numbers can vary across architectures and Swift implementations. A value’s layout is stable enough for the current platform, but not always identical everywhere.

Problem: Code that hard-codes one platform’s byte counts can fail or read invalid data on another platform.

let expectedBytes = 16
if MemoryLayout<Int>.size != expectedBytes {
    print("Unexpected platform layout")
}

Fix: Query the type at runtime and avoid assuming the same byte width on every system.

let actualBytes = MemoryLayout<Int>.size
print("Int uses \(actualBytes) bytes on this platform")

The corrected version adapts to the current environment instead of relying on a fixed assumption.

7. Best Practices

Practice 1: Use stride for buffers and arrays

When allocating or estimating repeated storage, prefer stride. It matches the spacing rules Swift uses for consecutive values.

struct Sample {
    var x: UInt8
    var y: Int
}

let bufferSize = 100 * MemoryLayout<Sample>.stride

This is the safest choice when values are stored in an array-like pattern.

Practice 2: Use size for a single in-memory value

If you only need the bytes occupied by one value, size is the right measurement.

struct Header {
    var version: UInt8
    var flags: UInt8
}

let bytesForOneHeader = MemoryLayout<Header>.size

This helps when you are packing or unpacking one value into a raw byte sequence.

Practice 3: Reorder stored properties only when it matters

Property order can improve memory efficiency, but readability still matters. Use ordering changes when a type appears many times or occupies large collections.

struct LogEntry {
    var timestamp: Int64
    var level: UInt8
    var isArchived: Bool
}

A layout-aware order can reduce waste, but you should not sacrifice clarity for a tiny savings in a rarely used type.

8. Limitations and Edge Cases

Warning: Never treat raw memory as a stable file format unless you explicitly define the format yourself. Native Swift layout is for memory, not long-term storage compatibility.

9. Practical Mini Project

Let’s build a tiny layout inspector that prints the memory characteristics of a few types. This is a simple but complete example you can adapt during debugging or learning.

import Foundation

struct Small {
    var a: UInt8
    var b: UInt8
}

struct Mixed {
    var a: UInt8
    var b: Int
}

func describe<T>(_ type: T.Type, name: String) {
    print("\(name):")
    print("  size: \(MemoryLayout<T>.size)")
    print("  stride: \(MemoryLayout<T>.stride)")
    print("  alignment: \(MemoryLayout<T>.alignment)")
}

describe(Small.self, name: "Small")
describe(Mixed.self, name: "Mixed")
describe(Int.self, name: "Int")

This project helps you compare types quickly and spot padding or alignment effects in your own models.

10. Key Points

11. Practice Exercise

Try this exercise to confirm your understanding of layout and alignment.

Expected output: You should see that the property order can change stride, even when size stays close to the same.

Hint: Put the largest alignment requirement first, then smaller fields later.

Solution:

struct FirstOrder {
    var a: UInt8
    var b: Int
    var c: UInt8
}

struct SecondOrder {
    var b: Int
    var a: UInt8
    var c: UInt8
}

print("FirstOrder size: \(MemoryLayout<FirstOrder>.size)")
print("FirstOrder stride: \(MemoryLayout<FirstOrder>.stride)")
print("FirstOrder alignment: \(MemoryLayout<FirstOrder>.alignment)")

print("SecondOrder size: \(MemoryLayout<SecondOrder>.size)")
print("SecondOrder stride: \(MemoryLayout<SecondOrder>.stride)")
print("SecondOrder alignment: \(MemoryLayout<SecondOrder>.alignment)")

let firstValues = [
    FirstOrder(a: 1, b: 2, c: 3),
    FirstOrder(a: 4, b: 5, c: 6)
]

let secondValues = [
    SecondOrder(b: 2, a: 1, c: 3),
    SecondOrder(b: 5, a: 4, c: 6)
]

let firstBytes = firstValues.count * MemoryLayout<FirstOrder>.stride
let secondBytes = secondValues.count * MemoryLayout<SecondOrder>.stride

print("FirstOrder array bytes: \(firstBytes)")
print("SecondOrder array bytes: \(secondBytes)")

This solution shows how layout can change when fields are reordered and why stride is the correct choice for repeated storage.

12. Final Summary

MemoryLayout is the Swift tool for understanding a type’s memory footprint. Its three measurements—size, stride, and alignment—help you reason about padding, storage cost, and raw memory usage.

In practical Swift code, the most important habit is knowing when to use each measurement. Use size for one value, stride for repeated values and buffers, and alignment when you need to understand how the value must be placed in memory.

If you want to go further, next study Swift unsafe pointers and raw buffers, because that is where MemoryLayout becomes especially useful in real projects.