← Back to all guides

Gleam TLDR

A rapid reference guide to the Gleam programming language. Type-safe functional programming on the BEAM.

v1.12.0 — October 2025

What is Gleam?

Gleam is a friendly, type-safe functional programming language that compiles to Erlang and JavaScript. It runs on the battle-tested BEAM VM, giving you access to Erlang's world-class concurrency and fault tolerance.

Type Safety

Statically typed with full inference. Catch errors at compile time, not in production. No null, no exceptions.

Fault Tolerant

Runs on the Erlang VM (BEAM). Millions of lightweight processes, self-healing supervisors, battle-tested since 1986.

Friendly

Helpful error messages, clean syntax inspired by Rust and Elixir. Designed to be approachable.

Dual Target

Compiles to Erlang for the BEAM or JavaScript for browsers and Node.js. Use existing libraries from both ecosystems.

Basics

Hello World

import gleam/io

pub fn main() {
  io.println("Hello, world!")
}

Variables

// Variables are immutable
let name = "Gleam"
let age: Int = 1  // optional type annotation

// Shadowing (rebinding) is allowed
let x = 5
let x = x + 1  // x is now 6

// Type aliases
pub type UserId = Int

Basic Types

// Integers (arbitrary precision on BEAM)
let int = 42
let big = 1_000_000
let hex = 0xF
let binary = 0b1010

// Floats (64-bit, different operators!)
let float = 3.14
let sum = 1.0 +. 2.5      // float addition
let product = 3.0 *. 4.0  // float multiplication

// Strings (UTF-8)
let greeting = "Hello, " <> "world!"  // concatenation

// Booleans
let yes = True
let no = False
let result = True && False  // False
let either = True || False  // True
Gleam has separate operators for Int and Float arithmetic! + for Int, +. for Float.

Operators

Operation Int Float
Addition + +.
Subtraction - -.
Multiplication * *.
Division / /.
Remainder %
Comparison < > <= >= <. >. <=. >=.
Equality == != (works for all types)

Data Types

Lists

// Lists are singly-linked, homogeneous
let numbers: List(Int) = [1, 2, 3]
let empty: List(String) = []

// Prepend with spread
let more = [0, ..numbers]  // [0, 1, 2, 3]

// Pattern match on lists
case numbers {
  [] -> "empty"
  [first] -> "single element"
  [first, ..rest] -> "has elements"
}

Tuples

// Fixed-size, mixed types
let pair = #(1, "hello")
let triple = #(1, 2.0, "three")

// Access by index
let first = pair.0   // 1
let second = pair.1  // "hello"

// Destructure
let #(x, y) = pair

Nil

// Nil is Gleam's unit type (like void)
let nothing: Nil = Nil

// Functions that don't return anything return Nil
pub fn say_hello() -> Nil {
  io.println("Hello!")
}

Blocks

// Blocks create a new scope and return last expression
let value = {
  let x = 10
  let y = 20
  x + y  // returns 30
}

Functions

Defining Functions

// Public function
pub fn add(a: Int, b: Int) -> Int {
  a + b  // implicit return (last expression)
}

// Private function (no pub)
fn helper(x: Int) -> Int {
  x * 2
}

Anonymous Functions

// Lambda syntax
let double = fn(x) { x * 2 }
let result = double(5)  // 10

// With type annotations
let add = fn(a: Int, b: Int) -> Int { a + b }

// Closures capture environment
let multiplier = 3
let triple = fn(x) { x * multiplier }

Labelled Arguments

// Define with labels
pub fn greet(name name: String, greeting msg: String) -> String {
  msg <> ", " <> name <> "!"
}

// Call with labels (any order)
greet(greeting: "Hello", name: "Lucy")
greet(name: "Lucy", greeting: "Hi")

// Shorthand: same internal and external name
pub fn create_user(name: String, age age: Int) -> User {
  User(name: name, age: age)
}

Function Capture

// Create partial function with _
let add_one = add(1, _)
let result = add_one(5)  // 6

// Equivalent to:
let add_one = fn(x) { add(1, x) }

Pipe Operator

// Chain function calls with |>
"hello world"
|> string.uppercase
|> string.reverse
|> io.println

// Equivalent to:
io.println(string.reverse(string.uppercase("hello world")))

// Pipe to any argument position
5
|> int.to_string
|> string.append("Number: ", _)

Generic Functions

// Type variables are lowercase
pub fn identity(x: a) -> a {
  x
}

pub fn pair(a: first, b: second) -> #(first, second) {
  #(a, b)
}

// Higher-order generic function
pub fn twice(f: fn(a) -> a, x: a) -> a {
  f(f(x))
}

Pattern Matching

Gleam uses case expressions for pattern matching. The compiler ensures all cases are handled.

Case Expressions

// Basic case
case x {
  0 -> "zero"
  1 -> "one"
  _ -> "other"  // wildcard matches anything
}

// With variable binding
case x {
  0 -> "zero"
  n -> "got: " <> int.to_string(n)
}

List Patterns

case my_list {
  [] -> "empty"
  [only] -> "single: " <> only
  [first, second] -> "exactly two"
  [head, ..tail] -> "first: " <> head
  [_, _, ..rest] -> "at least two"
}

String Patterns

// Match string prefix
case text {
  "Hello, " <> name -> "Greeting for: " <> name
  "Goodbye, " <> name -> "Farewell to: " <> name
  _ -> "Unknown format"
}

Alternative Patterns

// Multiple patterns with |
case month {
  1 | 3 | 5 | 7 | 8 | 10 | 12 -> 31
  4 | 6 | 9 | 11 -> 30
  2 -> 28
  _ -> 0
}

Guards

// Add conditions with if
case x {
  n if n < 0 -> "negative"
  n if n > 0 -> "positive"
  _ -> "zero"
}

// Guards with lists
case numbers {
  [first, ..] if first > 100 -> "starts big"
  [first, ..rest] -> "normal list"
  [] -> "empty"
}

Pattern Aliases

// Bind entire pattern with 'as'
case pair {
  #(a, b) as tuple -> {
    io.debug(tuple)  // use the whole thing
    a + b            // also use the parts
  }
}

Multiple Subjects

// Match multiple values at once
case x, y {
  0, 0 -> "both zero"
  0, _ -> "x is zero"
  _, 0 -> "y is zero"
  _, _ -> "neither zero"
}

Let Assert

// Assert pattern matches (panics on failure)
let assert [first, ..] = items
let assert Ok(value) = risky_function()

// With custom error message
let assert Ok(regex) = regex.compile("ab?c+") as "Invalid regex"
Use let assert sparingly! It causes a panic on failure. Prefer case or Result for recoverable errors.

Custom Types

Simple Enums

pub type Season {
  Spring
  Summer
  Autumn
  Winter
}

let current = Summer

case current {
  Spring -> "Flowers bloom"
  Summer -> "Beach time"
  Autumn -> "Leaves fall"
  Winter -> "Snow falls"
}

Records (Single Variant)

// Type with named fields
pub type User {
  User(name: String, age: Int, active: Bool)
}

// Create instance
let alice = User(name: "Alice", age: 30, active: True)

// Access fields
let name = alice.name
let age = alice.age

// Pattern match fields
let User(name: n, age: a, active: _) = alice

Variants with Data

pub type Shape {
  Circle(radius: Float)
  Rectangle(width: Float, height: Float)
  Triangle(base: Float, height: Float)
}

pub fn area(shape: Shape) -> Float {
  case shape {
    Circle(r) -> 3.14159 *. r *. r
    Rectangle(w, h) -> w *. h
    Triangle(b, h) -> b *. h /. 2.0
  }
}

Record Updates

// Create new record with some fields changed
let alice = User(name: "Alice", age: 30, active: True)
let older_alice = User(..alice, age: 31)

// Multiple updates
let inactive_bob = User(..alice, name: "Bob", active: False)

Generic Types

// Type with type parameter
pub type Box(inner) {
  Box(value: inner)
}

let int_box: Box(Int) = Box(42)
let str_box: Box(String) = Box("hello")

// Multiple type parameters
pub type Pair(a, b) {
  Pair(first: a, second: b)
}

Opaque Types

// Hide constructor from other modules
pub opaque type PositiveInt {
  PositiveInt(value: Int)
}

// Only expose a validated constructor
pub fn new(n: Int) -> Result(PositiveInt, String) {
  case n > 0 {
    True -> Ok(PositiveInt(n))
    False -> Error("must be positive")
  }
}

Results & Options

Gleam has no exceptions or null. Use Result for operations that can fail, and Option for values that may be absent.

Result Type

// Result is defined as:
pub type Result(value, error) {
  Ok(value)
  Error(error)
}

// Function that can fail
pub fn divide(a: Int, b: Int) -> Result(Int, String) {
  case b {
    0 -> Error("division by zero")
    _ -> Ok(a / b)
  }
}

// Handle result
case divide(10, 2) {
  Ok(value) -> io.println("Result: " <> int.to_string(value))
  Error(msg) -> io.println("Error: " <> msg)
}

Result Module Functions

import gleam/result

// map: transform success value
Ok(5)
|> result.map(fn(x) { x * 2 })  // Ok(10)

// try: chain operations that can fail
Ok("42")
|> result.try(int.parse)  // Ok(42) or Error

// unwrap: get value or provide default
Error("oops")
|> result.unwrap(0)  // 0

// is_ok / is_error
result.is_ok(Ok(1))     // True
result.is_error(Ok(1))  // False

Option Type

import gleam/option.{type Option, Some, None// Option is defined as:
pub type Option(a) {
  Some(a)
  None
}

// Optional fields
pub type Person {
  Person(name: String, nickname: Option(String))
}

let alice = Person("Alice", Some("Ali"))
let bob = Person("Robert", None)

// Handle option
case alice.nickname {
  Some(nick) -> "Call me " <> nick
  None -> "No nickname"
}

Custom Error Types

pub type ParseError {
  InvalidFormat
  OutOfRange(min: Int, max: Int)
  UnknownField(name: String)
}

pub fn parse_age(input: String) -> Result(Int, ParseError) {
  case int.parse(input) {
    Error(_) -> Error(InvalidFormat)
    Ok(age) if age < 0 -> Error(OutOfRange(0, 150))
    Ok(age) if age > 150 -> Error(OutOfRange(0, 150))
    Ok(age) -> Ok(age)
  }
}

Use Expression

The use expression reduces nesting when working with callbacks and Result/Option chains. It's Gleam's most distinctive feature.

Basic Use

// Without use (deeply nested)
pub fn without_use() -> Result(Int, String) {
  result.try(get_a(), fn(a) {
    result.try(get_b(), fn(b) {
      result.try(get_c(), fn(c) {
        Ok(a + b + c)
      })
    })
  })
}

// With use (flat!)
pub fn with_use() -> Result(Int, String) {
  use a <- result.try(get_a())
  use b <- result.try(get_b())
  use c <- result.try(get_c())
  Ok(a + b + c)
}

How Use Works

// use turns remaining code into a callback
use x <- some_function(arg1, arg2)
// ...rest of function...

// Is equivalent to:
some_function(arg1, arg2, fn(x) {
  // ...rest of function...
})

Common Use Patterns

// Chaining Results
pub fn process_user(id: Int) -> Result(String, DbError) {
  use user <- result.try(fetch_user(id))
  use profile <- result.try(fetch_profile(user))
  use avatar <- result.try(fetch_avatar(profile))
  Ok(avatar.url)
}

// With list operations
pub fn process_items(items: List(Int)) {
  use item <- list.each(items)
  io.println(int.to_string(item))
}

// Mapping over results
pub fn double_result(r: Result(Int, e)) -> Result(Int, e) {
  use value <- result.map(r)
  value * 2
}

Bool Module with Use

import gleam/bool

// Early return pattern with bool.guard
pub fn validate(x: Int) -> Result(Int, String) {
  use <- bool.guard(x < 0, Error("must be positive"))
  use <- bool.guard(x > 100, Error("must be <= 100"))
  Ok(x)
}
use works with any function that takes a callback as its last argument. The standard library is designed around this pattern.

Modules & Imports

Imports

// Qualified import (recommended)
import gleam/io
import gleam/string

io.println("Hello")
string.uppercase("hello")

// Aliased import
import gleam/string as text
text.reverse("hello")

// Unqualified import
import gleam/io.{println, debug}
println("Hello")

// Import types
import gleam/option.{type Option, Some, None}
let x: Option(Int) = Some(42)

Standard Library Highlights

// gleam/list
list.map([1, 2, 3], fn(x) { x * 2 })        // [2, 4, 6]
list.filter([1, 2, 3], fn(x) { x > 1 })    // [2, 3]
list.fold([1, 2, 3], 0, fn(acc, x) { acc + x })  // 6
list.find([1, 2, 3], fn(x) { x > 1 })     // Ok(2)

// gleam/dict (hash map)
import gleam/dict
let scores = dict.from_list([#("alice", 100), #("bob", 85)])
dict.get(scores, "alice")  // Ok(100)
dict.insert(scores, "carol", 90)

// gleam/string
string.split("a,b,c", ",")   // ["a", "b", "c"]
string.join(["a", "b"], "-") // "a-b"
string.contains("hello", "ell")  // True

// gleam/int, gleam/float
int.to_string(42)      // "42"
int.parse("42")        // Ok(42)
float.to_string(3.14)  // "3.14"

Debugging

// echo keyword for quick debugging
let result = echo calculate(42)
// Prints: [src/main.gleam:5] 84

// io.debug returns the value (can be chained)
42
|> int.multiply(2)
|> io.debug  // prints and passes through
|> int.to_string

Todo & Panic

// Mark unfinished code
pub fn coming_soon() {
  todo as "implement this feature"
}

// Crash with message (for truly impossible cases)
case impossible_value {
  _ -> panic as "this should never happen"
}

OTP & Concurrency

Gleam runs on the BEAM, giving you access to Erlang's legendary concurrency model. The gleam_otp library provides type-safe actors.

Quick Commands

# Create new project
gleam new myproject
cd myproject

# Build project
gleam build

# Run project
gleam run

# Run tests
gleam test

# Add dependencies
gleam add gleam_json
gleam add gleam_otp

# Format code
gleam format

# Type check
gleam check

# Generate docs
gleam docs build

Project Structure

myproject/
├── gleam.toml         # Project config
├── src/
│   └── myproject.gleam  # Main module
├── test/
│   └── myproject_test.gleam
└── build/             # Generated (gitignored)

gleam.toml

name = "myproject"
version = "1.0.0"
target = "erlang"  # or "javascript"

[dependencies]
gleam_stdlib = "~> 0.40"
gleam_otp = "~> 0.12"
gleam_json = "~> 2.0"

[dev-dependencies]
gleeunit = "~> 1.0"

Processes

import gleam/erlang/process.{type Subject// Spawn a process
let pid = process.start(fn() {
  io.println("Hello from process!")
}, True)

// Sleep
process.sleep(1000)  // milliseconds

Actors (Type-Safe Message Passing)

import gleam/otp/actor.{type Next}
import gleam/erlang/process.{type Subject}

// Define message type
pub type Message {
  Increment
  GetCount(reply_to: Subject(Int))
}

// Message handler
fn handle_message(msg: Message, count: Int) -> Next(Message, Int) {
  case msg {
    Increment -> actor.continue(count + 1)
    GetCount(client) -> {
      process.send(client, count)
      actor.continue(count)
    }
  }
}

// Start actor
pub fn start_counter() {
  let assert Ok(actor) = actor.start(0, handle_message)

  // Send messages
  process.send(actor, Increment)
  process.send(actor, Increment)

  // Request response
  let count = process.call(actor, GetCount, 1000)
  io.debug(count)  // 2
}
Gleam's actor system is fully type-checked at compile time. You cannot send the wrong message type to an actor!