iosNudge

What is the difference between a struct and a class?

EasyAsked oftenSwift 3 min read

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)       // 10

With a struct, a change in one place cannot surprise code in another place. With a class, every reference sees the change.

Side-by-side

StructClass
KindValue typeReference type
AssignmentCopies the valueCopies the reference
InheritanceNoYes
deinitNoYes
Identity (===)NoYes
Memberwise initFreeYou write init
MemoryUsually inline / stackHeap + ARC
Mutating a letNot allowedAllowed (properties are var)
SendableEasy (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 mutable

Methods 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 deinit to clean up resources.
  • An API needs it: @Observable models, NSObject subclasses, 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 Sendable easily.

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 final on classes that are not subclassed. final makes method calls faster and intent clearer.

Common follow-up questions

  • Are enums value types? Yes. Enums, tuples, structs, Array, String and Dictionary are all value types.
  • Is copying a big struct slow? Not usually. Array, String and Dictionary use 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 View a 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.