Skip to content
Comprehensions: for
step 1/7

Reading — step 1 of 7

Learn

~3 min readAtoms, Anonymous Functions, Comprehensions

Elixir's for is a comprehension — like Python's list comprehension or Haskell's, but with Elixir's pattern matching. Programming Elixir treats it as a powerful generator/filter/transformer.

Basic syntax

for n <- [1, 2, 3, 4, 5], do: n * 2
# [2, 4, 6, 8, 10]

Reads: "for each n in [1..5], yield n * 2."

With a filter (guard)

for n <- 1..10, rem(n, 2) == 0, do: n * n
# [4, 16, 36, 64, 100]

The second clause is a filter — only items where the expression is truthy continue.

Multiple generators

for x <- [1, 2, 3], y <- [:a, :b], do: {x, y}
# [{1, :a}, {1, :b}, {2, :a}, {2, :b}, {3, :a}, {3, :b}]

Multiple generators give a CARTESIAN PRODUCT — like nested loops.

With a filter constraining the cross:

for x <- 1..3, y <- 1..3, x != y, do: {x, y}
# [{1, 2}, {1, 3}, {2, 1}, {2, 3}, {3, 1}, {3, 2}]

Pattern matching in generators

users = [{1, "Ada"}, {2, "Bob"}, {3, "Carol"}]
for {id, name} <- users, do: "#{id}: #{name}"
# ["1: Ada", "2: Bob", "3: Carol"]

Destructure each element. If the pattern doesn't match, the item is SKIPPED (not an error). Useful for filtering by shape:

mixed = [{:ok, 1}, {:error, "bad"}, {:ok, 2}, {:ok, 3}]
for {:ok, value} <- mixed, do: value * 10
# [10, 20, 30]   — :error tuples skipped

The pattern {:ok, value} matches only success tuples. Errors silently dropped.

:into — produce other collections

By default for produces a list. Use :into for maps, MapSets, etc:

for {k, v} <- [{:a, 1}, {:b, 2}], into: %{}, do: {k, v * 10}
# %{a: 10, b: 20}

for n <- [1, 1, 2, 3, 3], into: MapSet.new(), do: n
# MapSet.new([1, 2, 3])

The :into accumulator collects results. Any Collectable works.

:reduce — accumulator-based comprehensions

for n <- 1..5, reduce: 0 do
    acc -> acc + n
end
# 15 — sum

Like Enum.reduce in comprehension form. Pattern-match the accumulator across iterations:

for n <- 1..10, reduce: %{evens: [], odds: []} do
    %{evens: e, odds: o} when rem(n, 2) == 0 ->
        %{evens: [n | e], odds: o}
    %{evens: e, odds: o} ->
        %{evens: e, odds: [n | o]}
end

Reduce with branching — useful for grouping logic.

Bitstring comprehensions

Process binary data:

pixels = <<213, 45, 132, 64, 76, 32, 76, 0, 0, 234, 32, 15>>
for <<r::8, g::8, b::8 <- pixels>>, do: {r, g, b}
# [{213, 45, 132}, {64, 76, 32}, {76, 0, 0}, {234, 32, 15}]

Destructure 3 bytes at a time. Useful for binary protocols, image data.

When use comprehension vs Enum / pipeline?

  • Comprehension — multiple generators / Cartesian products, :into for non-list output, complex :reduce patterns
  • Pipeline (|>) — transformations on a single collection, deeply composable
  • Enum functions — when the right named function exists

Pipelines tend to read better for long sequences of single transformations. Comprehensions shine when you need multiple sources or shape-filtering.

Common mistakes

  • Forgetting do: on one-linersfor n <- list, n * 2 is a syntax error. for n <- list, do: n * 2 works.
  • Multi-line without do/end — for multi-clause :reduce, use the block form: for ... reduce: 0 do ... end.
  • Expecting filtering to error on bad shapes — pattern non-matches SKIP, not error. Surprising if you didn't intend to filter.
  • Comprehension when pipeline is clearerfor n <- list, do: f(n) is just Enum.map(list, &f/1). Use the simpler form.
  • :into with mutable expectation — Elixir is immutable. :into produces a new collection; original is unchanged.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…