Skip to content
Enum vs Stream
step 1/5

Reading — step 1 of 5

Learn

~1 min readPattern Matching and Pipelines

Enum is eager. Stream is lazy. Same operations, very different performance characteristics on big data.

Enum — materializes intermediate lists:

1..1_000_000
|> Enum.map(&(&1 * 2))            # builds full list of 1M doubled
|> Enum.filter(&rem(&1, 3) == 0)  # builds list of those % 3 == 0
|> Enum.take(5)                    # finally takes 5

Every Enum.something/2 allocates a new list. For 1M items, that's 1M doubled, then filtered to ~330K, then sliced to 5. Wasteful.

Stream — lazy:

1..1_000_000
|> Stream.map(&(&1 * 2))
|> Stream.filter(&rem(&1, 3) == 0)
|> Enum.take(5)                    # `Enum.take` is the consumer that triggers evaluation

Nothing runs until Enum.take (or Enum.to_list, Enum.reduce, etc.). The pipeline only computes 5 results — short-circuits as soon as 5 are accumulated.

Most-used Enum functions:

Enum.map([1,2,3], &(&1 * 2))                 # [2, 4, 6]
Enum.filter([1,2,3,4], &rem(&1, 2) == 0)     # [2, 4]
Enum.reduce([1,2,3], 0, &+/2)                # 6
Enum.sort([3,1,2])                            # [1, 2, 3]
Enum.sort_by(users, & &1.age)
Enum.group_by([1,2,3,4], &rem(&1, 2))        # %{0 => [2,4], 1 => [1,3]}
Enum.chunk_every([1,2,3,4,5], 2)             # [[1,2], [3,4], [5]]
Enum.zip([1,2,3], ["a","b","c"])              # [{1,"a"}, {2,"b"}, {3,"c"}]
Enum.with_index(["a","b","c"])               # [{"a",0}, {"b",1}, {"c",2}]

For most everyday code, Enum is fine — lists are short. Reach for Stream when:

  • Working with big collections
  • Building infinite sequences
  • Reading line-by-line from a file: File.stream!("big.log")

Discussion

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

Sign in to post a comment or reply.

Loading…