Polyglot
Languages Swift pattern matching
Swift § pattern-matching

Pattern matching

Swift’s pattern matching is one of its most distinctive features. The principal mechanism is the switch statement — exhaustive matching with substantial pattern grammar: value patterns (literals), wildcard (_), value-binding (let x), tuple patterns, enum case patterns (with associated values), type-casting patterns (as), range patterns (1...10), and expression patterns (using ~=). The where clause admits guards. Patterns also appear in for case, if case, guard case, while case, and destructuring assignment. The combination — exhaustive switch, the substantial pattern grammar, patterns outside switch, the where guards, the type-cast patterns — is the substance of Swift’s pattern-matching mechanism.

switch

The principal form:

switch value {
case pattern1:
    body
case pattern2:
    body
default:
    body
}

Examples:

let n = 5

switch n {
case 0:
    print("zero")
case 1, 2, 3:                                      // multiple values
    print("small")
case 4...10:                                       // range
    print("medium")
case let x where x.isMultiple(of: 5):              // bound + guard
    print("multiple of 5: \(x)")
default:
    print("large")
}

The switch is exhaustive — every possible value of the discriminant must be covered, or the compiler refuses to compile:

let active: Bool = true
switch active {
case true: print("active")
case false: print("inactive")
                                                   // exhaustive — no default needed
}

For non-exhaustive coverage, default is required.

No fallthrough by default

Each case body runs once and exits the switchno fallthrough:

switch n {
case 1:
    print("one")
case 2:
    print("two")
}
// Prints only "one" for n=1

For explicit fallthrough, the fallthrough keyword:

switch n {
case 1:
    print("one")
    fallthrough                                    // explicit
case 2:
    print("two")                                   // also runs
}

The form is rare in idiomatic Swift; case 1, 2, 3 admits the conventional “match any of these” without fallthrough.

Value patterns

Match against literals or constants:

let n = 5
switch n {
case 0: print("zero")
case 1: print("one")
case 2: print("two")
default: print("other")
}

let s = "hello"
switch s {
case "hello": print("greeting")
case "goodbye": print("farewell")
default: print("other")
}

Wildcard _

The _ matches anything without binding:

let coord = (1, 2)
switch coord {
case (0, _): print("on y-axis")
case (_, 0): print("on x-axis")
case (_, _): print("anywhere")
}

Value-binding patterns

The let (or var) admits binding the matched value:

let coord = (1, 2)
switch coord {
case (0, let y): print("y-axis at \(y)")
case (let x, 0): print("x-axis at \(x)")
case let (x, y): print("(\(x), \(y))")             // bind both
}

The pattern admits substantial decomposition with binding.

Tuple patterns

Tuples admit substantial pattern matching:

let point = (x: 1, y: 2)
switch point {
case (0, 0):
    print("origin")
case (_, 0):
    print("on x-axis")
case (0, _):
    print("on y-axis")
case (let x, let y) where x == y:
    print("diagonal")
case let (x, y):
    print("(\(x), \(y))")
}

The form admits substantial multi-axis dispatch.

Enum case patterns

enum Shape {
    case circle(radius: Double)
    case rectangle(width: Double, height: Double)
    case triangle(base: Double, height: Double)
}

func area(_ s: Shape) -> Double {
    switch s {
    case .circle(let r):
        return Double.pi * r * r
    case .rectangle(let w, let h):
        return w * h
    case .triangle(let b, let h):
        return b * h / 2
    }
}

The .circle(let r) admits binding the associated value.

For let factored out:

switch s {
case let .circle(r):
    return Double.pi * r * r
case let .rectangle(w, h):
    return w * h
case let .triangle(b, h):
    return b * h / 2
}

The let may appear inside the case (case .circle(let r)) or outside (case let .circle(r)); both forms are admitted.

For matching specific values:

switch s {
case .circle(0):
    print("zero-radius circle")
case .circle(let r) where r > 100:
    print("large circle")
case .circle(let r):
    print("circle radius \(r)")
default:
    break
}

Range patterns

let n = 42
switch n {
case 0:
    print("zero")
case 1...10:
    print("small")
case 11..<100:                                     // half-open
    print("medium")
case 100...:                                       // one-sided
    print("large")
default:
    print("negative")
}

// Character ranges:
let c: Character = "g"
switch c {
case "a"..."m":
    print("first half")
case "n"..."z":
    print("second half")
default:
    print("other")
}

Type-casting patterns

The is and as patterns admit type-based matching:

let value: Any = "hello"

switch value {
case is Int:
    print("an Int")
case let s as String:
    print("a String: \(s)")
case let arr as [Int]:
    print("an [Int] of length \(arr.count)")
default:
    print("unknown")
}

The pattern admits substantial discrimination on runtime type.

Optional patterns

Optionals admit ? and .some/.none:

let n: Int? = 42

switch n {
case .none:
    print("nothing")
case .some(let value):
    print("got \(value)")
}

// Or:
switch n {
case nil:
    print("nothing")
case let value?:
    print("got \(value)")                          // optional pattern
}

The let value? is the optional pattern — admits binding the wrapped value.

where guards

let n = 7
switch n {
case let x where x.isMultiple(of: 2):
    print("even: \(x)")
case let x where x.isMultiple(of: 3):
    print("multiple of 3: \(x)")
case let x where x.isMultiple(of: 5):
    print("multiple of 5: \(x)")
default:
    print("other")
}

The where admits substantial guards on bound values.

Multiple patterns per case

switch day {
case .saturday, .sunday:
    print("weekend")
case .monday, .tuesday, .wednesday, .thursday, .friday:
    print("weekday")
}

let coord = (1, 0)
switch coord {
case (0, 0), (1, 0), (0, 1):
    print("special point")
default:
    print("ordinary")
}

if case, guard case, while case, for case

Patterns appear outside switch:

if case

let value: Int? = 42

if case .some(let n) = value {
    print(n)
}

if case let .some(n) = value, n > 0 {
    print("positive: \(n)")
}

guard case

func process(value: Result<Int, Error>) {
    guard case .success(let n) = value else {
        return
    }
    print("got \(n)")
}

while case

var iter = arr.makeIterator()
while case let .some(item) = iter.next() {
    process(item)
}

for case

let mixed: [Any] = [1, "hello", 2.0, "world", 3]

for case let s as String in mixed {
    print("string: \(s)")                          // "hello", "world"
}

for case let n as Int in mixed where n > 1 {
    print("int > 1: \(n)")
}

The form admits substantial filtered iteration with type discrimination.

Destructuring assignment

let pair = (1, "hello")
let (n, s) = pair                                  // destructure

let coord = (x: 1, y: 2)
let (x, y) = coord

// In function parameters:
func process(_ point: (Int, Int)) {
    let (x, y) = point
    /* use x, y */
}

Common patterns

Discriminated union dispatch

enum Action {
    case increment(by: Int)
    case decrement(by: Int)
    case reset
    case set(value: Int)
}

func reduce(state: Int, action: Action) -> Int {
    switch action {
    case .increment(let n): return state + n
    case .decrement(let n): return state - n
    case .reset: return 0
    case .set(let value): return value
    }
}

State machine

enum State {
    case idle
    case running(startedAt: Date)
    case completed(result: String)
    case failed(error: Error)
}

func describe(_ state: State) -> String {
    switch state {
    case .idle:
        return "Ready"
    case .running(let date):
        return "Running since \(date)"
    case .completed(let result):
        return "Done: \(result)"
    case .failed(let error):
        return "Error: \(error.localizedDescription)"
    }
}

Result handling

let result: Result<Data, Error> = ...

switch result {
case .success(let data):
    process(data)
case .failure(let error):
    log(error)
}

Type-based dispatch

func describe(_ value: Any) -> String {
    switch value {
    case let n as Int: return "int \(n)"
    case let d as Double: return "double \(d)"
    case let s as String: return "string \(s)"
    case let arr as [Any]: return "array \(arr.count) items"
    case let dict as [String: Any]: return "dict \(dict.count) keys"
    case is NSNull: return "null"
    case nil: return "nil"
    default: return "unknown: \(value)"
    }
}

Range-based dispatch

func category(age: Int) -> String {
    switch age {
    case ..<13: return "child"
    case 13..<20: return "teen"
    case 20..<65: return "adult"
    case 65...: return "senior"
    default: return "invalid"
    }
}

Tuple dispatch

func quadrant(_ x: Int, _ y: Int) -> Int {
    switch (x, y) {
    case (0, 0): return 0
    case (let x, let y) where x > 0 && y > 0: return 1
    case (let x, let y) where x < 0 && y > 0: return 2
    case (let x, let y) where x < 0 && y < 0: return 3
    case (let x, let y) where x > 0 && y < 0: return 4
    default: return 0
    }
}

Optional binding via if case

let value: Int? = 42

if case let n? = value, n > 0 {
    print("positive: \(n)")
}

Filter with type-cast in for

for case let url as URL in mixedArray {
    fetch(url)
}

Validation with guard case

func processResult(_ result: Result<User, Error>) async throws -> User {
    guard case .success(let user) = result else {
        throw AuthError.fetchFailed
    }
    return user
}

Switch as expression (Swift 5.9+)

let category = switch age {
case ..<13: "child"
case 13..<20: "teen"
case 20..<65: "adult"
default: "senior"
}

The expression-form admits substantial conciseness — particularly for value-returning conditional logic.

Enum with multiple associated values

enum Event {
    case tap(point: CGPoint)
    case key(code: UInt32, modifiers: UInt32)
    case scroll(delta: Double)
}

func handle(_ event: Event) {
    switch event {
    case .tap(let point):
        handleTap(point)
    case .key(let code, let modifiers):
        handleKey(code: code, modifiers: modifiers)
    case .scroll(let delta):
        scroll(by: delta)
    }
}

Recursive enum

indirect enum Expr {
    case literal(Int)
    case add(Expr, Expr)
    case mul(Expr, Expr)
}

func eval(_ e: Expr) -> Int {
    switch e {
    case .literal(let n): return n
    case .add(let a, let b): return eval(a) + eval(b)
    case .mul(let a, let b): return eval(a) * eval(b)
    }
}

let expr = Expr.add(.literal(2), .mul(.literal(3), .literal(4)))
print(eval(expr))                                  // 14

The indirect admits the enum to recursively reference itself.

Match with side effect via where

switch userInput {
case let s where s.matches(/^\d+$/):
    parseNumber(s)
case let s where s.matches(/^[a-z]+$/):
    parseIdentifier(s)
default:
    error()
}

Pattern matching with ~=

The ~= is the pattern match operator — admits explicit matching outside switch:

let n = 5
if 1...10 ~= n {
    print("in range")
}

// Custom ~=:
struct Even {}
func ~= (pattern: Even, value: Int) -> Bool {
    value.isMultiple(of: 2)
}

if Even() ~= 4 {
    print("4 is even")
}

// And in switch:
switch n {
case Even():
    print("even")
default:
    print("odd")
}

The mechanism admits substantial custom pattern matching.

A note on the conventional discipline

The contemporary Swift pattern-matching advice:

  • Use switch for value-driven dispatch.
  • Trust exhaustiveness — the compiler verifies all cases are covered.
  • Use enum case patterns with associated values for substantial discrimination.
  • Use where for guards.
  • Use if case / guard case for inline pattern matching.
  • Use for case for filtered iteration.
  • Use type-casting patterns (as, is) for runtime-type discrimination.
  • Use range patterns for substantial value ranges.
  • Use switch as expression (5.9+) for substantial conciseness.
  • Avoid fallthrough — multiple values per case is conventionally clearer.
  • Use _ wildcards freely.

The combination — exhaustive switch, the substantial pattern grammar, patterns in if case/guard case/for case/while case, where guards, the expression-form, the ~= for custom matching — is the substance of Swift’s pattern matching. The discipline produces substantial, type-safe dispatch with substantial expressiveness for substantial discriminated-union code.