Value Types vs Reference Types in Swift: A Visual Guide


When Woody and Buzz get duplicated at a toy factory, the result depends on how the copy is made. If each copy is completely independent — paint its own face, stuff its own filling — that’s a value type. If every “copy” is just another tag pointing to the same toy on the shelf, that’s a reference type. This difference is one of the most important concepts in Swift.

You’ll learn how structs and classes behave differently when copied, what copy-on-write means, and how to choose the right one for your code. We won’t cover memory management (ARC) or performance benchmarks — those are covered in dedicated posts.

What You’ll Learn

What Are Value and Reference Types?

In Swift, every type falls into one of two categories:

  • Value types create an independent copy when assigned to a new variable or passed to a function. Changing the copy doesn’t affect the original. Structs, enums, and tuples are value types.
  • Reference types share the same underlying data. When you assign a class instance to a new variable, both variables point to the same object in memory. Changing one changes the other. Classes and closures are reference types.

Think of it like this: a value type is like making a photocopy of a Pixar movie poster — each copy is independent, and drawing a mustache on one doesn’t affect the others. A reference type is like sharing a Google Doc — everyone sees the same document, and any edit appears for all.

Apple Docs: Choosing Between Structures and Classes — Swift Documentation

We introduced structs and classes earlier. Now let’s see how this copy behavior plays out in practice.

Value Types: Structs and Enums

When you assign a struct to a new variable, Swift creates a completely independent copy:

struct PixarMovie {
    var title: String
    var rating: Double
}

var original = PixarMovie(title: "Toy Story", rating: 9.8)
var copy = original
copy.title = "Toy Story 2"

print("Original: \(original.title)")
print("Copy: \(copy.title)")
Original: Toy Story
Copy: Toy Story 2

Changing copy.title has no effect on original. They’re two separate values in memory — like two separate action figures from the same mold.

The same applies to enums:

enum Emotion {
    case joy, sadness, anger, fear, disgust
}

var rileyFeeling = Emotion.joy
var clone = rileyFeeling
clone = .sadness

print(rileyFeeling)
print(clone)
joy
sadness

Each variable holds its own independent value. Changing clone doesn’t change rileyFeeling.

Tip: Most Swift standard library types — Int, String, Array, Dictionary, Bool — are all value types (structs).

Reference Types: Classes

With classes, assignment doesn’t copy — it creates another reference to the same object:

class ToyCharacter {
    var name: String
    var owner: String

    init(name: String, owner: String) {
        self.name = name
        self.owner = owner
    }
}

let woody = ToyCharacter(name: "Woody", owner: "Andy")
let sameWoody = woody
sameWoody.owner = "Bonnie"

print("woody.owner: \(woody.owner)")
print("sameWoody.owner: \(sameWoody.owner)")
woody.owner: Bonnie
sameWoody.owner: Bonnie

Both woody and sameWoody point to the same object in memory. Changing the owner through sameWoody also changes it through woody. This is called shared mutable state — and it’s the source of many bugs.

Here’s a diagram to visualize the difference:

VALUE TYPE (struct):              REFERENCE TYPE (class):

original ──→ [ Toy Story ]       woody ──────┐

copy ────→ [ Toy Story 2 ]       [ name: "Woody", owner: "Bonnie" ]

(independent copies)             sameWoody ──┘

                                  (shared object)

Identity vs Equality

Because classes share references, Swift provides two kinds of comparison:

  • Equality (==) — Do these two values have the same content?
  • Identity (===) — Do these two variables point to the same object in memory?
let buzz1 = ToyCharacter(name: "Buzz", owner: "Andy")
let buzz2 = ToyCharacter(name: "Buzz", owner: "Andy")
let buzz3 = buzz1

print(buzz1 === buzz2) // Different objects, same content
print(buzz1 === buzz3) // Same object
false
true

buzz1 and buzz2 have identical content but are different objects in memory, so === returns false. buzz1 and buzz3 point to the exact same object, so === returns true.

Note: The === operator only works with classes (reference types). Structs don’t have identity — they’re just values.

Structs use == for equality when they conform to Equatable:

struct Movie: Equatable {
    var title: String
}

let a = Movie(title: "Up")
let b = Movie(title: "Up")
print(a == b)
true

Copy-on-Write

You might wonder: “If Array is a struct, does Swift copy the entire array every time I assign it?” Not exactly. Swift uses an optimization called copy-on-write (COW): the copy shares the same underlying storage until one of them is modified. Only then does Swift create a separate copy.

var movies = ["Toy Story", "Finding Nemo", "Up"]
var backup = movies // No copy yet — shared storage

backup.append("Coco") // NOW Swift copies the data

print("movies: \(movies.count) items")
print("backup: \(backup.count) items")
movies: 3 items
backup: 4 items

Until backup.append("Coco") runs, both arrays share the same memory. The copy only happens when you actually change one of them. This gives you value-type safety without the performance cost of copying large collections on every assignment.

Tip: Copy-on-write is built into Array, Dictionary, Set, and String. Custom structs don’t automatically get COW — you’d need to implement it yourself (an advanced topic).

How to Choose: Struct or Class?

Swift’s official guidance is: start with a struct and only switch to a class when you need one of the things only classes provide.

Use a struct when:

  • The data is a simple value (coordinates, colors, measurements)
  • You want independent copies by default
  • You don’t need inheritance
  • Thread safety matters — value types are inherently safe to share across threads

Use a class when:

  • You need shared mutable state (a single source of truth)
  • You need inheritance from a base class
  • You need identity (===) — like tracking whether two variables point to the same object
  • You’re working with Objective-C APIs that require classes
// ✅ Struct — each review is an independent value
struct MovieReview {
    var title: String
    var score: Double
    var comment: String
}

// ✅ Class — shared state for a data manager
class MovieLibrary {
    var movies: [String] = []

    func add(_ movie: String) {
        movies.append(movie)
    }
}

Common Mistakes

Expecting Class Copies to Be Independent

This is the most common source of bugs for beginners coming from other languages:

// ❌ Unintended shared state
class Settings {
    var theme = "light"
}

let userSettings = Settings()
let previewSettings = userSettings
previewSettings.theme = "dark"

print(userSettings.theme) // "dark" — oops!
// ✅ Use a struct for independent copies
struct Settings {
    var theme = "light"
}

var userSettings = Settings()
var previewSettings = userSettings
previewSettings.theme = "dark"

print(userSettings.theme) // "light" — safe!
light

If you need independent copies, use a struct. If you intentionally want shared state, use a class — but be aware that changes through one reference affect all others.

Using let and Thinking a Class Is Constant

With structs, let means the entire value is immutable. With classes, let only means the reference can’t change — the object’s properties can still be modified:

// ❌ Misleading — `let` doesn't prevent property changes
let character = ToyCharacter(name: "Woody", owner: "Andy")
character.owner = "Bonnie" // This compiles! The object is mutated.
// ✅ With a struct, `let` truly prevents mutation
struct ToyFigure {
    var name: String
    var owner: String
}

let figure = ToyFigure(name: "Woody", owner: "Andy")
// figure.owner = "Bonnie" // ❌ Compiler error — struct is immutable

With classes, let means “this variable always points to the same object” — not “this object can’t change.” With structs, let means the whole value is frozen.

What’s Next?

  • Value types (structs, enums) create independent copies on assignment
  • Reference types (classes) share the same object — changes propagate
  • Use === to check identity (same object) and == to check equality (same content)
  • Copy-on-write gives Swift collections value-type safety with reference-type performance
  • Start with structs — only use classes when you need shared state, inheritance, or identity

You’ve now completed the Swift language fundamentals. Next up: Xcode Essentials — getting comfortable with the tool you’ll use to build real iOS apps with everything you’ve learned.