Swift Protocols That Look the Same But Do Completely Different Things
Master the three essential protocols that let you compare, sort, and store custom types — with automatic synthesis that’ll make you wonder…
Swift Essentials

🤔 The Confusion: They All Sound the Same
Okay so when I first saw Equatable, Comparable, and Hashable in Swift docs, my brain went “aren’t these basically the same thing?”
I mean look at the names. They all end in “-able.” They all seem to be about… comparing stuff? Checking if things are the same? Something like that?
Turns out they’re completely different. And using the wrong one (or not using one when you should) leads to some really confusing compiler errors.
Here’s what happened to me early on. I made a simple struct:
struct GamePlayer {
let playerID: String
let username: String
let score: Int
}Seemed fine. Then I tried to compare two players:
let player1 = GamePlayer(playerID: "abc123", username: "ProGamer", score: 1500)
let player2 = GamePlayer(playerID: "xyz789", username: "NoobSlayer", score: 1200)
if player1 == player2 { // ❌ Binary operator '==' cannot be applied
print("Same player")
}Compiler error. “But why?” I thought. “They’re both GamePlayer instances, just compare them!”
Then I tried putting players in a Set:
var playerSet: Set<GamePlayer> = [] // ❌ Type 'GamePlayer' does not conform to protocol 'Hashable'Another error. Now it wants Hashable?
Then I tried sorting an array of players:
let players = [player1, player2]
let sorted = players.sorted() // ❌ Referencing instance method 'sorted(by:)' requires that 'GamePlayer' conform to 'Comparable'ANOTHER error! This time Comparable!
So which protocol do I need? All three? Just one? What’s the difference?
Yeah, that confusion is super common. Let’s clear it up.
📺 I’m putting together a video breaking down these protocols with real examples. Check it out on Swift Pal when it drops: _https://youtube.com/@swift-pal_
**Swift Pal** _👨💻 Welcome to Swift Pal - Your iOS Dev Companion! Whether you're just starting your journey with Swift and SwiftUI…_youtube.comhttps://youtube.com/@swift-pal
⚖️ Equatable: Can Two Things Be Equal?
Equatable is the simplest one. It answers one question: “Are these two things equal?”
That’s it. When you make a type Equatable, you’re saying “here’s how to check if two instances are the same.”
Basic implementation:
struct GamePlayer: Equatable {
let playerID: String
let username: String
let score: Int
}
let player1 = GamePlayer(playerID: "abc123", username: "ProGamer", score: 1500)
let player2 = GamePlayer(playerID: "abc123", username: "ProGamer", score: 1500)
let player3 = GamePlayer(playerID: "xyz789", username: "NoobSlayer", score: 1200)
print(player1 == player2) // true - all properties match
print(player1 == player3) // false - different valuesNotice I just added : Equatable and it worked? That's because Swift automatically synthesizes the implementation for you. It checks if ALL stored properties are equal.
But what if you want custom equality logic?
Maybe you only care about the playerID:
struct GamePlayer: Equatable {
let playerID: String
let username: String
let score: Int
static func == (lhs: GamePlayer, rhs: GamePlayer) -> Bool {
return lhs.playerID == rhs.playerID
}
}
let player1 = GamePlayer(playerID: "abc123", username: "ProGamer", score: 1500)
let player2 = GamePlayer(playerID: "abc123", username: "DifferentName", score: 2000)
print(player1 == player2) // true! Only playerID mattersSee that static func ==? That's what you implement when you need custom equality. The parameters are typically called lhs and rhs (left-hand side, right-hand side).
When you get automatic synthesis:
Swift gives you Equatable for free when:
- All stored properties are already Equatable
- You’re using a struct or enum
- Properties aren’t computed or lazy
struct Point: Equatable {
let x: Int
let y: Int
}
// Works automatically - both Int properties are Equatable
struct Rectangle: Equatable {
let topLeft: Point
let bottomRight: Point
}
// Also works - Point is Equatable, so Rectangle gets it freeClasses need a bit more work:
class Vehicle: Equatable {
let vehicleID: String
let manufacturer: String
init(vehicleID: String, manufacturer: String) {
self.vehicleID = vehicleID
self.manufacturer = manufacturer
}
static func == (lhs: Vehicle, rhs: Vehicle) -> Bool {
return lhs.vehicleID == rhs.vehicleID &&
lhs.manufacturer == rhs.manufacturer
}
}Classes don’t get automatic synthesis. You gotta write the == function yourself.
What you get with Equatable:
Once your type is Equatable, you can:
- Use
==and!=operators - Use
contains()on arrays - Remove duplicates with logic you define
- Check if arrays/sets contain specific instances
let allPlayers = [player1, player2, player3]
if allPlayers.contains(player1) {
print("Found the player")
}
// Find first match
if let found = allPlayers.first(where: { $0 == player2 }) {
print("Located: \(found.username)")
}That’s Equatable. Just equality checking. Nothing fancy.
📊 Comparable: Which One is Bigger?
Comparable builds on Equatable. It’s for ordering things — figuring out which comes first, which is greater, which is smaller.
Think sorting. Ranking. Ordering by priority.
Here’s the key: Comparable requires Equatable. You can’t have Comparable without Equatable. Makes sense — to know if A < B, you also need to know if A == B.
struct GamePlayer: Comparable {
let playerID: String
let username: String
let score: Int
// Comparable requires you implement <
static func < (lhs: GamePlayer, rhs: GamePlayer) -> Bool {
return lhs.score < rhs.score
}
}
let player1 = GamePlayer(playerID: "abc123", username: "ProGamer", score: 1500)
let player2 = GamePlayer(playerID: "xyz789", username: "NoobSlayer", score: 1200)
let player3 = GamePlayer(playerID: "def456", username: "EliteGamer", score: 2000)
print(player1 < player2) // false (1500 > 1200)
print(player2 < player3) // true (1200 < 2000)
// Now you can sort!
let players = [player1, player2, player3]
let rankedPlayers = players.sorted()
// Sorted by score: [player2 (1200), player1 (1500), player3 (2000)]
for (index, player) in rankedPlayers.enumerated() {
print("\(index + 1). \(player.username) - \(player.score) points")
}You only have to implement <. Swift figures out >, <=, and >= for you based on that.
Wait, but I also need Equatable?
Actually, when you conform to Comparable, you need to also specify Equatable. But Swift helps:
struct GamePlayer: Comparable {
let playerID: String
let username: String
let score: Int
// For Comparable
static func < (lhs: GamePlayer, rhs: GamePlayer) -> Bool {
return lhs.score < rhs.score
}
// For Equatable (required by Comparable)
static func == (lhs: GamePlayer, rhs: GamePlayer) -> Bool {
return lhs.playerID == rhs.playerID
}
}Though honestly, a lot of times you can just let Swift synthesize the == part if it makes sense to compare all properties.
More complex sorting:
What if you want to sort by score first, then by username if scores are tied?
struct GamePlayer: Comparable {
let playerID: String
let username: String
let score: Int
static func < (lhs: GamePlayer, rhs: GamePlayer) -> Bool {
if lhs.score != rhs.score {
return lhs.score < rhs.score
}
return lhs.username < rhs.username
}
}Now players with the same score get sorted alphabetically by username.
What you get with Comparable:
- All comparison operators:
<,>,<=,>= - Automatic sorting with
.sorted() - Min/max operations
- Range checks
let players = [player1, player2, player3]
let topPlayer = players.max() // Returns player with highest score
let bottomPlayer = players.min() // Returns player with lowest score
// Check if in range
let midTierPlayer = player1
if midTierPlayer > player2 && midTierPlayer < player3 {
print("Mid-tier player")
}Important: Comparable is specifically for types that have a natural ordering. Not everything should be Comparable. Like, is one image “less than” another? Doesn’t really make sense unless you define it (by file size? dimensions? creation date?).
🔑 Hashable: The Dictionary/Set Enabler
Alright, Hashable is the weird one. It’s not about comparing values directly — it’s about generating a hash value.
“What’s a hash value?” Basically a number that represents your instance. Used internally by Sets and Dictionaries for fast lookups.
Here’s why it matters:
Sets and Dictionaries need to quickly check “is this item already here?” Instead of comparing your object to every single item in the collection (slow), they use hash values for instant lookups.
struct GamePlayer: Hashable {
let playerID: String
let username: String
let score: Int
}
// Now this works!
var uniquePlayers: Set<GamePlayer> = []
let player1 = GamePlayer(playerID: "abc123", username: "ProGamer", score: 1500)
let player2 = GamePlayer(playerID: "abc123", username: "ProGamer", score: 1500)
let player3 = GamePlayer(playerID: "xyz789", username: "NoobSlayer", score: 1200)
uniquePlayers.insert(player1)
uniquePlayers.insert(player2) // Won't be added - same as player1
uniquePlayers.insert(player3)
print(uniquePlayers.count) // 2 (player1 and player2 are duplicates)Hashable requires Equatable.
Why? Because after Swift checks hash values, it still needs to confirm with == that items are actually equal. Hash values can collide (different items with same hash), so the final check uses equality.
The hierarchy:
- Hashable requires Equatable
- Comparable requires Equatable
- Equatable stands alone
struct GamePlayer: Hashable { // Automatically makes it Equatable too
let playerID: String
let username: String
let score: Int
}Just like Equatable, Swift auto-synthesizes Hashable if all your properties are Hashable.
Custom hashing:
Sometimes you want to hash based on specific properties only:
struct GamePlayer: Hashable {
let playerID: String
let username: String
let score: Int
func hash(into hasher: inout Hasher) {
hasher.combine(playerID)
// Only hash playerID, ignore username and score
}
static func == (lhs: GamePlayer, rhs: GamePlayer) -> Bool {
return lhs.playerID == rhs.playerID
}
}Now two players with the same ID but different scores/usernames are considered identical.
Using as Dictionary keys:
struct GamePlayer: Hashable {
let playerID: String
let username: String
let score: Int
}
var playerStats: [GamePlayer: [String]] = [:]
let player1 = GamePlayer(playerID: "abc123", username: "ProGamer", score: 1500)
playerStats[player1] = ["Win", "Win", "Loss", "Win"]
if let stats = playerStats[player1] {
print("Player stats: \(stats)")
}Can’t do this without Hashable. Dictionaries require Hashable keys.
What you get with Hashable:
- Can be used as Dictionary keys
- Can be stored in Sets
- Fast contains/lookup operations
- Duplicate removal in collections
let player1 = GamePlayer(playerID: "abc123", username: "ProGamer", score: 1500)
let player2 = GamePlayer(playerID: "xyz789", username: "NoobSlayer", score: 1200)
let player3 = GamePlayer(playerID: "abc123", username: "ProGamer", score: 1500) // duplicate
let allPlayers = [player1, player2, player3]
let uniquePlayers = Set(allPlayers) // Automatically removes player3 duplicate
print(uniquePlayers.count) // 2🔗 How They Relate to Each Other
This is where it clicks. These protocols have a hierarchy:
Equatable (base)
↓
Hashable (needs Equatable)
Equatable (base)
↓
Comparable (needs Equatable)Equatable is the foundation. It’s just “can you check if two things are equal?”
Hashable builds on Equatable. It adds “can you generate a hash value?” Needs Equatable because after hash comparison, Swift double-checks with ==.
Comparable also builds on Equatable. It adds “can you order these things?” Needs Equatable because ordering implies equality (if A == B, then A is not < B or > B).
Can you have all three?
Absolutely. And it’s actually common:
struct GamePlayer: Hashable, Comparable {
let playerID: String
let username: String
let score: Int
// Comparable requirement
static func < (lhs: GamePlayer, rhs: GamePlayer) -> Bool {
return lhs.score < rhs.score
}
// Equatable (auto-synthesized)
// Hashable (auto-synthesized)
}
let player1 = GamePlayer(playerID: "abc123", username: "ProGamer", score: 1500)
let player2 = GamePlayer(playerID: "xyz789", username: "NoobSlayer", score: 1200)
// Equatable
print(player1 == player2) // false
// Comparable
print(player1 > player2) // true
// Hashable - use in Set
var players: Set<GamePlayer> = [player1, player2]
// Comparable - sort
let sorted = [player1, player2].sorted()One type, all three protocols. Now it can be compared, sorted, hashed, stored in sets, used as dictionary keys — everything.
When do you need which?
- Just comparing for equality? → Equatable
- Need to sort/order? → Comparable (which includes Equatable)
- Need to use in Set or as Dictionary key? → Hashable (which includes Equatable)
- Need all the things? → Hashable + Comparable
Most of the time, if you’re making a data model, just conform to Hashable and Comparable. Gives you maximum flexibility.
⚡ Automatic Synthesis (The Magic)
Swift does a ton of work for you with these protocols. You don’t always have to write the implementation.
Equatable auto-synthesis works when:
✅ All stored properties are Equatable
✅ It’s a struct or enum
✅ Properties aren’t computed
struct Address: Equatable { // Just add the protocol
let street: String
let city: String
let zipCode: String
}
// Swift writes the == implementation for you!Hashable auto-synthesis works when:
✅ All stored properties are Hashable
✅ It’s a struct or enum
✅ Type conforms to Equatable (or will auto-conform)
struct Address: Hashable { // Swift handles everything
let street: String
let city: String
let zipCode: String
}Comparable does NOT have auto-synthesis.
You always have to implement < yourself. Swift can't guess what "less than" means for your type.
struct Priority: Comparable {
let level: Int
let timestamp: Date
// Have to write this yourself
static func < (lhs: Priority, rhs: Priority) -> Bool {
if lhs.level != rhs.level {
return lhs.level < rhs.level
}
return lhs.timestamp < rhs.timestamp
}
}When auto-synthesis DOESN’T work:
❌ Classes — You have to implement manually
❌ Non-Equatable properties — If any property can’t be compared, you’re on your own
struct Weird: Equatable {
let name: String
let closure: () -> Void // Closures aren't Equatable!
// Have to write custom ==
static func == (lhs: Weird, rhs: Weird) -> Bool {
return lhs.name == rhs.name
// Can't compare closures, so we ignore them
}
}❌ Custom equality logic — When you want something different than “all properties equal”
Pro tip: Start with automatic synthesis. Only write custom implementations when you need different behavior.
🎬 Wrapping This Up
So yeah, these three protocols aren’t the same at all. They just sound similar.
Quick recap:
Equatable = “Are these equal?” Lets you use == and !=. Foundation for the others.
Comparable = “Which is bigger/smaller?” Enables sorting and ordering. Requires Equatable.
Hashable = “Generate a hash value.” Enables Sets and Dictionary keys. Requires Equatable.
The relationship:
- Equatable is the base
- Hashable needs Equatable
- Comparable needs Equatable
- You can have all three
Swift auto-synthesizes Equatable and Hashable for structs/enums when possible. You always write Comparable yourself.
For most data models? Just conform to Hashable and Comparable. Gives you maximum flexibility — comparison, sorting, sets, dictionaries, everything.
And remember: start with automatic synthesis. Only write custom implementations when the default behavior doesn’t match what you need.
Next time you see “Type ‘Foo’ does not conform to protocol ‘Hashable’” — you’ll know exactly what that means and how to fix it in 30 seconds.
🎉 Enjoyed this article? Your support means the world to me!
💬 Drop a comment below! I love hearing about your experiences and answering questions
🎬 Subscribe on Youtube and become early subscribers of my channel: https://www.youtube.com/@swift-pal
💼 Let’s connect on LinkedIn for more professional insights: https://www.linkedin.com/in/karan-pal
Happy coding! 🚀
New articles, straight to your inbox.
No spam, no filler — just new writing on iOS, the web, and AI when it ships. Unsubscribe anytime.
Keep reading
Why I'm Rebuilding My Blog From Scratch (and Leaving Medium)
After years of publishing on someone else's platform, I'm moving my writing to a home I actually own. Here's the reasoning, and what I'm building instead.
ReadData Is the Model: The Most Ignored Part of AI
A beginner-friendly guide to why data quality beats model hype.
Read