# Swift Identity Operators (===, !==): Compare Object References

Source: https://devdocs10.com/articles/swift/swift-identity-operators  
By Murat Bo · Published 2026-05-01 · DevDocs10 (https://devdocs10.com)

Swift identity operators, `===` and `!==`, are used to check whether two variables refer to the exact same class instance in memory. This is different from checking whether two values merely contain the same data. In this tutorial, you will learn what identity means in Swift, how these operators work, when to use them, and how to avoid confusing identity with equality when working with reference types.

## 1. What Is Identity in Swift?

In Swift, identity answers a very specific question: do two references point to the same object instance? The identity operators are only for reference types, which means class instances.

- `===` returns `true` when two references point to the same class instance.
- `!==` returns `true` when two references point to different class instances.
- Identity is not the same as value equality.
- Identity operators do not work with structs, enums, strings, or integers.

Swift has both value types and reference types. A struct is copied when assigned, but a class instance is shared by reference. Identity operators exist because with reference types, two variables may refer to one shared object.

> If you need to know whether two things contain the same data, use equality such as `==`. If you need to know whether they are literally the same object, use `===`.

## 2. Why Identity Operators Matter

Identity checks matter when your program stores, passes, and mutates class instances in multiple places. In these cases, two variables may show the same property values, but they may still be different objects. That difference affects updates, caching, shared state, and object tracking.

You should reach for identity operators when you need to confirm shared object references, such as checking whether a cached instance was reused, whether two properties point to the same model object, or whether an item in a graph structure is the exact node you expect.

You should not use identity operators when comparing value types or when your real goal is content comparison. For example, two separate `User` objects with the same ID and name may represent the same real-world user, but identity would still report them as different objects unless they are the same instance.

## 3. Basic Syntax

The core idea is simple: create a class, store an instance in one or more variables, and then compare those references with `===` or `!==`.

### Minimal syntax

This example shows the smallest useful form of an identity comparison.

```swift
class Person {
    let name: String

    init(name: String) {
        self.name = name
    }
}

let first = Person(name: "Maya")
let second = first
let third = Person(name: "Maya")

print(first === second)
print(first === third)
print(first !== third)
```

Output (tested with Swift 6.4):

```text
true
false
true
```

Here is what each part means:

- `first` stores a new `Person` object.
- `second = first` does not create a copy. Both references point to the same object.
- `third` is a new object with the same property value.
- `first === second` is `true` because they are the same instance.
- `first === third` is `false` because they are different instances.
- `first !== third` is `true` for the same reason.

This example shows the central rule: identity compares references, not data.

### Identity works only with classes

If you try to use identity operators with value types like structs, Swift will reject the code because value types do not have reference identity in this sense.

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

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

// a === b  // Error: identity operators apply only to class instances
```

This is why identity is tied to reference semantics in Swift.

## 4. Step-by-Step Examples

### Example 1: Two variables sharing one class instance

This first example demonstrates the most direct identity check. Both variables refer to the same object, so changing one reference affects the same underlying instance.

```swift
class Document {
    var title: String

    init(title: String) {
        self.title = title
    }
}

let original = Document(title: "Report")
let sharedReference = original

sharedReference.title = "Updated Report"

print(original.title)
print(original === sharedReference)
```

Output (tested with Swift 6.4):

```text
Updated Report
true
```

The output shows that the title changed through both references, and `original === sharedReference` is `true`. That confirms both variables point to the same object.

### Example 2: Same data, different instances

Now compare two separate objects that happen to store identical values. Their contents match, but their identities do not.

```swift
class Product {
    let id: Int
    let name: String

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

let p1 = Product(id: 101, name: "Keyboard")
let p2 = Product(id: 101, name: "Keyboard")

print(p1 === p2)
print(p1 !== p2)
```

Output (tested with Swift 6.4):

```text
false
true
```

Even though both products look the same, they are different instances. The first print is `false` and the second is `true`.

### Example 3: Checking whether a cached object was reused

Identity operators are especially useful in caching. You may want to know whether a method returned the already stored instance or created a new one.

```swift
class ImageResource {
    let name: String

    init(name: String) {
        self.name = name
    }
}

class ImageCache {
    private var storage: [String: ImageResource] = [:]

    func loadImage(named: String) -> ImageResource {
        if let cached = storage[named] {
            return cached
        }

        let newImage = ImageResource(name: named)
        storage[named] = newImage
        return newImage
    }
}

let cache = ImageCache()
let firstLoad = cache.loadImage(named: "logo")
let secondLoad = cache.loadImage(named: "logo")

print(firstLoad === secondLoad)
```

Output (tested with Swift 6.4):

```text
true
```

The result is `true`, which confirms the cache returned the same object instead of creating another one.

### Example 4: Comparing optional class references

In real code, class references are often optional. Swift lets you compare optional class references with identity operators as long as the wrapped type is a class.

```swift
class Session {
    let token: String

    init(token: String) {
        self.token = token
    }
}

let activeSession: Session? = Session(token: "abc123")
let sameSession = activeSession
let newSession: Session? = Session(token: "abc123")

print(activeSession === sameSession)
print(activeSession === newSession)
```

Output (tested with Swift 6.4):

```text
true
false
```

The first comparison is `true` because both optionals wrap the same object. The second is `false` because the instances are different.

## 5. Practical Use Cases

- Checking whether a cache returned the exact same class instance that was stored earlier.
- Verifying that two properties in an object graph point to the same shared node.
- Preventing duplicate processing when the same class instance appears in multiple collections.
- Testing whether a delegate, manager, or controller reference is the exact object expected.
- Detecting whether a factory or singleton-style provider is reusing an existing object.
- Comparing parent and child references in tree or graph structures where instance identity matters more than matching values.

## 6. Common Mistakes

### Mistake 1: Using identity when you really need equality

Many developers compare object references when they actually want to compare contents. That gives the wrong answer when separate objects have the same data.

**Warning:** Bad code: using identity to compare matching data.

```swift
class City {
    let name: String

    init(name: String) {
        self.name = name
    }
}

let c1 = City(name: "Paris")
let c2 = City(name: "Paris")

print(c1 === c2)
```

Output (tested with Swift 6.4):

```text
false
```

This prints `false` because the objects are different instances, even though the names match.

The correct approach is to define equality based on the data you care about.

```swift
class City: Equatable {
    let name: String

    init(name: String) {
        self.name = name
    }

    static func == (lhs: City, rhs: City) -> Bool {
        lhs.name == rhs.name
    }
}

let c1 = City(name: "Paris")
let c2 = City(name: "Paris")

print(c1 == c2)
```

Output (tested with Swift 6.4):

```text
true
```

This version compares values instead of object identity.

### Mistake 2: Trying to use identity operators with structs

Identity only applies to class instances. Structs are value types, so identity operators are not valid.

**Warning:** Bad code: identity comparison on a struct.

```swift
struct User {
    let id: Int
}

let u1 = User(id: 1)
let u2 = User(id: 1)

// print(u1 === u2) // Error
```

The correct version compares values instead.

```swift
struct User: Equatable {
    let id: Int
}

let u1 = User(id: 1)
let u2 = User(id: 1)

print(u1 == u2)
```

Output (tested with Swift 6.4):

```text
true
```

This is the correct mental model: value types use equality, not identity.

### Mistake 3: Assuming assignment creates a new class instance

With classes, assignment copies the reference, not the object. Developers sometimes expect a new independent instance and are surprised when identity is still true.

**Warning:** Bad assumption: assignment creates a separate object.

```swift
class Counter {
    var value: Int

    init(value: Int) {
        self.value = value
    }
}

let counter1 = Counter(value: 0)
let counter2 = counter1

counter2.value = 10
print(counter1.value)
```

Output (tested with Swift 6.4):

```text
10
```

This prints `10` because both variables refer to the same object.

If you need a separate instance, create one explicitly.

```swift
class Counter {
    var value: Int

    init(value: Int) {
        self.value = value
    }
}

let counter1 = Counter(value: 0)
let counter2 = Counter(value: counter1.value)

print(counter1 === counter2)
```

Output (tested with Swift 6.4):

```text
false
```

This prints `false`, showing the objects are distinct.

## 7. Best Practices

### Use identity only for reference-semantics questions

Choose `===` when your question is about shared object instances, not matching content. This keeps your intent clear to other developers.

```swift
class Manager {
    let id: Int

    init(id: Int) {
        self.id = id
    }
}

let primaryManager = Manager(id: 7)
let currentManager = primaryManager

if currentManager === primaryManager {
    print("Using the exact same manager instance.")
}
```

Output (tested with Swift 6.4):

```text
Using the exact same manager instance.
```

This is a good use of identity because the code is checking whether both variables refer to the same manager object.

### Define `Equatable` when data comparison matters

If your class needs content-based comparisons, implement `==` separately. That prevents misuse of identity operators in business logic.

```swift
class Book: Equatable {
    let isbn: String
    let title: String

    init(isbn: String, title: String) {
        self.isbn = isbn
        self.title = title
    }

    static func == (lhs: Book, rhs: Book) -> Bool {
        lhs.isbn == rhs.isbn
    }
}

let book1 = Book(isbn: "978-1", title: "Swift Basics")
let book2 = Book(isbn: "978-1", title: "Swift Basics")

print(book1 == book2)
print(book1 === book2)
```

Output (tested with Swift 6.4):

```text
true
false
```

This is best practice because the code distinguishes between equal content and identical instances.

### Use identity in graph, cache, and shared-state logic

Identity is most valuable where object sharing is part of the design. In these situations, checking identity makes the program safer and easier to reason about.

```swift
class Node {
    let value: Int
    var next: Node?

    init(value: Int) {
        self.value = value
    }
}

let tail = Node(value: 3)
let head = Node(value: 1)
let middle = Node(value: 2)

head.next = middle
middle.next = tail

if middle.next === tail {
    print("Middle points to the exact tail node.")
}
```

Output (tested with Swift 6.4):

```text
Middle points to the exact tail node.
```

This pattern is appropriate because linked structures depend on exact node relationships, not just matching values.

## 8. Limitations and Edge Cases

- Identity operators work only with class instances and optional class references, not with structs, enums, tuples, or primitive values.
- Two different objects can hold exactly the same property values and still fail an identity comparison.
- Assignment of a class instance creates another reference to the same object, not a cloned object.
- If your class conforms to `Equatable`, `==` and `===` can legitimately produce different results.
- Identity says nothing about whether two objects should be considered logically equivalent in your business model.
- When working with optionals, identity comparisons are valid only if the wrapped type is a class type.

## 9. Practical Mini Project

Let us build a small cache-based profile loader. The goal is to show how identity operators can confirm whether repeated requests return the exact same user profile instance from a cache.

The full program below creates a `UserProfile` class and a `ProfileStore` that reuses instances for the same ID.

```swift
class UserProfile: Equatable {
    let id: Int
    var name: String

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

    static func == (lhs: UserProfile, rhs: UserProfile) -> Bool {
        lhs.id == rhs.id && lhs.name == rhs.name
    }
}

class ProfileStore {
    private var cache: [Int: UserProfile] = [:]

    func profile(for id: Int, name: String) -> UserProfile {
        if let existingProfile = cache[id] {
            return existingProfile
        }

        let newProfile = UserProfile(id: id, name: name)
        cache[id] = newProfile
        return newProfile
    }
}

let store = ProfileStore()

let profileA = store.profile(for: 1, name: "Nina")
let profileB = store.profile(for: 1, name: "Nina")
let profileC = UserProfile(id: 1, name: "Nina")

print("A and B are the same instance:", profileA === profileB)
print("A and C are the same instance:", profileA === profileC)
print("A and C have equal data:", profileA == profileC)

profileB.name = "Nina Updated"
print("profileA name after updating profileB:", profileA.name)
```

Output (tested with Swift 6.4):

```text
A and B are the same instance: true
A and C are the same instance: false
A and C have equal data: true
profileA name after updating profileB: Nina Updated
```

This mini project demonstrates three important ideas. First, the cache returns the same object for the same user ID, so `profileA === profileB` is `true`. Second, a manually created profile with the same data is not the same instance, so `profileA === profileC` is `false`. Third, updating `profileB` also affects `profileA` because they refer to the same shared object.

## 10. Key Points

- `===` checks whether two references point to the same class instance.
- `!==` checks whether two references point to different class instances.
- Identity is different from equality and should not be used for content comparison.
- Identity operators work only with reference types such as classes.
- Assigning a class instance to another variable copies the reference, not the object.
- Separate objects can contain identical data and still fail an identity comparison.
- Identity is especially useful in caches, graphs, delegates, and shared-state systems.

## 11. Practice Exercise

- Create a `class` named `Playlist` with a mutable `title` property.
- Create one instance named `mainPlaylist`.
- Assign it to another variable named `backupReference`.
- Create a separate instance named `copiedPlaylist` with the same title value.
- Print whether `mainPlaylist` and `backupReference` are the same instance.
- Print whether `mainPlaylist` and `copiedPlaylist` are the same instance.
- Change the title through `backupReference` and print `mainPlaylist.title`.
- Expected output: the first identity check should be `true`, the second should be `false`, and the final printed title should reflect the updated value.
- Hint: remember that classes are reference types, so assignment shares the same object.

```swift
class Playlist {
    var title: String

    init(title: String) {
        self.title = title
    }
}

let mainPlaylist = Playlist(title: "Morning Mix")
let backupReference = mainPlaylist
let copiedPlaylist = Playlist(title: "Morning Mix")

print(mainPlaylist === backupReference)
print(mainPlaylist === copiedPlaylist)

backupReference.title = "Evening Mix"
print(mainPlaylist.title)
```

Output (tested with Swift 6.4):

```text
true
false
Evening Mix
```

## 12. Final Summary

Swift identity operators help you answer a narrow but important question: are these two references pointing to the exact same class object? That is why `===` and `!==` are only for class instances and not for value types like structs or enums. Once you understand the difference between identity and equality, many confusing behaviors around shared state become easier to reason about.

In this article, you learned the syntax of identity operators, saw how they behave with shared references and separate instances, explored common mistakes, and used identity in realistic patterns such as caching and object graphs. This topic connects directly to Swift's larger model of value semantics versus reference semantics, so mastering it will make your class-based code more predictable and safer to design.

As a next step, practice combining `===` with custom `Equatable` implementations so you can clearly separate object identity from business-level equality in your own Swift programs.
