Skip to content

Swift Enums — Powerful Enumerations with Associated Values

DodaTech Updated 2026-06-28 9 min read

In this tutorial, you will learn about Swift Enums. We cover key concepts, practical examples, and best practices to help you master this topic.

Swift enums are first-class types that define a common group of related values and enable you to work with those values in a type-safe way, far beyond simple integer constants found in other languages.

What You'll Learn

  • Basic enum syntax and pattern matching with switch
  • Raw values and associated values
  • Recursive enums
  • Enum methods, computed properties, and protocol conformance
  • Memory and performance characteristics
  • Real-world enum patterns

Why It Matters

Enums in Swift are dramatically more powerful than in most languages. They can have methods, computed properties, initializers, conform to protocols, and carry associated data. This makes them ideal for modeling state machines, network responses, payment methods, navigation routes, and any set of mutually exclusive options.

Real-World Use

A ride-sharing app uses an enum to model the ride state: .requested, .driverAssigned(driverID: String), .inTransit(currentLocation: CLLocation), .completed, .cancelled(reason: String). Each state carries different associated data, and the compiler ensures all states are handled in every switch statement.

Learning Path

flowchart LR
  A[Generics
Lesson 15] --> B[Enums
You are here] B --> C[Views and UI
Lesson 17] B --> D[Table and Collection Views
Lesson 18] style B fill:#f90,color:#fff

Basic Enum Syntax

Enums declare a type with a fixed set of possible values using the case keyword.

enum CompassDirection {
    case north
    case south
    case east
    case west
}

var direction = CompassDirection.north
direction = .east
print("Direction: \(direction)")

switch direction {
case .north:
    print("Going north")
case .south:
    print("Going south")
case .east:
    print("Going east")
case .west:
    print("Going west")
}

Output:

Direction: east
Going east

Once the type is known, Swift infers the enum type so you can use .east shorthand. The switch must be exhaustive — all cases must be covered.

Raw Values

Enums can have raw values of the same type assigned to each case. Raw values can be strings, characters, integers, or floating-point numbers.

enum Planet: Int {
    case mercury = 1
    case venus
    case earth
    case mars
    case jupiter
    case saturn
    case uranus
    case neptune
}

enum HTTPStatus: Int {
    case ok = 200
    case created = 201
    case badRequest = 400
    case unauthorized = 401
    case notFound = 404
    case serverError = 500
}

enum Direction: String {
    case north = "N"
    case south = "S"
    case east = "E"
    case west = "W"
}

let earth = Planet.earth
print("Earth is planet #\(earth.rawValue)")

let status = HTTPStatus.notFound
print("Status code: \(status.rawValue)")

print("Direction abbreviation: \(Direction.north.rawValue)")

if let planet = Planet(rawValue: 3) {
    print("Planet #3 is \(planet)")
}

if let error = HTTPStatus(rawValue: 404) {
    print("Error: \(error)")
}

Output:

Earth is planet #3
Status code: 404
Direction abbreviation: N
Planet #3 is earth
Error: notFound

Integer raw values auto-increment if you do not assign them explicitly. Failable initializers like Planet(rawValue:) return nil if no matching case exists.

Associated Values

Associated values let you attach arbitrary data of different types to each enum case, making enums much more expressive.

enum Barcode {
    case upc(Int, Int, Int, Int)
    case qrCode(String)
    case aztec(code: String, version: Int)
}

var productBarcode = Barcode.upc(0, 12345, 67890, 3)
productBarcode = .qrCode("https://example.com")

switch productBarcode {
case .upc(let numberSystem, let manufacturer, let product, let check):
    print("UPC: \(numberSystem)-\(manufacturer)-\(product)-\(check)")
case .qrCode(let url):
    print("QR Code: \(url)")
case .aztec(let code, let version):
    print("Aztec v\(version): \(code)")
}

Output: QR Code: https://example.com

Each case can have different associated value types. Barcode.upc has four integers while Barcode.qrCode has a single string. Pattern matching extracts the values in the switch.

Value Binding Shorthand

You can place let or var before the case name to bind all associated values.

switch productBarcode {
case let .upc(numberSystem, manufacturer, product, check):
    print("UPC: \(numberSystem)-\(manufacturer)-\(product)-\(check)")
case let .qrCode(url):
    print("QR Code: \(url)")
case let .aztec(code, version):
    print("Aztec v\(version): \(code)")
}

Enum Methods and Properties

Enums can have computed properties, methods, and even initializers.

enum TrafficLight {
    case red
    case yellow
    case green

    var description: String {
        switch self {
        case .red:
            return "Stop"
        case .yellow:
            return "Caution"
        case .green:
            return "Go"
        }
    }

    var duration: Double {
        switch self {
        case .red: return 30.0
        case .yellow: return 5.0
        case .green: return 25.0
        }
    }

    mutating func next() {
        switch self {
        case .red:
            self = .green
        case .yellow:
            self = .red
        case .green:
            self = .yellow
        }
    }

    func shouldStop() -> Bool {
        return self == .red || self == .yellow
    }
}

var light = TrafficLight.red
print("\(light): \(light.description)")
light.next()
print("\(light): \(light.description)")
light.next()
print("\(light): \(light.description)")
print("Should stop? \(light.shouldStop())")

Output:

red: Stop
green: Go
yellow: Caution
Should stop? true

The next() method is marked mutating because it changes self. Computed properties use switch to return different values per case.

Enums with Associated Values and Methods

Methods can work with associated values using pattern matching.

enum NetworkResult {
    case success(data: Data, statusCode: Int)
    case failure(error: Error, statusCode: Int)
    case loading(progress: Double)

    var description: String {
        switch self {
        case .success(_, let statusCode):
            return "Success with status \(statusCode)"
        case .failure(let error, let statusCode):
            return "Error \(statusCode): \(error.localizedDescription)"
        case .loading(let progress):
            return "Loading: \(Int(progress * 100))%"
        }
    }

    var data: Data? {
        if case .success(let data, _) = self {
            return data
        }
        return nil
    }
}

let result = NetworkResult.loading(progress: 0.75)
print(result.description)

let completed = NetworkResult.success(data: Data(), statusCode: 200)
print(completed.description)
print("Has data: \(completed.data != nil)")

Output:

Loading: 75%
Success with status 200
Has data: true

Recursive Enums

A recursive enum has a case that refers to one or more instances of the same enum. You mark such cases with indirect.

indirect enum ArithmeticExpression {
    case number(Int)
    case addition(ArithmeticExpression, ArithmeticExpression)
    case multiplication(ArithmeticExpression, ArithmeticExpression)
    case subtraction(ArithmeticExpression, ArithmeticExpression)
    case division(ArithmeticExpression, ArithmeticExpression)
}

func evaluate(_ expression: ArithmeticExpression) -> Int {
    switch expression {
    case .number(let value):
        return value
    case .addition(let left, let right):
        return evaluate(left) + evaluate(right)
    case .multiplication(let left, let right):
        return evaluate(left) * evaluate(right)
    case .subtraction(let left, let right):
        return evaluate(left) - evaluate(right)
    case .division(let left, let right):
        return evaluate(left) / evaluate(right)
    }
}

let expression = ArithmeticExpression.addition(
    .number(5),
    .multiplication(.number(3), .number(4))
)

print("Result: \(evaluate(expression))")

Output: Result: 17

The expression 5 + (3 * 4) is represented as a recursive enum tree. The indirect keyword tells Swift to store the enum case as a reference (heap-allocated) to support arbitrary nesting.

Protocol Conformance

Enums can conform to protocols, including standard library protocols like Codable, CaseIterable, Comparable, and CustomStringConvertible.

enum CardSuit: String, CaseIterable, Codable, CustomStringConvertible {
    case hearts = "H"
    case diamonds = "D"
    case clubs = "C"
    case spades = "S"

    var description: String {
        switch self {
        case .hearts: return "Hearts"
        case .diamonds: return "Diamonds"
        case .clubs: return "Clubs"
        case .spades: return "Spades"
        }
    }

    var symbol: String {
        switch self {
        case .hearts: return "♥"
        case .diamonds: return "♦"
        case .clubs: return "♣"
        case .spades: return "♠"
        }
    }
}

print("All suits:")
for suit in CardSuit.allCases {
    print("  \(suit.symbol) \(suit)")
}

let jsonData = try JSONEncoder().encode(CardSuit.hearts)
let decoded = try JSONDecoder().decode(CardSuit.self, from: jsonData)
print("Decoded: \(decoded)")

Output:

All suits:
  ♥ Hearts
  ♦ Diamonds
  ♣ Clubs
  ♠ Spades
Decoded: Hearts

CaseIterable provides the allCases property automatically. Codable serializes enums with raw or associated values.

Real-World Enum Patterns

State Machine

enum DownloadState {
    case idle
    case downloading(progress: Double)
    case completed(fileURL: URL)
    case failed(error: Error)

    var progressDescription: String {
        switch self {
        case .idle:
            return "Waiting to start"
        case .downloading(let progress):
            return "Downloading: \(Int(progress * 100))%"
        case .completed(let fileURL):
            return "Completed: \(fileURL.lastPathComponent)"
        case .failed(let error):
            return "Failed: \(error.localizedDescription)"
        }
    }
}

let state = DownloadState.downloading(progress: 0.5)
print(state.progressDescription)

Output: Downloading: 50%

Optional Values

Swift's own Optional is an enum:

// Simplified version of Swift's Optional
// enum Optional<Wrapped> {
//     case none
//     case some(Wrapped)
// }

let name: String? = "Alice"
switch name {
case .none:
    print("No name provided")
case .some(let value):
    print("Name is \(value)")
}

Output: Name is Alice

Common Mistakes

  1. Not handling all cases in switch: Swift enforces exhaustive switch statements. Use a default case only when you explicitly want to ignore some cases — otherwise handle each case individually for better safety.

  2. Using raw values when associated values would be better: Raw values fit simple constant mappings. For complex data per case, use associated values instead.

  3. Forgetting indirect for recursive enums: Recursive enums cause a compile error without the indirect keyword on the enum or individual cases.

  4. Modifying self in enum methods without mutating: Enum methods that change self must be marked mutating.

  5. Overusing enums for unrelated cases: Cases in an enum should represent mutually exclusive variants of the same concept. Grouping unrelated values in one enum is poor design.

Practice Questions

  1. What is the difference between raw values and associated values in Swift enums?
  2. Why must switch statements over enums be exhaustive?
  3. When would you use indirect for an enum case?
  4. Can enums conform to protocols like Codable and CaseIterable?
  5. Challenge: Build a NetworkRequest enum with associated values that models GET, POST, PUT, DELETE methods. Each case should carry a URL, headers dictionary, and optional body data. Add a method that converts it to a URLRequest.

Mini Project

Create an OrderStatus enum for a food delivery app with:

  • Cases: .placed, .confirmed(estimatedMinutes: Int), .preparing(chefName: String), .outForDelivery(driverName: String, phone: String), .delivered, .cancelled(reason: String)
  • Computed property displayText: String for user-facing status
  • Computed property isActive: Bool (true for in-progress states)
  • Method timeRemaining: String? that returns estimated time if available
  • Conformance to Codable so it can be sent over the network

FAQ

Can enums have stored properties?

No. Enums cannot have stored properties because they represent values, not objects. However, each case can have its own associated values, which serve a similar purpose.

Are enums value or reference types?

Enums are value types. When you assign an enum to a new variable or pass it to a function, Swift creates a copy.

Can I add inheritance to an enum?

No. Enums cannot inherit from other enums or classes. However, they can conform to protocols.

How are enums stored in memory?

Enums use a discriminant (tag) to identify the case plus storage for the largest associated value. The size is the tag plus the maximum associated data size.

Can I use enums as dictionary keys?

Yes. Enums that are Hashable (which includes all enums without associated values automatically) can be used as dictionary keys.

What's Next

Apply your enum knowledge to build real interfaces in Views and UI, or explore Table and Collection Views for data-driven list interfaces.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro