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?
- Value Types: Structs and Enums
- Reference Types: Classes
- Identity vs Equality
- Copy-on-Write
- How to Choose: Struct or Class?
- Common Mistakes
- What’s Next?
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, andString. 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.