Resolving OCaml Pattern-Matching Exhaustiveness Warnings
Learn how to diagnose and fix OCaml's 'Warning 8' non-exhaustive pattern-matching errors to prevent runtime Match_failure exceptions.
25 Jul 2026, 21:07 UTC

The Problem: Warning 8
When the OCaml compiler emits Warning 8: this pattern-matching is not exhaustive, it means there is at least one possible value of the type being matched that does not map to any of the provided branches. If you have enabled -warn-error +8, this warning becomes a hard build failure.
The primary risk of ignoring this warning is a Match_failure exception at runtime, which crashes the program when an unhandled case is encountered.
Diagnostic Matrix
Use this table to identify the root cause based on the type you are matching.
| Type Category | Common Cause | Diagnostic Signal |
|---|---|---|
| Variant Types | Forgotten constructor | Warning lists a specific constructor (e.g., None or Error _) |
| Option/List | Partial match | Missing None or [] case |
| Polymorphic Variants | Unexpected tag | Warning indicates a tag not present in the current match block |
| Tuples/Records | Missing combination | Warning suggests a specific combination of values (e.g., (true, false)) |
Step-by-Step Resolution Process
- Isolate the Warning: Run the compiler with
-warn-error +8to treat these warnings as errors. This prevents non-exhaustive matches from slipping into production.# Run via ocamlc ocamlc -warn-error +8 main.ml - Inspect the Type Definition: Locate the definition of the type being matched. List every constructor defined for that type. If the type is defined in an external module, check the
.mliinterface file. - Map Constructors to Branches: Compare the list of constructors against the
matchblock. Identify which specific constructor is missing. - Apply the Fix: Based on the finding, choose one of the following implementation strategies:
- Explicit Handling: Add a branch for the missing constructor. This is the safest approach for business logic.
- The Catch-all: Use the underscore
_pattern to handle all remaining cases. Use this only for extensible types or when all other cases share the same logic. - Impossible Cases: If you know a case is logically impossible due to external constraints, use
failwithorassert falseto document the invariant.
Example: Fixing a Partial Match
Consider a function handling a network response:
type response = Success of string | Timeout | ServerError
(* Incorrect: Warning 8 (Missing Timeout and ServerError) *)
let handle_res r =
match r with
| Success msg -> print_endline msg
To fix this, provide explicit branches for the remaining constructors:
let handle_res r =
match r with
| Success msg -> print_endline msg
| Timeout -> print_endline "Request timed out"
| ServerError -> print_endline "Internal server error"
Verification and Limitations
To verify the fix, re-run the compilation command. The absence of Warning 8 confirms the match is exhaustive. You can also verify the logic by writing a test case that specifically passes the previously missing constructor to the function.
Limitations: Exhaustiveness checking is a static analysis. It cannot detect logic errors where a branch is present but performs the wrong action. Additionally, using _ (the wildcard) silences the warning but removes the compiler's ability to alert you if you add new constructors to the type in the future.
Escalation Criteria
Escalate the issue to a senior architect or lead developer if:
- The type is defined in a third-party library and frequently changes, making explicit matches brittle.
- The match logic requires complex nested patterns that make the code unreadable.
- You find yourself using
assert falsein more than 20% of your match branches, suggesting the type definition itself may be too broad for the current context.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.