# Swift Value Semantics vs Reference Semantics Explained

Source: https://devdocs10.com/articles/swift/swift-value-vs-reference-semantics  
By Murat Bo · Published 2026-05-22 · DevDocs10 (https://devdocs10.com)

Understanding value semantics and reference semantics is one of the most important parts of learning Swift custom types. These rules affect how data is copied, how changes spread through your program, and why `struct` and `class` behave so differently. In this article, you will learn what each semantic model means, how Swift applies them, when to use each one, and how to avoid the bugs that come from choosing the wrong type.

> **Quick answer:** In Swift, types with value semantics behave like independent copies, while types with reference semantics let multiple variables refer to the same shared instance. Most `struct`, `enum`, and standard library collection types are value-oriented, while `class` instances use reference semantics.

> **Difficulty:** Beginner
>
> **Helpful to know first:** You'll understand this better if you know basic Swift syntax, variables and constants, functions, and the difference between `struct` and `class`.

## 1. What Are the Options?

This comparison is really about two different ways a type can behave when you assign it, pass it to a function, or store it in another variable.

- **Value semantics:** the data acts like a separate value. When you assign it or pass it around, each place works with its own logical copy.
- **Reference semantics:** the data acts like a shared object. Multiple variables can point to the same instance.
- In Swift, `struct` and `enum` are typically used for value semantics.
- In Swift, `class` uses reference semantics.
- This matters because it changes how mutation works and whether state is isolated or shared.

Developers often feel this difference when one variable changes and another variable unexpectedly changes too. That usually means reference semantics are involved.

## 2. Side-by-Side Comparison

| Behavior | Value Semantics | Reference Semantics |
| --- | --- | --- |
| Typical Swift type | `struct`, `enum` | `class` |
| Assignment | Creates an independent value | Copies the reference to the same instance |
| Mutation | Changes affect only that value | Changes can be seen through all references |
| Shared state | Usually avoided by default | Common and intentional |
| Equality checks | Usually value comparison with `==` | May use `==` for value equality and `===` for identity |
| Reasoning about code | Often simpler and safer | Useful for shared mutable state and identity |

## 3. Key Differences Explained

### Assignment behavior

With value semantics, assigning one variable to another gives the new variable its own value. Mutating the new variable does not change the original.

With reference semantics, assigning one variable to another does not create a new object by default. It creates another reference to the same instance.

### Mutation and isolation

Value semantics make local changes easier to understand because data is isolated. If a function receives a value type, it works with that value unless you explicitly use `inout` or return a new result.

Reference semantics allow shared mutable state. That can be useful, but it also means changes in one part of the program may affect another part unexpectedly.

### Identity

Value types usually care about what the data *is*. Reference types often care about both what the data contains and which exact instance it is.

That is why classes can be checked with `===` to ask whether two variables refer to the same instance.

### Performance model

Beginners sometimes assume value semantics always means expensive copying. In Swift, that is not necessarily true. Standard library types like `Array`, `String`, and `Dictionary` use value semantics while often relying on copy-on-write internally.

> Copy-on-write means Swift can delay making a real separate storage copy until one of the values is mutated. You still get value-style behavior, but often with efficient memory use.

### Swift == vs ===

This comparison often appears alongside value vs reference semantics.

- `==` asks whether two values are equal in content, if the type supports equality.
- `===` asks whether two class references point to the exact same instance.

You use `===` only with class instances, not with structs.

## 4. When to Use Each

Neither approach is universally better. The right choice depends on the kind of data you are modeling.

### Use value semantics when:

- The type represents data, such as a point, range, user settings, or configuration.
- You want changes to stay local and predictable.
- You want safer code with fewer shared-state bugs.
- The type should behave like an independent value when copied.

### Use reference semantics when:

- The type has identity, such as a shared manager, cache, or object that must be observed from multiple places.
- Multiple parts of the program must refer to the same mutable instance.
- You need class-only features such as inheritance or weak references.
- Sharing state is intentional and part of the design.

> A good Swift default is to start with `struct` and use `class` only when identity or shared mutable state is truly needed.

## 5. Code Examples

The examples below show how the same kind of data behaves differently depending on whether you model it as a struct or a class.

### Example 1: Struct assignment creates an independent value

Here, `Score` is a value type. After assignment, each variable behaves independently.

```swift
struct Score {
    var points: Int
}

var firstScore = Score(points: 10)
var secondScore = firstScore

secondScore.points = 50

print(firstScore.points)
print(secondScore.points)
```

Output (tested with Swift 6.4):

```text
10
50
```

This prints `10` and `50`. Changing `secondScore` does not affect `firstScore`.

### Example 2: Class assignment shares the same instance

Now the same idea is modeled with a class. Assignment copies the reference, not the underlying object.

```swift
class ScoreBoard {
    var points: Int
    
    init(points: Int) {
        self.points = points
    }
}

let boardA = ScoreBoard(points: 10)
let boardB = boardA

boardB.points = 50

print(boardA.points)
print(boardB.points)
```

Output (tested with Swift 6.4):

```text
50
50
```

This prints `50` twice because both variables refer to the same instance.

### Example 3: Passing a struct into a function

Passing a value type into a function gives the function its own copy to work with.

```swift
struct UserSettings {
    var volume: Int
}

func increaseVolume(settings: UserSettings) {
    var copy = settings
    copy.volume += 10
    print(copy.volume)
}

let settings = UserSettings(volume: 20)
increaseVolume(settings: settings)
print(settings.volume)
```

Output (tested with Swift 6.4):

```text
30
20
```

The original `settings` value remains unchanged. The function changed only its local copy.

### Example 4: Checking class identity with ===

This example shows the difference between two references that point to the same object and two separate instances with the same content.

```swift
class Document {
    var title: String
    
    init(title: String) {
        self.title = title
    }
}

let doc1 = Document(title: "Plan")
let doc2 = doc1
let doc3 = Document(title: "Plan")

print(doc1 === doc2)
print(doc1 === doc3)
```

Output (tested with Swift 6.4):

```text
true
false
```

This prints `true` and `false`. `doc1` and `doc2` are the same instance, but `doc3` is a different one.

## 6. Common Mistakes

### Mistake 1: Expecting a class assignment to make a separate copy

Many beginners assume assigning a class instance to a new variable duplicates the object. In Swift, it duplicates only the reference.

**Problem:** This code mutates shared state, so changing one variable also changes the other variable unexpectedly.

```swift
class Profile {
    var name: String
    
    init(name: String) {
        self.name = name
    }
}

let original = Profile(name: "Ava")
let copy = original
copy.name = "Liam"

print(original.name)
```

Output (tested with Swift 6.4):

```text
Liam
```

**Fix:** Use a `struct` when you want independent copies, or implement an explicit copy operation for the class.

```swift
struct Profile {
    var name: String
}

var original = Profile(name: "Ava")
var copy = original
copy.name = "Liam"

print(original.name)
```

Output (tested with Swift 6.4):

```text
Ava
```

The corrected version works because a struct assignment gives each variable its own value.

### Mistake 2: Trying to use === with a struct

The identity operator checks whether two class references point to the same instance. Structs do not have reference identity in that sense.

**Problem:** This code attempts an identity comparison on a value type, which causes a compile-time error because `===` is only for class instances.

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

let a = Point(x: 1, y: 2)
let b = a

print(a === b)
```

**Fix:** Compare structs by value, usually with `==`, after making the type conform to `Equatable`.

```swift
struct Point: Equatable {
    var x: Int
    var y: Int
}

let a = Point(x: 1, y: 2)
let b = a

print(a == b)
```

Output (tested with Swift 6.4):

```text
true
```

The corrected version works because value types are compared by their contents, not by object identity.

### Mistake 3: Thinking a let class instance is fully immutable

A constant reference means the variable cannot point to a different instance later, but the instance's mutable properties can still change if they are declared with `var`.

**Problem:** This assumption leads to confusing bugs because the class instance can still be mutated through a constant reference.

```swift
class Counter {
    var value: Int = 0
}

let counter = Counter()
counter.value = 5
```

**Fix:** Remember that `let` freezes the reference, not the internal mutable state of a class. If you need stronger immutability, use a struct with immutable properties or design the class carefully.

```swift
struct Counter {
    let value: Int
}

let counter = Counter(value: 5)
```

The corrected version works because the struct's stored property is immutable and the value is not shared through references.

### Mistake 4: Expecting a function to mutate a struct argument automatically

Passing a struct into a function does not let the function change the caller's original value unless you explicitly use `inout` or return a new value.

**Problem:** This code mutates only a local copy, so the original value outside the function stays unchanged.

```swift
struct Balance {
    var amount: Int
}

func addFunds(balance: Balance) {
    var balance = balance
    balance.amount += 100
}

var wallet = Balance(amount: 50)
addFunds(balance: wallet)
print(wallet.amount)
```

Output (tested with Swift 6.4):

```text
50
```

**Fix:** Use `inout` when the function should mutate the caller's value directly.

```swift
struct Balance {
    var amount: Int
}

func addFunds(balance: inout Balance) {
    balance.amount += 100
}

var wallet = Balance(amount: 50)
addFunds(balance: &wallet)
print(wallet.amount)
```

Output (tested with Swift 6.4):

```text
150
```

The corrected version works because `inout` allows the function to modify the caller's original value.

## 7. Tradeoffs and Edge Cases

- **Copy-on-write can hide implementation details:** types like `Array` feel like values even though Swift may optimize storage behind the scenes.
- **Reference semantics can simplify shared models:** if several objects truly need the same mutable state, a class may be the right design.
- **Value semantics can reduce bugs:** isolated data is easier to test and reason about, especially in larger codebases.
- **Classes are not always more efficient:** shared references avoid some copying, but shared mutable state can introduce complexity, synchronization concerns, and surprising side effects.
- **Structs can contain references:** a struct itself is a value type, but if it stores a class instance, some of its behavior may still involve shared reference state internally.
- **Mutating methods matter for structs:** methods that change stored properties in a struct must be marked `mutating`.
- **Identity-based logic is class-specific:** if your design depends on whether two variables point to the same exact object, a value type is probably not the right tool.

## 8. Decision Guide

If you are deciding between a value type and a reference type, use this quick guide.

- If the type mainly represents data, use `struct`.
- If copies should be independent, use `struct`.
- If multiple parts of the app must share and mutate the same instance, use `class`.
- If identity matters, such as “this exact object,” use `class`.
- If you are unsure, start with `struct` and switch to `class` only when the design clearly needs reference semantics.

> A useful mental model is this: choose a struct for data, choose a class for identity and intentional sharing.

## 9. Key Points

- Value semantics give each assignment its own independent logical value.
- Reference semantics let multiple variables point to the same class instance.
- `struct` and `enum` are the usual tools for value semantics in Swift.
- `class` uses reference semantics and supports identity checks with `===`.
- `==` compares equality of content, while `===` compares object identity.
- Copy-on-write lets many standard library value types stay efficient.
- Starting with value types usually leads to safer and more predictable code.

## 10. Final Summary

Swift value semantics and reference semantics describe how data behaves when it is assigned, passed around, and mutated. Value types such as structs usually give you independent, predictable data, while reference types such as classes let multiple variables share the same underlying instance. That difference affects everything from simple assignments to larger architectural decisions.

In practice, this topic is really about control over change. If you want isolated state and easier reasoning, value semantics are usually the better default. If you need identity and shared mutable behavior, reference semantics may be the right choice. A strong next step is to study `struct` vs `class` in more detail, then explore copy-on-write and `Equatable` to deepen your understanding of how Swift models data.
