# Faster Equatable and Hashable conformances

Compiler-synthesized conformance in Swift is a powerful tool that accelerates development by reducing boilerplate code. For protocols like `Equatable` and `Hashable`, the compiler can automatically generate the required implementations—a convenience that is widely utilized. However, this automation, like any abstraction, comes with trade-offs. The default behavior can introduce significant and non-obvious performance bottlenecks, particularly in systems that rely on a clear concept of object identity.

This analysis examines the default synthesis mechanism, identifies its performance characteristics, and proposes an architectural pattern for specific use cases where performance is critical.

### The Performance Cost of Synthesized Conformance

When the Swift compiler synthesizes conformance for `Equatable` and `Hashable`, it applies a simple, universal rule: every stored property of the type is included in the comparison or hashing logic. For a `struct` with a few value-type properties, the overhead is often negligible. However, for more complex data structures, the cost can become substantial.

Consider a model that includes a collection:

```swift
struct Company {
    let id: UUID
    let name: String
    let address: Address // Address might be a complex struct
    let employees: [User] // A potentially large collection
}
```

If `Company` conforms to `Equatable`, a comparison between two instances (`companyA == companyB`) will trigger a member-wise comparison of all properties. This includes iterating through the entire `employees` array and comparing each `User` object. Such an operation's complexity is at least O(N), where N is the number of elements in the collection, and can be worse depending on the complexity of the elements themselves.

In performance-sensitive contexts, such as the diffing algorithms used to update SwiftUI views, these O(N) comparisons can become a significant performance hotspot, leading to dropped frames and unresponsive user interfaces. The same complexity issue applies to `Hashable` conformance, affecting the performance of `Set` and `Dictionary` operations.

### The `Identifiable` Protocol and Value Equality

Modern Swift development, particularly with frameworks like SwiftUI and Combine, heavily utilizes the `Identifiable` protocol.

```swift
public protocol Identifiable {
    associatedtype ID: Hashable
    var id: ID { get }
}
```

The `Identifiable` protocol asserts that a type has a single, canonical property representing its stable identity. If two instances share the same `id`, they represent the same conceptual entity, even if their other properties differ (e.g., a mutable state that has been updated).

The compiler's synthesized `Equatable` implementation, however, checks every property for *value equality*. This is distinct from identity. The default behavior is not wrong, but in cases where a full member-wise comparison isn't needed, it can be computationally expensive.

### Verifying the Compiler's Behavior

An analysis of the [Swift open-source repository](https://github.com/swiftlang/swift) confirms that the derivation logic for `Equatable` and `Hashable` is generalized and does not account for the semantic meaning of `Identifiable`.

The relevant logic resides in the compiler source file [`DerivedConformanceEquatableHashable.cpp`](https://github.com/swiftlang/swift/blob/main/lib/Sema/DerivedConformance/DerivedConformanceEquatableHashable.cpp). When synthesizing conformance, the compiler's primary concern is to verify that all of a type's stored properties themselves conform to `Hashable` before generating the combined hash.

```swift
// Refuse to synthesize Hashable if type isn't a struct or enum, or if it
// has non-Hashable stored properties/associated values.
auto hashableProto = Context.getProtocol(KnownProtocolKind::Hashable);
if (!canDeriveConformance(getConformanceContext(), Nominal,
                          hashableProto)) {
  // ...error handling...
}
// see: https://github.com/swiftlang/swift/blob/main/lib/Sema/DerivedConformanceEquatableHashable.cpp
```

The same is true for `Equatable`. As the source demonstrates, the validation step is mechanical: it checks for protocol conformance on all members. It does not contain any special logic to detect if the type also conforms to `Identifiable` and, if so, use the `id` property as the sole source for hashing and equality.

The compiler will always default to its member-wise synthesis unless a more specific implementation—like the one provided by our extensions—is available to satisfy the protocol requirements first.

**A note on SwiftUI’s Equatable usage**

SwiftUI's rendering efficiency is deeply tied to the `Equatable` protocol. When your model or state type conforms to `Equatable`, SwiftUI can perform a quick check, `oldItem == newItem`, to determine if a value has actually changed. If items are equal, it smartly skips re-rendering the associated view, leading to significantly better performance by only updating what's absolutely necessary. Conversely, if your model does not conform to `Equatable`, SwiftUI lacks this comparison mechanism. In such types do not use this technique with Identifiable as changes on varying parameters won’t be reflected to view.

To ensure data consistency, SwiftUI must assume that the item might have changed and will consequently re-render the view for that item, or even the entire view hierarchy, every time, leading to unnecessary redraws and reduced performance. Therefore, understanding and leveraging `Equatable` correctly is crucial for building performant SwiftUI applications.

### Performance Benchmarks: The Numbers

To quantify the performance characteristics of both approaches, I created a custom benchmarking suite implemented directly in `main.swift`. The tests were conducted on a `Company` model containing over 1,000 employees, each with nested profile information.

| Test Case | Time (ms) |
| --- | --- |
| 1\. Equatable w/ Identifiable | 1.22 |
| 2\. Equatable (Synthesized) | 3.83 |
| 3\. Equatable (Synthesized, Identical) | 11.61 |
| 4\. Hashable w/ Identifiable | 6.44 |
| 5\. Hashable (Synthesized) | 195,413.01 |

### Performance Comparison

* **Synthesized Equatable is 3x slower** than the `Identifiable`\-based implementation.
    
* **Synthesized Hashable is 30K times slower** than the `Identifiable`\-based implementation.
    

### Test Configuration:

* **Test Object**: `Company` with 1,000+ employees
    
* **Nesting**: 3 levels deep (`Company` → `Employees` → `Profile`)
    
* **Iterations**: 1,000,000 (for all tests)
    
* **Environment**: Release build, MacBook Air M4, macOS Tahoe 26.0
    
* **Benchmark Code**: [https://github.com/erkekin/benchmark-equatable-and-hashable-with-id](https://github.com/erkekin/benchmark-equatable-and-hashable-with-id)
    

The results demonstrate that the `Identifiable`\-based implementation maintains consistent O(1) performance by focusing solely on the `id` property, while the synthesized implementation processes every property in the object graph. This difference becomes particularly relevant in performance-sensitive contexts like SwiftUI's diffing algorithms or when working with large collections in `Set` or `Dictionary` types.

**References**

[https://github.com/swiftlang/swift-evolution/blob/main/proposals/0185-synthesize-equatable-hashable.md](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0185-synthesize-equatable-hashable.md)

[https://stackoverflow.com/questions/64279211/is-it-right-to-conform-hashable-by-only-taking-id-into-consideration](https://stackoverflow.com/questions/64279211/is-it-right-to-conform-hashable-by-only-taking-id-into-consideration)
