Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Lists

A list is an ordered collection of values. Lists are generic, so their type tracks the kind of elements they hold, like List(Int) or List(String).

Internally, lists are immutable and singly-linked. This makes operations at the front fast and anything involving the end slower. For that reason, Gleam code usually builds lists from the front.

Use the gleam/list module for transformations like map, filter, and fold. This works the same on both BEAM and JavaScript targets.

List Literals

Use square brackets. An empty list is []. All elements must be the same type.

let empty_list = []
let letters = ["a", "b", "c"]

Prepend (Fast)

Adding to the front is efficient and doesn’t copy the list.

let tail = ["b", "c"]
let full = ["a", ..tail]  // ["a", "b", "c"]

The .. spread works only at the start of a list. It’s not a general-purpose concat operator.

Pattern Matching

You can match on lists using case, useful for handling different shapes.

case letters {
  [] -> "Empty"
  [x] -> "One: " <> x
  [first, ..rest] -> "First is " <> first
}

This helps cleanly separate the empty case, a single item, or a longer list.

Length, Emptiness, Membership

import gleam/list

list.length([1, 2, 3])           // 3
list.is_empty([])                // True
list.contains(["a", "b"], "b")   // True

These scan the list, so avoid using them in tight loops.

Head and Tail

list.first(["x", "y"])   // Ok("x")
list.rest(["x", "y"])    // Ok(["y"])

Both are fast and do not copy data.

Map, Filter, Fold

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

These replace manual recursion for most list processing.

Find, Any, All

list.find([1, 2, 3], fn(x) { x == 2 })   // Ok(2)
list.any([3, 5], fn(x) { x < 4 })        // True
list.all([3, 5], fn(x) { x > 2 })        // True

any and all stop as soon as possible.

Zip and Unzip

list.zip([1, 2], ["a", "b"])               // [#(1, "a"), #(2, "b")]
list.unzip([#(1, "x"), #(2, "y")])         // #([1, 2], ["x", "y"])

Use strict_zip if you want to fail on length mismatch.

Append, Flatten, Flat Map

list.append(["a"], ["b"])                  // ["a", "b"]
list.flatten([["a"], ["b", "c"]])          // ["a", "b", "c"]
list.flat_map(["x", "y"], fn(s) { [s, "!"] }) // ["x", "!", "y", "!"]

Appending copies the first list, so avoid chaining it repeatedly. Use a fold and reverse for better performance.

Take and Drop

list.take(["a", "b", "c"], 2)  // ["a", "b"]
list.drop(["a", "b", "c"], 1)  // ["b", "c"]

Both are safe and return smaller lists without error.

Unique and Sort

list.unique(["x", "x", "y"])             // ["x", "y"]
list.sort([3, 1, 2], by: int.compare)    // [1, 2, 3]

unique keeps the first of each value. sort needs a comparison function.

Windows, Chunks, Partition

list.window([1, 2, 3, 4], by: 2)        // [[1,2],[2,3],[3,4]]
list.sized_chunk([1, 2, 3, 4], into: 2) // [[1,2],[3,4]]
list.partition([1, 2, 3], int.is_even)  // #([2], [1, 3])

Use these to split or batch data.

Ranges and Repeats

list.range(1, 3)         // [1, 2, 3]
list.repeat("hi", 2)     // ["hi", "hi"]

Useful for generating test data or index sequences.

When Not to Use Lists

If you need random access, frequent edits in the middle, or lookups by key, lists aren’t the right tool. Try gleam/dict for key-value data or rewrite the algorithm to use folds.

list.at lets you read by index, but it’s O(n) and returns a result:

list.at(["a", "b"], 1) // Ok("b")

Tuples

Tuples hold a fixed number of values. Each element can be a different type. They’re good for short groups like a result pair or coordinates.

Tuple Literals

let status = #(404, "Not Found")
let config = #("dev", True, 3)

Each arity has a different type. A pair is not the same as a triple.

Access by Position

status.0   // 404
status.1   // "Not Found"
config.2   // 3

Accessors use zero-based indexing and don’t require pattern matching.

Pattern Matching

case config {
  #(env, active, tries) -> env <> ": " <> int.to_string(tries)
}

Use matching when you want to name all fields. If the tuple has meaning in your domain, consider using a record or custom type instead.