Gleam Option Types: Model Absence and Verify Exhaustive Matches
Build a small Gleam module that models a missing value with Option, run it, and confirm the compiler rejects a match that forgets None.
06 Oct 2026, 02:39 UTC

What you are building
Gleam has no null. When a value might be absent, you model that in the type: Option(a) is a built-in sum type with exactly two constructors, Some(value) and None. A sum type — sometimes called a tagged union — is a type whose values come from a fixed set of constructors, each of which may carry data.
By the end of this guide you will have a small module that describes an optional integer, and you will have confirmed that the compiler rejects a match that forgets None. That second part is the real payoff: the guarantee is enforced at build time, not by a runtime check you have to remember to write.
Prerequisites
- A Gleam toolchain on your PATH. Confirm with
gleam --versionin a terminal. - An Erlang/OTP installation, because Gleam compiles to BEAM bytecode and runs on the BEAM virtual machine.
- A writable scratch directory. The commands below create a new project inside it; nothing else on your machine is touched.
Version assumption, flagged for review: the syntax and commands here match a Gleam 1.x toolchain. The Option type is a long-standing part of the language, but helper names in the standard library and the wording of compiler messages have changed across releases. Run gleam --version and check the official documentation for your version before relying on a specific helper name or error text.
Step 1: Create a scratch project
Run this from the terminal, in the parent directory where you want the project to live. No elevated permissions are needed.
gleam new option_demo
cd option_demo
Expected check: the command creates gleam.toml, a src/ directory containing option_demo.gleam, and a test/ directory. If gleam new is not recognised, your toolchain predates that command; check your version before continuing.
Step 2: Declare the optional value
Replace the contents of src/option_demo.gleam with the following. Option, Some and None come from the language prelude, so they need no import; gleam/int and gleam/io do.
import gleam/int
import gleam/io
fn describe(value: Option(Int)) -> String {
case value {
Some(number) -> "got " <> int.to_string(number)
None -> "nothing here"
}
}
pub fn main() {
io.println(describe(Some(42)))
io.println(describe(None))
}
The annotation Option(Int) is doing the work here. It says the argument is either an integer wrapped in Some or the None constructor — there is no third possibility, and no way to pass a bare integer by mistake.
Step 3: Match on both constructors
The case expression in describe is a pattern match. Each branch names a constructor and binds the data it carries: Some(number) binds number to the wrapped integer, while None carries nothing to bind.
Gleam checks these matches for exhaustiveness — it verifies at compile time that every constructor the type can take is covered. You do not need a fallback branch, and adding one would weaken the check.
Step 4: Build and run
From the project root:
gleam build
gleam run
gleam build compiles without running anything; gleam run compiles and then executes main. The program should print two lines, in this order:
got 42
nothing here
Expected checks
- It compiles cleanly.
gleam buildshould finish without warnings about thecaseexpression. - Both branches are reachable. The two printed lines show that
Some(42)andNoneeach took their own branch. If you see only one line, the second call was not reached. - Exhaustiveness is enforced. Delete the
None -> "nothing here"line and rungleam buildagain. The compiler should refuse to build and report a non-exhaustive pattern match. The exact wording of that message varies between releases, so read the message rather than matching it against a remembered string. Restore the line afterwards.
That third check is worth repeating whenever you add a constructor to one of your own sum types: the compiler will point at every match that is now incomplete.
Option or Result?
Both are built-in sum types, and picking the wrong one is a common source of awkward code.
| Type | Constructors | Use when | What the compiler forces |
|---|---|---|---|
Option(a) | Some(a), None | A value may simply be absent, with nothing useful to say about why | You handle None |
Result(a, e) | Ok(a), Error(e) | An operation can fail and the caller needs the reason | You handle Error(e) |
If you find yourself wrapping a None in a Result just to carry an error message, Result was probably the right choice from the start. Conversely, using Result where absence is the only outcome adds a payload nobody reads.
Reducing the unwrapping
Matching every Option by hand gets repetitive. The standard library's gleam/option module provides helpers for the common shapes — mapping over the wrapped value, supplying a default, and chaining optional steps. Helper names have been added and renamed across releases, so check the module documentation for your installed version rather than copying names from an older tutorial. Flagged for review: confirm the current names before building a codebase around them.
Limitations to keep in mind
- Nesting is real.
Option(Option(Int))has three distinct states:Some(Some(1)),Some(None)andNone. If your data can produce that shape, decide explicitly whatSome(None)means; do not assume it collapses. - A catch-all branch removes the guarantee. Adding
_ -> ...to a match silences the exhaustiveness check for that expression. It compiles today and stops protecting you the moment a constructor is added. - The guarantee stops at the interop boundary. Erlang and Elixir libraries signal absence with their own conventions rather than
None. Convert deliberately where your Gleam code calls into them, and check current documentation for the recommended conversion helpers. Flagged for review: the exact conventions and helper names depend on the library and the Gleam release. - Compile-time checking is not a supervision strategy. Gleam runs on the BEAM, where long-lived processes accumulate state if they are not supervised and restarted correctly. That is a separate topic from
Optionand outside this guide's scope.
Cleaning up
The only state this guide created is the option_demo directory and its build output. To undo everything, delete the project directory from the terminal:
cd ..
rm -rf option_demo
If you only want to discard compiled artifacts and keep the source, remove the build/ directory inside the project, or use your toolchain's clean command if the version you have provides one.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.