Choosing Between Result and Panic for Error Handling in Gleam
Learn when to use Result types versus panics in Gleam to balance type safety with code readability and system stability.
12 Mar 2026, 23:07 UTC

The Engineering Trade-off: Explicit vs. Implicit Failure
In Gleam, you face a fundamental decision when a function can fail: do you return a Result type or allow the program to panic? Choosing the wrong approach leads to either bloated, verbose code or fragile production environments where unexpected crashes occur in recoverable scenarios.
The core takeaway is that Result types are for expected failures (network timeouts, validation errors, missing files), while panics are for impossible states (index out of bounds, logic invariants that should never be broken).
Comparison of Error Handling Strategies
| Feature | Result (Ok/Error) | Panic |
|---|---|---|
| Type Signature | Explicitly declared (e.g., Result(User, String)) |
Hidden; return type remains the success value |
| Compiler Enforcement | Forces the caller to handle the error case | No compile-time warning |
| Runtime Impact | Controlled flow of execution | Immediate process termination (BEAM crash) |
| Use Case | Business logic, user input, I/O | Developer bugs, unrecoverable system state |
Trade-offs and Decision Logic
Using Result shifts the burden of error handling to the caller. While this increases safety, it can lead to boilerplate fatigue if you are chaining many fallible operations. Gleam mitigates this with the use syntax, which flattens the logic and avoids deep nesting of case expressions.
Conversely, a panic is an admission that the current state is invalid and the process cannot possibly continue. Because Gleam typically targets the Erlang VM (BEAM), a panic crashes the individual process. While the "let it crash" philosophy of BEAM allows supervisors to restart processes, relying on this for business logic (like a wrong password) is an anti-pattern that obscures the cause of failure and degrades performance.
Implementation: Handling Fallible Logic
To implement a recoverable error flow, define your return type as a Result. The following example demonstrates a user lookup process where multiple steps could fail. Run it in a standard Gleam project (Gleam v1.0.0 or later assumed); no special permissions are required.
import gleam/io
import gleam/result
pub type User {
User(id: Int, name: String)
}
fn find_user_by_id(id: Int) -> Result(User, String) {
case id {
1 -> Ok(User(1, "Alice"))
2 -> Ok(User(2, "Bob"))
_ -> Error("User not found in database")
}
}
fn validate_user_access(user: User) -> Result(User, String) {
// Simulate a permission check
case user.id == 2 {
True -> Error("User is suspended")
False -> Ok(user)
}
}
pub fn get_authorized_user(id: Int) -> Result(Nil, String) {
// The 'use' syntax unwraps the Ok value or returns the Error early
use user <- result.try(find_user_by_id(id))
use authorized_user <- result.try(validate_user_access(user))
io.println("Access granted to: " <> authorized_user.name)
Ok(Nil)
}
Validation and Diagnostics
To verify your decision, attempt to use the User object from find_user_by_id without using result.try or a case match. The Gleam compiler will report a type mismatch error because you are attempting to treat a Result(User, String) as a User. This compile-time failure is the safety guarantee you are paying for with the extra syntax.
Checking the result:
- Success path: Calling
get_authorized_user(1)should print "Access granted to: Alice" and returnOk(Nil). - Expected error path: Calling
get_authorized_user(3)should returnError("User not found in database")without crashing the VM. - Panic path: If you used
panicinstead ofError, the process would terminate with a crash report visible in the BEAM runtime output.
Interoperability Limitations
When calling Erlang or Elixir functions via the Foreign Function Interface (FFI), be aware that those languages may throw exceptions. Gleam's type system cannot track these implicit exceptions. If an external function can fail, wrap the FFI call in a Gleam function that converts the external exception into a Result type to maintain type safety within your Gleam codebase.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.