Pattern Matching in OCaml: Catch Logic Errors Before They Catch You
OCaml's pattern matching catches missing cases at compile time, preventing runtime Match_failure exceptions. Use -warn-error +8 to elevate warnings to errors.
01 Sept 2025, 01:29 UTC

The Hidden Bug in Your OCaml Code
When you write OCaml code that handles different cases of an algebraic data type, the compiler is already doing the heavy lifting. If you miss a case you can introduce a runtime failure that could have been caught at compile time.
Consider a simple state machine for handling user authentication states.
How OCaml's Exhaustiveness Checking Works
OCaml's match expression does not just route control flow. It actively analyzes whether you have covered every possible constructor of your variant type. When you miss a case, the compiler emits a warning that can be elevated to an error.
This works because OCaml maintains precise knowledge of your type definitions. When you add a new constructor to a variant, any existing match expressions that do not handle it will trigger warnings.
See It in Action
Example with a user authentication system:
type auth_state =
| Logged_out
| Logged_in of string
| Auth_error of string
let handle_auth = function
| Logged_out -> "Please log in"
| Logged_in user -> "Welcome " ^ user
(* Missing Auth_error case - compiler will warn *)
If you compile this with ocamlc -warn-error +8 auth.ml, you will get Warning 8 about non-exhaustive patterns with no case matching Auth_error _.
Guards: Powerful, But Use With Care
OCaml lets you add when clauses to patterns, which are boolean conditions evaluated after the pattern matches. This is useful for filtering cases without nesting:
let analyze_score score = match score with
| n when n < 0 -> "Invalid"
| n when n = 100 -> "Perfect!"
| n when n > 80 -> "Good"
| n -> "Needs improvement"
Complex guards can obscure redundancy detection and reduce readability, so use them judiciously.
Why This Matters for Engineering
In production systems, missing a case in a match expression can lead to Match_failure exceptions at runtime. OCaml's exhaustiveness checking shifts this class of errors from runtime to compile time.
Practical Verification
- Use ocamlc -warn-error +8 in your build to treat warnings as errors
- When adding new constructors to variants, check existing match expressions for updates
- Test guard semantics in the REPL to confirm they are evaluated only after pattern match
The Trade-Off: Flexibility vs. Safety
While exhaustiveness checking catches many errors, it can create friction when you intentionally want to ignore cases. Using _ as a catch-all silences the warning and can hide bugs if new constructors are added later.
let debug_handler = function
| Logged_out -> "Debug: logged out"
| Logged_in user -> "Debug: " ^ user
| _ -> "Debug: unknown state"
Takeaway: Let the Compiler Be Your Linter
OCaml's pattern matching with exhaustiveness checking is a core safety feature. Treating compiler warnings as errors and understanding when to use catch-all patterns helps catch logic errors before production.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.