Chapter 74: Swift Enums & Pattern Matching
1. Why do we even need enums? (the most important intuition)
In real life we often have a fixed number of possible states or categories:
- The weather is either sunny, cloudy, rainy, snowy…
- User role is admin, moderator, member, guest…
- Payment status is pending, processing, completed, failed…
- App screen state is loading, success, error, empty…
In old languages people usually use:
- strings (“admin”, “moderator”)
- integers (0 = pending, 1 = completed)
- booleans (isAdmin = true/false)
All of these are dangerous because:
- You can write typos (“admnin”)
- You can pass wrong numbers (status = 999)
- You can mix unrelated states
- The compiler cannot help you remember all cases
Swift enums solve all of this.
An enum says: “This value can be one of these exact possibilities — and nothing else.”
The compiler forces you to handle every case (exhaustiveness checking).
That is the single most important superpower of Swift enums.
2. Basic enum — the simplest form
|
0 1 2 3 4 5 6 7 8 9 10 11 |
enum Weather { case sunny case cloudy case rainy case snowy } |
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
let today = Weather.rainy switch today { case .sunny: print("Wear sunglasses ☀️") case .cloudy: print("Maybe a light jacket 🧥") case .rainy: print("Take umbrella ☔") case .snowy: print("Wear warm coat & boots ❄️") } |
Key points:
- Cases start with lowercase (convention)
- You always write .caseName when using the value
- switch must be exhaustive → compiler error if you forget a case
3. Enum with associated values (very powerful — real game changer)
Very often the state needs to carry extra information.
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
enum Result { case success(message: String, data: [String]) case failure(errorCode: Int, message: String) case loading case cancelled } let apiResult = Result.success(message: "Loaded", data: ["item1", "item2"]) // Pattern matching — this is where it becomes magic switch apiResult { case .success(let msg, let items): print("Success: \(msg)") print("Got \(items.count) items") case .failure(let code, let msg): print("Error \(code): \(msg)") case .loading: print("Still loading…") case .cancelled: print("Request was cancelled") } |
Real-life example — network / API response (extremely common)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
enum NetworkResponse { case success(statusCode: Int, data: Data) case failure(error: Error) case unauthorized case noInternet } func handle(response: NetworkResponse) { switch response { case .success(let code, let data): if code == 200 { print("Success — parsing \(data.count) bytes") } else { print("Unexpected status: \(code)") } case .failure(let error): print("Network error: \(error.localizedDescription)") case .unauthorized: print("Please sign in again") showLoginScreen() case .noInternet: print("No internet connection — showing offline mode") } } |
4. Enum with raw values (when you need to map to String / Int)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
enum HTTPStatus: Int { case ok = 200 case created = 201 case badRequest = 400 case unauthorized = 401 case notFound = 404 case serverError = 500 } let statusCode = 404 if let status = HTTPStatus(rawValue: statusCode) { switch status { case .notFound: print("Page not found") case .ok, .created: print("Success") default: print("Other status: \(status.rawValue)") } } |
String raw value (very common for API endpoints, JSON keys)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
enum Endpoint: String { case users = "/api/v1/users" case posts = "/api/v1/posts" case comments = "/api/v1/comments" } let url = baseURL + Endpoint.posts.rawValue |
5. Pattern Matching — the real superpower of Swift enums
Pattern matching is what makes enums so powerful. You can destructure and extract associated values very elegantly.
Basic pattern matching in switch
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
enum PaymentStatus { case pending case processing(amount: Double) case completed(transactionID: String) case failed(reason: String) } let status = PaymentStatus.completed(transactionID: "TXN-987654") switch status { case .pending: print("Waiting for payment…") case .processing(let amount): print("Processing payment of ₹\(amount)") case .completed(let txID): print("Payment successful! Transaction: \(txID)") case .failed(let reason): print("Payment failed: \(reason)") } |
Pattern matching in if / guard
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
if case let .completed(txID) = status { print("Transaction ID: \(txID)") } guard case let .completed(txID) = status else { print("Not completed yet") return } // happy path — txID is unwrapped print("Success — TX: \(txID)") |
Very powerful real pattern — unwrap + check at once
|
0 1 2 3 4 5 6 7 8 9 10 |
if case let .success(data) = apiResult, data.count > 0 { process(data) } else { print("No data or failed") } |
6. Very Common Real-Life Examples You Will Write
Example 1 – API response handling (probably the #1 enum use-case)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
enum APIResult<T> { case success(T) case failure(Error) case loading } func loadUsers(completion: @escaping (APIResult<[User]>) -> Void) { // pretend network call DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { if Bool.random() { completion(.success([User(name: "Rahul"), User(name: "Priya")])) } else { completion(.failure(NSError(domain: "Network", code: -1009))) } } } loadUsers { result in switch result { case .success(let users): print("Loaded \(users.count) users") case .failure(let error): print("Failed: \(error.localizedDescription)") case .loading: print("Still loading…") } } |
Example 2 – Navigation / app state (very common in SwiftUI/UIKit)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
enum ScreenState { case idle case loading(message: String) case content(data: String) case error(title: String, message: String) } func updateUI(for state: ScreenState) { switch state { case .idle: hideLoading() hideError() showIdleView() case .loading(let msg): showLoading(message: msg) case .content(let data): hideLoading() showContent(data) case .error(let title, let msg): hideLoading() showError(title: title, message: msg) } } |
7. Quick Summary – When to use what
| Situation | Use this kind of enum | Typical example |
|---|---|---|
| Simple fixed states | enum Direction { case north, south, east, west } | UI states, weather, roles |
| State + extra data | case success(data: T) | API responses, async results |
| Error with details | case failure(code: Int, message: String) | Network, validation errors |
| Raw value needed (String/Int) | enum HTTPStatus: Int { case ok = 200 … } | API codes, persistence |
| Need exhaustive checking | switch without default on enum | Compiler forces you to handle all cases |
8. Small Practice – Try these
- Create enum PaymentStatus with cases:
- pending
- processing(amount: Double)
- completed(transactionID: String)
- failed(reason: String)
- Write a function that switches on it and prints appropriate message
- Create enum APIResult<T> with success(T), failure(String) → Use pattern matching to handle both cases
Paste your code here if you want feedback or want to see more elegant versions!
What would you like to explore next?
- Advanced pattern matching (if case let, guard case let, switch on tuples…)
- Enums with associated values in depth
- RawRepresentable enums (String/Int backed)
- Enums in SwiftUI (state management, view switching)
- Or move to another topic (optionals, arrays, map/filter/reduce, protocols…)
Just tell me — we’ll continue in the same clear, detailed, patient style 😊
