Gleam Pattern Matching

Pattern matching is Gleam's way of examining a value and taking different actions based on its shape or content. It replaces long chains of if-else with clear, structured checks that the compiler fully verifies.

What Is a Pattern?

A pattern is a description of a value's structure. When Gleam matches a value against a pattern, it checks whether the value fits that structure. If it fits, the match succeeds and any names in the pattern are bound to the corresponding parts of the value.


Pattern Matching — Concept Diagram
──────────────────────────────────────────────────
Value:    42

Pattern 1:  0       → Does 42 equal 0? No
Pattern 2:  100     → Does 42 equal 100? No
Pattern 3:  n       → n is a variable — it matches anything
                      Gleam binds n = 42 and proceeds

The case Expression

The primary tool for pattern matching is the case expression:

let status_code = 404

let message = case status_code {
  200 -> "OK"
  404 -> "Not Found"
  500 -> "Server Error"
  _   -> "Unknown"
}
// message = "Not Found"

case Flow Diagram
──────────────────────────────────────────────────
status_code = 404

       case status_code
            │
     ┌──────┼──────────┬─────────┐
    200    404        500        _
     │      │          │         │
    "OK"   "Not      "Server  "Unknown"
           Found"    Error"
            │
         selected! → "Not Found"

The underscore _ is a wildcard. It matches any value that did not match any earlier pattern. Always include it last as a catch-all.

Matching Literals

You can match exact integer, float, string, and boolean values:

pub fn day_name(n: Int) -> String {
  case n {
    1 -> "Monday"
    2 -> "Tuesday"
    3 -> "Wednesday"
    4 -> "Thursday"
    5 -> "Friday"
    6 -> "Saturday"
    7 -> "Sunday"
    _ -> "Invalid day"
  }
}

Binding Variables in Patterns

A bare name in a pattern matches any value and binds it to that name:

let score = 87

let feedback = case score {
  100 -> "Perfect score!"
  n if n >= 90 -> "Excellent — you scored " <> int.to_string(n)
  n if n >= 70 -> "Good — you scored " <> int.to_string(n)
  n -> "Keep practicing — you scored " <> int.to_string(n)
}

Here, n captures the value of score so you can use it inside the result expression. The if part after n is a guard — a boolean condition that further restricts the match. Guards are covered in the next topic.

Matching Tuples

Pattern matching works on tuples — you describe the structure and Gleam extracts each part:

let point = #(3, 7)

let description = case point {
  #(0, 0) -> "Origin"
  #(x, 0) -> "On X-axis at " <> int.to_string(x)
  #(0, y) -> "On Y-axis at " <> int.to_string(y)
  #(x, y) -> "Point at (" <> int.to_string(x) <> ", " <> int.to_string(y) <> ")"
}
// "Point at (3, 7)"

Tuple Pattern Matching
──────────────────────────────────────────────────
point = #(3, 7)

Pattern #(0, 0)  → 3 ≠ 0 → skip
Pattern #(x, 0)  → 7 ≠ 0 → skip
Pattern #(0, y)  → 3 ≠ 0 → skip
Pattern #(x, y)  → matches! x = 3, y = 7 → use

Matching Lists

Lists have their own special patterns using the [head, ..tail] syntax:

let numbers = [1, 2, 3, 4]

case numbers {
  []           -> "Empty list"
  [x]          -> "One element: " <> int.to_string(x)
  [x, y]       -> "Two elements"
  [first, ..rest] -> "Starts with " <> int.to_string(first)
}
// "Starts with 1"

List Pattern Breakdown
──────────────────────────────────────────────────
[first, ..rest] matches [1, 2, 3, 4]

first = 1
rest  = [2, 3, 4]

Think of it like:
[ first | rest ]
[  1   | 2, 3, 4 ]

Matching Custom Types

Custom types (covered later) use constructor patterns:

type Shape {
  Circle(radius: Float)
  Rectangle(width: Float, height: Float)
}

pub fn area(shape: Shape) -> Float {
  case shape {
    Circle(r)         -> 3.14159 *. r *. r
    Rectangle(w, h)   -> w *. h
  }
}

Nested Patterns

Patterns nest inside each other for complex matching:

let data = #("success", 200)

case data {
  #("success", code) if code < 300 -> "All good!"
  #("error", code)   if code >= 500 -> "Server problem"
  #(_, _)            -> "Something else"
}

The Exhaustiveness Guarantee

Gleam's compiler checks that your case expression covers every possible value. If you forget a case, the compiler refuses to compile the program.


Compiler Safety Check
──────────────────────────────────────────────────
type Direction { North, South, East, West }

case direction {
  North -> "Up"
  South -> "Down"
  East  -> "Right"
  // ✗ COMPILE ERROR: West is not covered!
}

// Fix:
case direction {
  North -> "Up"
  South -> "Down"
  East  -> "Right"
  West  -> "Left"   // ✓ All cases covered
}

This exhaustiveness check catches missing logic at compile time — not when a user triggers an unexpected case at 3 AM in production.

Practical Example — HTTP Response Handler

import gleam/io

type Response {
  Success(body: String)
  Redirect(url: String)
  ClientError(code: Int)
  ServerError(code: Int)
}

pub fn handle(resp: Response) -> String {
  case resp {
    Success(body)       -> "200 OK: " <> body
    Redirect(url)       -> "302 Redirect to: " <> url
    ClientError(404)    -> "404 Not Found"
    ClientError(code)   -> "Client Error: " <> int.to_string(code)
    ServerError(code)   -> "Server Error: " <> int.to_string(code)
  }
}

pub fn main() {
  io.println(handle(Success("Welcome!")))
  io.println(handle(ClientError(404)))
  io.println(handle(ServerError(503)))
}

Summary


Pattern Matching Cheat Sheet
──────────────────────────────────────────────────
Pattern          │ Matches
─────────────────┼──────────────────────────────
42               │ Exactly the value 42
"hello"          │ Exactly the string "hello"
True             │ The boolean True
n                │ Any value, binds it to n
_                │ Any value, discards it
#(x, y)          │ A tuple, binds x and y
[]               │ An empty list
[x]              │ A list with one element
[head, ..tail]   │ A non-empty list
Constructor(x)   │ A custom type variant

Pattern matching makes your intent visible in the code. Instead of asking "what is the value?" and checking with if, you describe every shape you expect and let Gleam route execution to the right branch automatically.

Leave a Comment

Your email address will not be published. Required fields are marked *