Gleam's Result Type and Exhaustive Pattern Matching for Production Error Handling
Gleam's Result type and compiler-enforced exhaustiveness prevent runtime errors in production apps, with practical combinators and a worked example for robust error handling.
12 Jun 2026, 18:21 UTC

The Result type at a glance
In Gleam, Result a b is a sum type with two variants: Ok(value) for success and Err(value) for failure. Because it's a sum type, the type checker knows exactly which constructors exist. If you pattern match on a Result and leave out one variant, the compiler flags an exhaustiveness error. There are no hidden code paths, and no way for an Err to "escape" unnoticed at runtime.
Exhaustive pattern matching & the Gleam compiler
The real power clicks when you run gleam build. Try matching only on Ok and omitting Err:
case result {
Ok(value) -> ...
}
The compiler will halt with an error like "non-exhaustive match: missing Err branch". That immediate feedback is the difference between discovering a bug in production and catching it during development. The same check applies to functions that return Result – every caller must handle both Ok and Err, or the build fails.
Composing fallible steps with combinators
Handling multiple fallible steps without nesting case expressions is where Gleam’s combinators shine. The Result type integrates with the Optional type via map, andThen, and orElse:
result.map(fn)transforms theOkvalue, passingErrthrough unchangedresult.andThen(fn)chains a function that returns anotherResultresult.orElse(fn)provides a fallbackErrvalue or anotherResult
Consider a configuration‑loading scenario. You might read a value, parse it, and validate it, each step potentially failing. Instead of nesting three case expressions, you can write:
let config =
result.map(&String.trim)
.andThen(&parse_integer)
.orElse(&default_config)
Here, if any step returns Err, the chain short‑circuits and the default is used only when the final step fails. The Optional type works similarly, and Gleam’s type checker ensures you’re not accidentally mixing the two without a conversion step.
When Results get nested & how to keep code readable
Deeply nested Result types can make code harder to read, especially in large pipelines. A common pattern is to define a domain‑specific error type that carries enough context without proliferating variants:
type AppError {
NotFound(String)
Validation(String)
Internal(String)
}
Wrapping intermediate errors into this type keeps the outer function signature clean and lets you pattern match on a single error enum throughout your app. When pipelines grow, consider extracting intermediate steps into named functions with descriptive names – the compiler will still enforce exhaustiveness, but your business logic stays at the surface.
Verifying the approach locally
To see the exhaustiveness check in action, create a new Gleam project:
gleam new my_app
cd my_app
Add a function that returns a Result and deliberately omit one branch in the matching code. Run:
gleam build
The build will fail with an exhaustiveness error pointing to the missing Err branch. Fix the match, run gleam build again, and the command succeeds only when every constructor is handled. You can also run gleam test to verify the full test suite type‑checks without implicit error escapes.
Closing thought
Gleam’s Result type and compiler‑enforced exhaustiveness give you a safety net that most production runtimes don’t provide. Errors become compile‑time concerns, and combinators let you chain fallible operations without the boilerplate of manual case expressions. When pipelines start to feel noisy, a domain‑specific error type or a well‑named intermediate function can restore readability while keeping the compiler’s guarantees. If you’re evaluating error‑handling strategies for a new service, Gleam’s approach is worth a spin – run gleam build on your current code and see how many previously hidden error paths surface.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.