Gleam Case Expression
The case expression is Gleam's primary decision-making tool. It matches a value against multiple patterns and executes the body of the first matching pattern. Unlike a simple if-else, case handles complex structures, multiple branches, and compiler-verified exhaustiveness.
Basic case Syntax
case <value> {
<pattern1> -> <expression1>
<pattern2> -> <expression2>
_ -> <default expression>
}The entire case expression evaluates to a single value — the result of whichever branch matched. Every branch must return the same type.
case Structure Diagram
──────────────────────────────────────────────────
case traffic_light { ← the value being checked
"red" -> "Stop" ← branch 1
"yellow" -> "Slow down" ← branch 2
"green" -> "Go" ← branch 3
_ -> "Unknown" ← catch-all
}
↓
Returns one String value
case as an Expression
A case block returns a value. Assign it to a variable just like any other expression:
let grade = 82
let letter = case grade {
g if g >= 90 -> "A"
g if g >= 80 -> "B"
g if g >= 70 -> "C"
g if g >= 60 -> "D"
_ -> "F"
}
// letter = "B"There is no semicolon, no assignment inside the branch, no early return. The case expression is one clean unit that produces one value.
Matching on Multiple Values at Once
Match a tuple of values when a decision depends on several things at once:
let is_admin = True
let is_verified = False
let access_level = case #(is_admin, is_verified) {
#(True, True) -> "Full access"
#(True, False) -> "Admin, needs verification"
#(False, True) -> "Verified user"
#(False, False) -> "Guest"
}
// "Admin, needs verification"
Two-Value Decision Table
──────────────────────────────────────────────────
is_admin │ is_verified │ Access Level
─────────┼─────────────┼─────────────────────────
True │ True │ Full access
True │ False │ Admin, needs verification
False │ True │ Verified user
False │ False │ Guest
Matching String Patterns
pub fn command_response(cmd: String) -> String {
case cmd {
"help" -> "Available commands: start, stop, status"
"start" -> "Service starting..."
"stop" -> "Service stopping..."
"status"-> "Service is running"
other -> "Unknown command: " <> other
}
}The variable other in the last branch captures the unmatched string value. This is more useful than _ when you want to include the original value in the response.
Matching Nested Structures
Nest patterns to match values within values:
type ApiResult {
Ok(code: Int, body: String)
Err(code: Int, reason: String)
}
pub fn describe(result: ApiResult) -> String {
case result {
Ok(200, body) -> "Success: " <> body
Ok(code, _) -> "Non-standard success: " <> int.to_string(code)
Err(404, _) -> "Resource not found"
Err(500, reason) -> "Server crash: " <> reason
Err(code, reason) -> "Error " <> int.to_string(code) <> ": " <> reason
}
}
Nested Pattern Match Flow
──────────────────────────────────────────────────
result = Err(404, "page missing")
Ok(200, body)? → No, it's Err
Ok(code, _)? → No, it's Err
Err(404, _)? → Yes! ✓
→ Returns "Resource not found"
Multiple Patterns per Branch
Use the | (pipe) symbol to match multiple patterns in one branch:
let day = "Saturday"
let day_type = case day {
"Saturday" | "Sunday" -> "Weekend"
"Monday" | "Friday" -> "Start or end of work week"
_ -> "Midweek"
}
// "Weekend"The case Expression Must Be Exhaustive
Gleam guarantees every possible value has a matching branch. The compiler rejects non-exhaustive case expressions:
Exhaustiveness in Action
──────────────────────────────────────────────────
type Season { Spring, Summer, Autumn, Winter }
case season {
Spring -> "🌸"
Summer -> "☀️"
Autumn -> "🍂"
// ✗ COMPILE ERROR: Winter is not covered
// Add this to fix:
Winter -> "❄️"
}
This guarantee means you can never forget to handle a case. The compiler acts as a safety net that catches omissions before they become bugs.
case Inside Functions
import gleam/int
pub fn fizzbuzz(n: Int) -> String {
case #(n % 3 == 0, n % 5 == 0) {
#(True, True) -> "FizzBuzz"
#(True, False) -> "Fizz"
#(False, True) -> "Buzz"
#(False, False) -> int.to_string(n)
}
}
FizzBuzz Decision Table
──────────────────────────────────────────────────
n % 3 == 0 │ n % 5 == 0 │ Output
───────────┼────────────┼─────────────
True │ True │ "FizzBuzz"
True │ False │ "Fizz"
False │ True │ "Buzz"
False │ False │ n as string
case vs if
Gleam does have a basic if expression for simple boolean decisions, but case is preferred because it scales naturally to complex conditions and the compiler verifies it.
When to Use Each
──────────────────────────────────────────────────
Situation │ Use
────────────────────────────────┼─────────────────
Simple True/False check │ case or bool check
Multiple possible values │ case
Matching tuple or list shape │ case (required)
Matching custom type variants │ case (required)
Combining multiple conditions │ case with tuples
Practical Example — Shipping Cost Calculator
import gleam/io
type ShippingZone { Domestic, Regional, International }
type ShippingSpeed { Standard, Express, Overnight }
pub fn shipping_cost(zone: ShippingZone, speed: ShippingSpeed) -> Float {
case #(zone, speed) {
#(Domestic, Standard) -> 50.0
#(Domestic, Express) -> 100.0
#(Domestic, Overnight) -> 200.0
#(Regional, Standard) -> 150.0
#(Regional, Express) -> 300.0
#(Regional, Overnight) -> 500.0
#(International, Standard) -> 500.0
#(International, Express) -> 900.0
#(International, Overnight) -> 1500.0
}
}
pub fn main() {
io.debug(shipping_cost(Domestic, Express)) // 100.0
io.debug(shipping_cost(International, Standard)) // 500.0
}
The case table reads like a pricing matrix. New zones or speeds are added by inserting rows — the compiler then enforces that all combinations are handled.
Key Points
case Expression Rules
──────────────────────────────────────────────────
1. Evaluates to the result of the matched branch
2. Every branch must return the same type
3. Compiler enforces exhaustiveness
4. Use | to combine multiple patterns in one branch
5. Use _ to catch remaining cases
6. Named variables in patterns capture the matched value
7. Works on primitives, tuples, lists, and custom types
The case expression is the heart of Gleam control flow. Writing good case expressions produces programs that handle every scenario explicitly — making them robust, readable, and safe.
