Class vs. Struct in Swift: What are the Real Differences?

(I wrote this with Swift in mind. The core ideas carry over to other languages like C#, though the details differ.)

I’m usually asked about the difference between classes and structs in interviews, especially more recently. So I decided to study it a bit more and write a simple article about it, so I can use it as a reference to refresh my memory later. (if it sounds like another blah blah article, you can skip it and jump to How should I answer this question in an interview directly)



Introduction

If you’ve written Swift for more than a week, you’ve faced this small decision over and over: should this be a class or a struct? At first it feels like a coin flip. Both let you bundle properties and methods together, both can conform to protocols, and both can have initializers. On the surface they look almost identical.

But the choice affects how your data is copied, how it’s stored, how bugs show up, and even how your code performs. Swift’s own standard library leans heavily on structs (Int, String, Array, and Dictionary are all structs), while much of UIKit is built on classes. That split isn’t an accident. Once you understand why, the decision gets much easier.


Meet the Two: What Is a Struct and What Is a Class?

Before comparing them, let’s look at each one on its own.

What is a struct?

A struct is a way to group related values into a single, custom type. Think of it as a small container of data with some behavior attached. You define what it holds (properties) and what it can do (methods), and Swift handles the rest.

struct User {
    var name: String
    var age: Int

    func greeting() -> String {
        "Hi, I'm \(name)!"
    }
}

var user = User(name: "Reza", age: 30)
print(user.greeting()) // Hi, I'm Reza!

Notice that you didn’t write an initializer. Swift generates one for you automatically. The most important thing to remember about a struct is that it’s a value type. Every time you pass it around or assign it to a new variable, you get your own copy. It behaves a lot like a plain number or a string: if you copy 5, you don’t expect changing one copy to change the other.

How about a class?

A class is also a blueprint for a custom type with properties and methods, but it works more like a real “thing” that exists once and can be pointed to from many places.

class Player {
    var name: String
    var score: Int

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

    func addPoints(_ points: Int) {
        score += points
    }
}

let player = Player(name: "Sara", score: 0)
player.addPoints(10)

Here you have to write your own init, and you can modify score even though player is declared with let. That’s because a class is a reference type. The variable doesn’t hold the object itself, only a reference to it. Anyone else holding that same reference is looking at the very same object.

Classes also bring extra abilities that structs don’t have: they can inherit from other classes, they can be checked for identity, and they can clean up after themselves with a deinit.


Hmm OK so What are the Differences?

The biggest difference is that structs are value types and classes are reference types. Everything else follows from that.

When you assign a struct to a new variable, you get an independent copy:

struct Point {
    var x: Int
    var y: Int
}

var a = Point(x: 1, y: 2)
var b = a
b.x = 99

print(a.x) // 1
print(b.x) // 99

Changing b doesn’t touch a. Each has its own data.

With a class, both variables point to the same instance:

class Counter {
    var value = 0
}

let c1 = Counter()
let c2 = c1
c2.value = 5

print(c1.value) // 5

Here c1 and c2 are two names for one object. Change it through either and both see the change.

A few other differences are worth knowing:

  • Inheritance. Classes can inherit from other classes. Structs can’t. If you want to share behavior across structs, you use protocols and extensions instead.
  • Initializers. Structs get a free memberwise initializer. Classes don’t, so you write your own init.
  • Mutability. A struct declared with let is fully immutable, including its properties. A class declared with let only means the reference can’t change, and you can still modify the properties inside it.
  • Identity. Classes have identity. You can ask whether two variables point to the same object using ===. Structs only have equality (are the values the same?), never identity.
  • Deinitializers. Only classes have deinit, which runs when the last reference disappears. That’s useful for cleaning up resources like file handles or observers.
  • Reference counting. Classes are managed by ARC, so retain cycles and weak/unowned references are something you have to think about. Structs don’t have that problem.
  • Objective-C interop. If you need to subclass NSObject or work with Objective-C APIs, you need a class.

Where They Live in Memory

You’ll often hear the short version: structs live on the stack, classes live on the heap. It’s a good starting point, but it’s only partly true.

The stack is fast and simple. Memory is allocated and freed automatically as functions are entered and exited, with almost no overhead. The heap is more flexible, since data can outlive the function that created it, but allocating and freeing memory there is slower and has to be tracked. That’s what ARC is doing for you behind the scenes.

Class instances always live on the heap. The variable you hold is just a reference (a pointer) to it, and copying that variable copies the pointer, not the object.

Structs are often stored on the stack, or directly inline inside whatever contains them. But there are plenty of exceptions:

  • If a struct is a property of a class, it lives inside that class’s heap allocation.
  • If a closure captures a struct, it may get moved to the heap.
  • A struct with a large collection inside, like an Array, keeps the actual elements in a heap buffer. The struct itself only holds a small header pointing to it. Swift uses copy-on-write here, so copying the array is cheap until one copy is modified.

So the more accurate way to think about it is this: the value-versus-reference distinction is about semantics, meaning how your code behaves. Stack versus heap is an optimization detail the compiler is allowed to choose. Design around the semantics and let the compiler worry about placement.


When and How to Choose

Apple’s own guidance is to start with a struct and switch to a class only when you have a reason. I agree with that. Here’s how I usually decide.

Reach for a struct when:

  • The type is mostly data: a user profile, a coordinate, a network response model, a configuration.
  • Equality should be based on values. Two Point(x: 1, y: 2) instances are the same thing regardless of where they came from.
  • You want safety. Since every copy is independent, nobody can change your data from somewhere else in the app.
  • You don’t need inheritance.

Reach for a class when:

  • You need shared, mutable state, where multiple parts of the app must see the same instance and its changes. A database connection, a cache, or an audio player are good examples.
  • Identity matters. This object represents one specific thing, not just a bundle of values.
  • You need inheritance or Objective-C interoperability, which is common with UIKit (UIViewController, UIView) and other framework base classes.
  • You need deinit to clean up when the object goes away.

A quick gut-check I like is to ask: “If I copied this, would it still make sense?” A copy of a coordinate makes perfect sense. A copy of a network manager or a live camera session usually doesn’t, because there should only be one of them.

Also keep in mind that this isn’t an either/or choice for a whole app. Many well-designed codebases use structs for models and state, and classes for the few objects that own resources or coordinate work. In SwiftUI, for example, your views and most of your data are structs, while shared observable state is often a class.

One last practical warning: don’t pick a class “for performance” without measuring. Copying small structs is cheap, and heap allocation plus reference counting can actually cost more. Choose based on meaning first, then optimize if a profiler tells you to.


How should I answer it in a interview?

I would say something like:

The biggest difference is that a struct is a value type and a class is a reference type. When you copy a struct, you get a new independent copy. When you copy a class, both variables point to the same object in memory. Also, classes support inheritance and have deinitializers, but structs don’t. In my daily work, I use struct by default. I only choose class when I need shared state, identity, or inheritance.

Possible follow-up question: “What is copy-on-write, and does it apply to structs?”

Answer:

Copy-on-write means Swift doesn’t copy the data right away when you assign a collection to another variable. Both variables share the same storage until one of them changes something. Only then does Swift make a real copy. So we get value semantics, but we don’t pay the cost of copying every time. It’s built into Array, Dictionary, and String. For our own structs it doesn’t happen automatically. We need to implement it ourselves, for example with isKnownUniquelyReferenced.


Conclusion

Classes and structs look like twins, but they behave very differently because of one idea: structs are copied, and classes are shared. That single difference shapes how safe your code is, how easy it is to reason about, and how your data moves through memory.

Default to structs. They’re simpler, safer, and fit naturally with how Swift is designed. When you genuinely need shared state, identity, inheritance, or cleanup logic, switch to a class and do it on purpose. If you’re ever unsure, go back to that small question: does it make sense to copy this thing? Your answer will usually tell you what you need to know.

Leave a Reply

Your email address will not be published. Required fields are marked *