Flattening Error Logic in Gleam with Result and use
Stop nesting case expressions in Gleam. Learn how to use the Result type and the `use` keyword to create flat, readable, and safe error-handling pipelines.
09 Oct 2025, 11:55 UTC

The Problem: The "Pyramid of Doom"
When writing functions that perform multiple fallible steps—like parsing a request, validating a user, and fetching a record—you often end up with deeply nested case expressions. This "pyramid of doom" obscures the actual business logic, making the code harder to read and maintain because the "happy path" is pushed further and further to the right of the screen.
The Solution: Sequential Binding with use
Gleam solves this using the Result type and the use keyword. The Result type is a sum type that is either Ok(value) (success) or Error(e) (failure). While case expressions are great for branching, the use keyword allows for sequential binding.
When you use use value <- expression, Gleam checks the result of the expression. If it is Ok, it unwraps the value and continues to the next line. If it is Error, it immediately returns that error from the entire function, bypassing all subsequent steps. This transforms nested blocks into a flat list of operations.
Worked Example: Processing User Data
Consider a scenario where we must decode a JSON string, ensure a required field exists, and validate that a numeric value is within a specific range.
import gleam/io
import gleam/json
import gleam/result
pub fn calculate_user_score(json_string: String) -> Result(Int, String) {
// 1. Decode the JSON string
use decoded <- json.decode_string(json_string)
|> result.map_err(fn(_) { "Invalid JSON format" })
// 2. Extract the "age" field
use age_json <- decoded
|> json.get("age")
|> result.map_err(fn(_) { "Missing age field" })
// 3. Convert JSON value to an Integer
use age <- age_json
|> json.int
|> result.map_err(fn(_) { "Age must be a number" })
// 4. Validate age is positive
use validated_age <- if age > 0 {
Ok(age)
} else {
Error("Age must be a positive integer")
}
// Final Step: Calculate a score based on the validated age
Ok(validated_age * 10)
}
In this flow, if json.decode_string fails, the function returns Error("Invalid JSON format") immediately. The rest of the logic is never executed, and you avoid writing four separate case matches.
Trade-offs and Limitations
While ergonomic, this pattern has specific considerations:
- Allocation: Every
OkandErroris a tuple allocation. In extremely tight loops (millions of iterations per second), this may introduce more overhead than raw exceptions. For most application logic, this is negligible. - Foreign Function Interface (FFI): When calling Erlang or Elixir code, those languages often raise exceptions rather than returning
Resulttypes. A crash in an Erlang library will bypass yourusechain entirely. To handle this, you must wrap the call in a utility (like those found ingleam_otp) that catches the exception and converts it into a GleamError.
Verifying the Implementation
To test this logic, create a project with gleam new result_demo and add the function to src/main.gleam. You can verify the propagation by running the following in your main function:
pub fn main() {
let valid_json = "{\"age\": 25}"
let invalid_json = "{\"age\": -5}"
io.debug(calculate_user_score(valid_json)) // Expected: Ok(250)
io.debug(calculate_user_score(invalid_json)) // Expected: Error("Age must be a positive integer")
}
Run the code using gleam run. To see how Gleam implements this under the hood, run gleam build -t erlang and inspect the compiled BEAM bytecode; you will see that Result is represented as standard {:ok, Value} and {:error, Reason} tuples.
Actionable Closing
If your Gleam functions are becoming deeply nested with case statements, migrate them to use bindings. Start by identifying the "happy path" of your function and listing those steps sequentially. This not only cleans up the visual structure of your code but makes error propagation explicit and predictable.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.