What is the difference between a struct and a class?
TL;DR
A struct is a value type. When you assign it or pass it, you get a copy.
A class is a reference type. When you assign it, both names point to the same object.
Prefer structs by default. Use a class when you need shared identity, inheritance, or deinit.
Copy vs share
This is the main difference. Interviewers want to hear it first.
struct PointS { var x = 0 }
final class PointC { var x = 0 }
var a = PointS()
var b = a // b is a copy
b.x = 10
print(a.x) // 0
let c = PointC()
let d = c // d points to the same object
d.x = 10
print(c.x) // 10With a struct, a change in one place cannot surprise code in another place. With a class, every reference sees the change.
Side-by-side
| Struct | Class | |
|---|---|---|
| Kind | Value type | Reference type |
| Assignment | Copies the value | Copies the reference |
| Inheritance | No | Yes |
deinit | No | Yes |
Identity (===) | No | Yes |
| Memberwise init | Free | You write init |
| Memory | Usually inline / stack | Heap + ARC |
Mutating a let | Not allowed | Allowed (properties are var) |
Sendable | Easy (if all members are) | Hard (needs final + immutable state) |
let means different things
A let struct is fully frozen. A let class only freezes the reference, not the object.
let p = PointS()
// p.x = 5 // error: p is a let constant
let q = PointC()
q.x = 5 // fine: the object is still mutableMethods that change a struct need mutating
struct Counter {
var value = 0
mutating func increment() { value += 1 }
}A class method can change properties without any keyword, because self is a reference.
Memory and performance
Class instances live on the heap. Swift counts references to them with ARC (Automatic Reference Counting). That adds a small cost on every copy of the reference.
Structs are often stored inline (on the stack or inside another value). No reference counting is needed if they only hold simple values.
Careful: a struct that holds class references (or String, Array) still does reference counting for those parts.
When to pick a class
- You need shared, mutable state that many parts of the app see (for example, a cache or a session).
- You need identity: "is this the same object?" (
===). - You need inheritance, for example subclassing
UIViewController. - You need
deinitto clean up resources. - An API needs it:
@Observablemodels,NSObjectsubclasses, Objective-C interop.
@Observable
final class CartModel {
var items: [Item] = []
}When to pick a struct
- Data models:
User,Message,Settings. - Things that are compared by value, not identity.
- Anything you send between threads or tasks. Structs become
Sendableeasily.
Apple's guidance is simple: start with a struct, move to a class only when you have a reason.
Common mistakes
- Saying "structs are always on the stack". Not true. A struct inside an array or captured by an escaping closure lives on the heap.
- Putting a class inside a struct and expecting value semantics. The inner object is still shared.
- Forgetting
finalon classes that are not subclassed.finalmakes method calls faster and intent clearer.
Common follow-up questions
- Are enums value types? Yes. Enums, tuples, structs,
Array,StringandDictionaryare all value types. - Is copying a big struct slow? Not usually.
Array,StringandDictionaryuse copy-on-write, so the real copy only happens on mutation. - Can a struct have a
deinit? Only a noncopyable struct (~Copyable). Normal structs cannot. - Why is SwiftUI
Viewa struct? Views are cheap descriptions that SwiftUI creates and throws away often. Value types make that fast and safe. - What is an actor? A reference type like a class, but it protects its state from data races. Use it for shared mutable state across tasks.