LearnAI ToolsCareerPractice BuildsPlayContact
Lesson 916 min read

Enums

Learn Swift enums with raw values, associated values, CaseIterable, methods on enums, and modeling state safely with switch.

Introduction

Enums in Swift are far more powerful than "named constants" in most other languages. A Swift enum case can carry its own associated data, an enum can have methods and computed properties, and — combined with an exhaustive `switch` — enums let you model state in a way the compiler can fully verify for you.

This lesson covers defining enums, giving cases raw values, attaching associated values, iterating over all cases, adding methods, and briefly, recursive enums.

Defining Enums

enum Direction {
case north, south, east, west
}
let heading = Direction.north
print(heading)
Output

Click Run to see what this code prints.

Raw Values

An enum can assign each case a raw value of a specified type. When cases are integers, Swift automatically increments each subsequent case unless you assign one explicitly.

enum Weekday: Int {
case monday = 1, tuesday, wednesday, thursday, friday, saturday, sunday
}
let today = Weekday.wednesday
print(today.rawValue)
print(Weekday(rawValue: 5)?.rawValue ?? -1)
Output

Click Run to see what this code prints.

Initializing an enum from a raw value (`Weekday(rawValue: 5)`) returns an Optional, since the raw value you pass in might not correspond to any case.

Associated Values

Unlike raw values (one fixed value per case, shared across all instances), associated values let each individual instance of a case carry its own unique data — this is what makes Swift enums genuinely powerful for modeling things like API results.

enum NetworkResult {
case success(data: String)
case failure(error: String)
}
func handle(_ result: NetworkResult) {
switch result {
case .success(let data):
print("Got data: \(data)")
case .failure(let error):
print("Failed: \(error)")
}
}
handle(.success(data: "User profile loaded"))
handle(.failure(error: "Timeout"))
Output

Click Run to see what this code prints.

Iterating All Cases

Conforming an enum to `CaseIterable` automatically generates an `allCases` array containing every case, useful for building menus, loops, or tests that must cover every possibility.

enum Suit: CaseIterable {
case clubs, diamonds, hearts, spades
}
for suit in Suit.allCases {
print(suit)
}
Output

Click Run to see what this code prints.

Methods on Enums

Enums can define methods and computed properties, just like structs and classes — a huge part of why they're so useful for modeling behavior, not just data.

enum Direction2 {
case north, south, east, west
func opposite() -> Direction2 {
switch self {
case .north: return .south
case .south: return .north
case .east: return .west
case .west: return .east
}
}
}
print(Direction2.north.opposite())
Output

Click Run to see what this code prints.

Switching on Enums

Because `switch` must be exhaustive, matching on an enum with `switch` is one of Swift's strongest safety guarantees: if you later add a new case to the enum, every `switch` statement matching on it (without a `default`) will fail to compile until you handle the new case too.

Recursive (indirect) Enums

Normally an enum case can't contain itself, since Swift needs to know its size at compile time. Marking a case (or the whole enum) `indirect` tells the compiler to store that case behind a reference, allowing genuinely recursive data structures like simple expression trees.

indirect enum ArithmeticExpression {
case number(Int)
case addition(ArithmeticExpression, ArithmeticExpression)
}
let sum = ArithmeticExpression.addition(.number(3), .number(4))

Common Mistakes

Avoid These Mistakes
  • Treating enums as "just named constants" and reaching for loosely-typed strings or dictionaries instead of associated values to model varying data.
  • Forgetting `let`/`var` when extracting an associated value inside a switch case, e.g. writing `case .success:` instead of `case .success(let data):`.
  • Adding a `default` case to every switch out of habit, which silently hides the compiler's exhaustiveness check when you add a new enum case later.
  • Forgetting that raw values must be unique across all cases of an enum.

Best Practices

  • Use enums with associated values to model API results, states, or outcomes, instead of loosely-typed strings or booleans.
  • Conform to `CaseIterable` whenever you need to enumerate, test, or list every possible case.
  • Avoid a catch-all `default` case in switches where you specifically want the compiler to catch missing cases as you evolve the enum.
  • Give enums and their cases clear, singular names following Swift naming conventions (e.g. `case success`, not `case Success`).

Frequently Asked Questions

A raw value is one fixed value baked into the enum case definition itself and shared by all instances of that case. An associated value is supplied fresh each time you create an instance, so different instances of the same case can carry different data.

No — an enum can have one or the other, not both, since raw values require every case to have a compile-time-constant value of the same type.

Swift needs to know an enum's exact memory size at compile time. A case referencing its own enum type directly would have unbounded size, so `indirect` tells the compiler to store it behind a reference instead.

Key Takeaways

  • Enums define a fixed set of related cases, optionally backed by raw values.
  • Associated values let each case instance carry unique data, unlike shared raw values.
  • CaseIterable auto-generates an `allCases` collection for looping over every case.
  • Enums can have methods and computed properties, just like structs and classes.
  • indirect enables recursive enum cases when a case needs to reference its own type.

Summary

You can now model fixed sets of states and outcomes with Swift enums, including ones that carry rich associated data. Next, you'll work with Swift's core collection types: arrays, sets, and dictionaries.

Next Lesson →

Collections