Skip to main content

Swift Pattern Matching Basics

Pattern matching is one of Swift's most powerful features that lets you check if values match certain patterns. Beyond simple equality checks, pattern matching in Swift provides an elegant way to extract information and make decisions based on the structure of your data. In this guide, we'll explore the basics of pattern matching in Swift and how it can make your code more expressive and concise.

What is Pattern Matching?

Pattern matching is a mechanism for checking a value against a pattern and, when the pattern matches, extracting parts of that value for further use. Swift's pattern matching is primarily implemented through the switch statement, but can also be used with if, guard, and while statements.

Pattern matching allows you to:

  • Match simple values like integers or strings
  • Check ranges or intervals
  • Extract parts from complex data types
  • Test multiple conditions at once
  • Bind matched values to variables

Let's dive into the basics!

Simple Value Matching

The most basic form of pattern matching is checking if a value is equal to another value:

swift
let number = 10

switch number {
case 0:
print("Zero")
case 1:
print("One")
case 10:
print("Ten")
default:
print("Some other number")
}

// Output: Ten

Unlike other languages, Swift's switch statements:

  • Must be exhaustive (cover all possible values)
  • Don't fall through by default (no need for break statements)
  • Can match against any type, not just integers

Matching Ranges

You can match ranges of values using Swift's range operators:

swift
let score = 85

switch score {
case 0..<60:
print("Failed")
case 60..<70:
print("Passed with D")
case 70..<80:
print("Passed with C")
case 80..<90:
print("Passed with B")
case 90...100:
print("Passed with A")
default:
print("Invalid score")
}

// Output: Passed with B

In this example, we use half-open ranges (0..<60) and closed ranges (90...100) to check where the score falls.

Multiple Patterns in a Single Case

You can combine multiple patterns in a single case using commas:

swift
let character = "e"

switch character {
case "a", "e", "i", "o", "u":
print("\(character) is a vowel")
case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
"n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z":
print("\(character) is a consonant")
default:
print("\(character) is not a letter")
}

// Output: e is a vowel

This makes your code more concise when multiple values should trigger the same action.

Value Binding

Pattern matching can extract parts of the matched value and bind them to variables or constants:

swift
let point = (x: 3, y: 2)

switch point {
case (0, 0):
print("Point is at the origin")
case (_, 0):
print("Point is on the x-axis")
case (0, _):
print("Point is on the y-axis")
case (let x, let y):
print("Point is at (\(x), \(y))")
}

// Output: Point is at (3, 2)

The let x, let y pattern binds the values of the tuple to the variables x and y, which can then be used in the case block.

Where Clauses

You can add conditions to your pattern matching using where clauses:

swift
let point = (x: 5, y: 7)

switch point {
case let (x, y) where x == y:
print("Point is on the line x = y")
case let (x, y) where x == -y:
print("Point is on the line x = -y")
case let (x, y):
print("Point is at (\(x), \(y))")
}

// Output: Point is at (5, 7)

The where clause adds an additional check to the pattern, making your pattern matching more specific.

Pattern Matching with Optionals

Pattern matching works well with optionals:

swift
let optionalNumber: Int? = 10

switch optionalNumber {
case .none:
print("No value")
case .some(let value) where value > 10:
print("Large value: \(value)")
case .some(let value):
print("Value: \(value)")
}

// Output: Value: 10

This is especially useful when working with Swift's optional values, allowing you to handle both presence and absence of values elegantly.

Using Pattern Matching in If Statements

Pattern matching isn't limited to switch statements. You can use if case syntax:

swift
let age = 25

// Using if case for pattern matching
if case 18...30 = age {
print("Young adult")
}

// Output: Young adult

// Using if case with binding
let coordinates = (x: 3, y: 5)
if case let (x, y) where x == y = coordinates {
print("On the diagonal")
} else {
print("Not on the diagonal")
}

// Output: Not on the diagonal

This syntax is convenient when you only need to check for a specific pattern, rather than exhaustively handling all cases.

Pattern Matching in For Loops

You can use pattern matching in for loops to filter and transform elements:

swift
let numbers = [1, 2, 3, nil, 5, nil, 7]

// Only process non-nil values
for case let number? in numbers {
print("Found a number: \(number)")
}

// Output:
// Found a number: 1
// Found a number: 2
// Found a number: 3
// Found a number: 5
// Found a number: 7

This is a powerful way to process collections with specific elements that match your pattern.

Real-World Example: Processing API Responses

Here's a real-world example where pattern matching simplifies handling API responses:

swift
enum APIResponse {
case success(data: [String: Any], statusCode: Int)
case failure(error: String, statusCode: Int)
case networkError(reason: String)
}

func processResponse(_ response: APIResponse) {
switch response {
case .success(let data, 200):
print("Success with data: \(data)")
case .success(_, let code) where code >= 300:
print("Unexpected success with code: \(code)")
case .failure(let error, 404):
print("Not found error: \(error)")
case .failure(let error, let code) where code >= 500:
print("Server error (\(code)): \(error)")
case .failure(let error, let code):
print("Other failure (\(code)): \(error)")
case .networkError(let reason):
print("Network error: \(reason)")
}
}

// Example usage:
let response1 = APIResponse.success(data: ["user": "John"], statusCode: 200)
processResponse(response1)
// Output: Success with data: ["user": "John"]

let response2 = APIResponse.failure(error: "Resource not found", statusCode: 404)
processResponse(response2)
// Output: Not found error: Resource not found

This example shows how pattern matching can elegantly handle different API response cases with specific data extraction and conditions.

Summary

Swift's pattern matching is a powerful feature that goes beyond simple value comparison. It allows you to:

  • Match values against multiple patterns
  • Extract components from complex data types
  • Add conditions to your patterns with where clauses
  • Use pattern matching in various contexts (switch, if, guard, for loops)

By mastering pattern matching, you can write more expressive, concise, and readable code that clearly communicates your intent.

Exercises

To practice your pattern matching skills, try these exercises:

  1. Write a function that takes a tuple of (name: String, age: Int) and uses pattern matching to categorize people into different age groups.

  2. Create an enum to represent different shapes (circle, rectangle, triangle) with associated values for their dimensions. Write a function that calculates the area of each shape using pattern matching.

  3. Write a function that takes an array of Any type and uses pattern matching to count how many integers, strings, and boolean values are in the array.

Additional Resources

Pattern matching is a feature that becomes more powerful as you explore more advanced Swift concepts like enums with associated values, structs, and protocols. Keep practicing to become more comfortable with this elegant Swift feature!



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)