The match Expression
The match expression is F#'s primary tool for branching on the shape and value of data, replacing chains of if/elif with a single expression that examines a value against a series of patterns and evaluates the branch tied to the first pattern that matches. A simple example, "match x with | 0 -> \"zero\" | _ -> \"nonzero\"", checks whether x equals the literal 0 and falls through to the wildcard _ -- which matches anything -- otherwise.
Cricket analogy: An umpire reviewing a run-out via DRS checks a sequence of possibilities -- bat grounded, bails off, foot behind the line -- and rules on the first condition that clearly applies, much like match evaluates patterns in order and commits to the first one that fits.
Matching on Discriminated Unions
Pattern matching shows its full power against discriminated unions (DUs), where each case can carry different data and match both identifies which case a value is and extracts its payload in one step. Given "type Shape = Circle of float | Rectangle of float * float", the expression "match shape with | Circle r -> Math.PI * r * r | Rectangle (w, h) -> w * h" simultaneously checks the case tag and binds r or (w, h) for use in that branch's body.
Cricket analogy: Classifying a dismissal in cricket as Bowled, Caught, or LBW while simultaneously noting the specific fielder or delivery involved mirrors matching a DU case like Caught fielder, which identifies the case and extracts the fielder's name in one step.
type Shape =
| Circle of radius: float
| Rectangle of width: float * height: float
| Triangle of baseLen: float * height: float
let area shape =
match shape with
| Circle r -> System.Math.PI * r * r
| Rectangle (w, h) -> w * h
| Triangle (b, h) -> 0.5 * b * h
let describe n =
match n with
| x when x < 0 -> "negative"
| 0 -> "zero"
| x when x % 2 = 0 -> "positive even"
| _ -> "positive odd"
printfn "%f" (area (Circle 3.0))
printfn "%s" (describe -4)Exhaustiveness Checking and Wildcards
The F# compiler statically checks that a match expression covers every possible case of the value being matched, and emits a warning (FS0025) if any case is left unhandled, catching an entire class of bugs at compile time rather than at runtime. Adding a new case to a discriminated union will cause every existing match over that union to become non-exhaustive again, immediately flagging every call site that needs updating.
Cricket analogy: A pre-match checklist that must account for every possible weather outcome -- clear, rain delay, or bad light -- before the umpires proceed mirrors F#'s compiler refusing to let a match compile cleanly unless every DU case is handled.
A catch-all wildcard pattern _ silences the compiler's exhaustiveness check, which means adding a new case to a discriminated union later will NOT trigger a warning at any match site that already has a _ branch -- a common source of silently-wrong behavior after a union is extended.
Guards and Active Patterns
A when guard attaches an additional boolean condition to a pattern, letting you combine structural matching with arbitrary logic, as in "match n with | x when x < 0 -> \"negative\" | 0 -> \"zero\" | _ -> \"positive\"", where the first branch only fires if the bound value x also satisfies x < 0. Guards are evaluated only after the pattern itself matches, so they can reference any values already bound by that pattern.
Cricket analogy: A fielding restriction rule that only applies during the powerplay overs -- a condition layered on top of which over is being bowled -- mirrors a when guard adding an extra boolean check on top of a matched pattern.
Active patterns, declared with banana-shaped brackets like "let (|Even|Odd|) n = if n % 2 = 0 then Even else Odd", let you define custom, named patterns that classify or decompose values in ways the built-in pattern syntax can't express directly, and they can then be used inside match exactly like a built-in case, e.g. "match n with | Even -> \"even\" | Odd -> \"odd\"".
Cricket analogy: A scoring analyst who defines a reusable custom classification like 'boundary over' (an over with 2+ fours or sixes) and applies it across every match report mirrors an active pattern like (|Even|Odd|) used repeatedly across match expressions.
Pattern matching isn't limited to discriminated unions -- you can destructure tuples (match point with | (0, 0) -> "origin" | (x, 0) -> "on x-axis" | _ -> "elsewhere") and lists (match xs with | [] -> "empty" | [x] -> "one item" | x :: rest -> "head and tail") directly in the same match syntax.
- match compares a value against ordered patterns and runs the first branch that fits.
- Matching a discriminated union both identifies the case and extracts its payload in one step.
- The compiler performs exhaustiveness checking and warns (FS0025) about unhandled cases.
- A when guard adds a boolean condition on top of a structural pattern.
- Active patterns let you define custom, reusable classification logic usable inside match.
- Tuples and lists can be destructured directly within match patterns.
- A wildcard _ branch suppresses exhaustiveness warnings, which can hide bugs after a union changes.
Practice what you learned
1. What compiler warning indicates a non-exhaustive match?
2. Given type Shape = Circle of float | Rectangle of float * float, what does | Circle r -> do?
3. What is the purpose of a when guard in a pattern?
4. What risk does a wildcard _ pattern introduce?
5. What does an active pattern like let (|Even|Odd|) n = ... provide?
Was this page helpful?
You May Also Like
Type Inference in F#
See how F#'s Hindley-Milner-based type inference deduces precise types without annotations, and when explicit annotations are still needed.
Functions in F#
Learn how F# treats functions as first-class values, covering let bindings, currying, partial application, and higher-order functions.
Lists and Sequences
Compare F#'s immutable linked-list list type with the lazy, deferred seq type, and learn the core operations for building and transforming both.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics