Structs vs Classes in Swift: Understanding Value and Reference Types
Imagine Remy from Ratatouille writes down his secret soup recipe on a card. If you photocopy that card, you get your own independent copy — scribble on it all you want, and Remy’s original stays untouched. But if there’s only one master recipe book in the kitchen and everyone shares it, then any chef who changes a step changes it for everyone. That’s the core difference between structs (the photocopy) and classes (the shared book) in Swift.
In this guide, you’ll learn how to define structs and classes, understand value types vs reference types, see how class inheritance works, and know when to choose each one. We won’t cover protocols or memory management — those have their own dedicated posts.
What You’ll Learn
- What Are Structs and Classes?
- Defining a Struct and a Class
- Value Types vs Reference Types
- Inheritance (Classes Only)
- When to Choose Each
- Common Mistakes
- What’s Next?
What Are Structs and Classes?
Both structs and classes let you group related data and behavior into a single custom type. You can give them properties (data) and methods (actions), and use them to model anything in your app — a movie, a character, a recipe.
The key difference is how copies work:
- A struct is a value type. When you copy it, you get an independent duplicate. Changing the copy doesn’t affect the original.
- A class is a reference type. When you copy it, both variables point to the same object in memory. Changing one affects the other.
This single distinction drives most of the decisions around when to use each.
Defining a Struct and a Class
Structs
Define a struct with the struct keyword. Swift automatically generates a memberwise initializer — a free init
that takes each property as a parameter:
struct Movie {
var title: String
var year: Int
var rating: Double
}
let coco = Movie(title: "Coco", year: 2017, rating: 8.4)
print("\(coco.title) (\(coco.year)) — \(coco.rating)/10")
Coco (2017) — 8.4/10
You didn’t write an init — Swift created one automatically because Movie is a struct. The memberwise initializer
includes one parameter per stored property, in the order they’re declared.
Classes
Define a class with the class keyword. Classes do not get a free memberwise initializer, so you must write your
own:
class Director {
var name: String
var movieCount: Int
init(name: String, movieCount: Int) {
self.name = name
self.movieCount = movieCount
}
}
let pete = Director(name: "Pete Docter", movieCount: 4)
print("\(pete.name) directed \(pete.movieCount) films")
Pete Docter directed 4 films
The init method runs when you create a new instance. Inside init, self.name refers to the property, while name
refers to the parameter.
Tip: If you want a struct-like memberwise initializer for a class, you have to write it yourself. This is by design — classes often need more complex initialization logic.
Value Types vs Reference Types
This is the most important section in this post. Understanding this difference is essential to writing correct Swift code.
Structs Copy Independently
When you assign a struct to a new variable or pass it to a function, Swift creates a full, independent copy:
var original = Movie(title: "Up", year: 2009, rating: 8.3)
var copy = original
copy.title = "Inside Out"
copy.year = 2015
print("Original: \(original.title) (\(original.year))")
print("Copy: \(copy.title) (\(copy.year))")
Original: Up (2009)
Copy: Inside Out (2015)
Changing copy didn’t affect original. They’re completely separate — like two independent photocopies of Remy’s
recipe card.
Classes Share the Same Instance
When you assign a class instance to a new variable, both variables point to the same object in memory:
let director1 = Director(name: "Brad Bird", movieCount: 2)
let director2 = director1
director2.movieCount = 3
print("Director 1: \(director1.movieCount) films")
print("Director 2: \(director2.movieCount) films")
Director 1: 3 films
Director 2: 3 films
Both director1 and director2 refer to the same Director object. When we changed movieCount through director2,
the change was visible through director1 too — because there’s only one object. It’s like two chefs looking at the
same master recipe book.
Warning: Shared mutable state through class references is a common source of bugs. When multiple parts of your code can change the same object, unexpected mutations can happen. This is one reason Swift encourages structs by default.
Identity vs Equality
Because class instances are shared, Swift gives you the identity operator (===) to check if two variables point to
the exact same object:
let a = Director(name: "Andrew Stanton", movieCount: 3)
let b = a
let c = Director(name: "Andrew Stanton", movieCount: 3)
print(a === b) // Same object
print(a === c) // Different objects with same data
true
false
Structs don’t have === because each copy is always a distinct value — there’s no concept of shared identity.
Inheritance (Classes Only)
Classes support inheritance — a class can build on another class, inheriting all its properties and methods. The new class is called a subclass, and the class it builds on is the superclass:
class Film {
var title: String
var year: Int
init(title: String, year: Int) {
self.title = title
self.year = year
}
func summary() -> String {
"\(title) (\(year))"
}
}
class PixarFilm: Film {
var director: String
init(title: String, year: Int, director: String) {
self.director = director
super.init(title: title, year: year)
}
override func summary() -> String {
"\(title) (\(year)) — directed by \(director)"
}
}
let soul = PixarFilm(title: "Soul", year: 2020, director: "Pete Docter")
print(soul.summary())
Soul (2020) — directed by Pete Docter
Key points about inheritance:
PixarFilm: FilmmeansPixarFilminherits fromFilm.super.init(...)calls the superclass’s initializer.overridemarks a method that replaces the superclass version.
Structs cannot inherit from other structs. If you need inheritance, you need a class. However, both structs and classes can conform to protocols, which is often a better choice.
Apple Docs:
Structures and Classes— The Swift Programming Language
When to Choose Each
Apple’s official guidance is clear: default to structs. Use classes only when you specifically need reference semantics or class-only features.
Use a struct when:
- You’re modeling simple data (a point, a movie, a recipe).
- You want copies to be independent.
- You don’t need inheritance.
- The data is used on a single thread or passed between contexts.
Use a class when:
- You need shared mutable state — multiple parts of your code should see the same object.
- You need inheritance to build a hierarchy of types.
- You need identity — checking if two variables point to the same instance.
- You need interop with Objective-C APIs (which are class-based).
Note: In practice, the vast majority of custom types in a modern Swift app are structs. SwiftUI views, for example, are all structs.
Common Mistakes
Mutating a Struct Through let
Structs assigned to a let constant are completely immutable — you can’t change any of their properties:
// ❌ Won't compile — can't mutate a let struct
let movie = Movie(title: "Cars", year: 2006, rating: 7.1)
// movie.rating = 7.5 // Error: Cannot assign to property
// ✅ Use var if you need to modify the struct
var movie = Movie(title: "Cars", year: 2006, rating: 7.1)
movie.rating = 7.5
print("\(movie.title): \(movie.rating)/10")
Cars: 7.5/10
With classes, let only prevents you from reassigning the variable to a different object — you can still modify the
object’s properties. With structs, let locks everything down.
Unexpected Shared State with Classes
This is the flip side of reference semantics. If you expect independent copies but use a class, mutations leak across references:
// ❌ Unexpected shared state
class Recipe {
var name: String
var servings: Int
init(name: String, servings: Int) {
self.name = name
self.servings = servings
}
}
let remyRecipe = Recipe(name: "Ratatouille", servings: 4)
let linguiniRecipe = remyRecipe // Shared reference!
linguiniRecipe.servings = 2
print("Remy's servings: \(remyRecipe.servings)")
// Prints 2 — Remy's recipe was changed!
Remy's servings: 2
// ✅ Use a struct for independent copies
struct RecipeCard {
var name: String
var servings: Int
}
var remyCard = RecipeCard(name: "Ratatouille", servings: 4)
var linguiniCard = remyCard // Independent copy
linguiniCard.servings = 2
print("Remy's servings: \(remyCard.servings)")
Remy's servings: 4
When you use a struct, Linguini gets his own copy of the recipe. Remy’s original stays safe — exactly like photocopying a recipe card.
What’s Next?
- Structs and classes both group data and behavior into custom types.
- Structs are value types — copies are independent.
- Classes are reference types — copies share the same underlying object.
- Only classes support inheritance; both structs and classes can use protocols.
- Swift’s official guidance: default to structs unless you need reference semantics.
Now that you understand how to model data with structs and classes, it’s time to explore the different kinds of properties they can hold. Head to Properties in Swift to learn about stored properties, computed properties, lazy properties, and property observers.