Iterable
The Iterable module operates on any value that implements [Symbol.iterator] —
arrays, strings, Sets, Maps, generators, and your own custom lazy sequences.
Unlike Array, its transformations are lazy: map, filter, take, drop,
and friends return new iterables that do no work until something pulls values out of
them. This lets you describe pipelines over unbounded sequences and only pay for the
slice you actually materialize.
import { Array, Iterable } from "effect"
// An infinite sequence of naturals — nothing runs yet.const naturals = Iterable.range(1)
// Compose lazily: still nothing runs.const evenSquares = Iterable.filter( Iterable.map(naturals, (n) => n * n), (n) => n % 2 === 0)
// Bound it, then materialize. Only 4 squares are ever computed.const firstFour = Iterable.take(evenSquares, 4)console.log(Array.fromIterable(firstFour))// => [4, 16, 36, 64]Mental model: producers, transformers, consumers
Section titled “Mental model: producers, transformers, consumers”The whole module falls into three buckets, and knowing which is which tells you when work happens:
- Producers create iterables, possibly infinite:
range,makeBy,repeat,forever,unfold,replicate,of,empty. - Transformers take an iterable and return a new lazy iterable:
map,filter,flatMap,take,drop,scan,zip,intersperse,chunksOf,group, … No work happens until the result is iterated. - Consumers pull values and produce a final answer, doing the work immediately:
reduce,size,head,findFirst,some,countBy,forEach,contains,groupBy, andArray.fromIterable/Array.from.
import { Array, Iterable } from "effect"
// `map` is a transformer: this logs nothing.const mapped = Iterable.map([1, 2, 3], (n) => { console.log("mapping", n) return n * 10})
// `Array.fromIterable` is a consumer: now the logs fire and we get a result.console.log(Array.fromIterable(mapped))// => mapping 1// => mapping 2// => mapping 3// => [10, 20, 30]Iterable vs Array: when to reach for which
Section titled “Iterable vs Array: when to reach for which”Both modules share most of the same operation names, but they make opposite tradeoffs:
Iterable | Array | |
|---|---|---|
| Evaluation | Lazy — intermediate steps don’t allocate | Eager — each step allocates a new array |
| Indexing / length | No random access; size walks the whole thing | O(1) length, index access, slicing |
| Infinite sequences | Supported (range, forever, unfold) | Not possible |
| Best for | Streaming, fused pipelines, “first N matching” | Random access, repeated reads, small finite data |
Reach for Iterable when you are chaining several transformations, when the source may
be unbounded, or when you only need a prefix of a large computation. Reach for Array
when you need indexing, length, or a concrete materialized collection. Convert from one
to the other with Array.fromIterable.
import { Array, Iterable } from "effect"
// Lazy: only enough candidates are tested to find the first 3 primes above 100.const isPrime = (n: number) => n > 1 && !Iterable.some(Iterable.range(2, Math.floor(Math.sqrt(n))), (d) => n % d === 0)
const bigPrimes = Iterable.filter(Iterable.range(101), isPrime)console.log(Array.fromIterable(Iterable.take(bigPrimes, 3)))// => [101, 103, 107]NonEmptyIterable: a compile-time non-empty guarantee
Section titled “NonEmptyIterable: a compile-time non-empty guarantee”NonEmptyIterable<A> is an Iterable<A> branded with a type-level proof that it
contains at least one element. It is accepted anywhere an Iterable<A> is, but lets
callers safely read the head without an Option. Use it for APIs (reductions,
aggregations, comparisons) that are undefined for empty input.
import { NonEmptyIterable } from "effect"
function firstOf<A>(data: NonEmptyIterable.NonEmptyIterable<A>): A { // `unprepend` is safe here because the type guarantees an element exists. const [first] = NonEmptyIterable.unprepend(data) return first}
// A generator typed as NonEmptyIterable satisfies the brand without a cast.function* oneToThree(): NonEmptyIterable.NonEmptyIterable<number> { yield 1 yield 2 yield 3}
console.log(firstOf(oneToThree()))// => 1Reference
Section titled “Reference”Every example materializes lazy results with Array.fromIterable so the // =>
comments show concrete values. Functions are grouped by role; remember the
producer / transformer / consumer distinction from above.
Constructors and infinite sequences
Section titled “Constructors and infinite sequences”makeBy
Section titled “makeBy”Builds an iterable by applying a function to consecutive indices starting at 0. Omit
length for an infinite sequence.
import { Array, Iterable } from "effect"
console.log(Array.fromIterable(Iterable.makeBy((n) => n * 2, { length: 4 })))// => [0, 2, 4, 6]Integers from start, increasing by 1. With end (inclusive) it is finite; without
end it is infinite.
import { Array, Iterable } from "effect"
console.log(Array.fromIterable(Iterable.range(1, 4)))// => [1, 2, 3, 4]console.log(Array.fromIterable(Iterable.take(Iterable.range(1), 3)))// => [1, 2, 3]replicate
Section titled “replicate”Repeats a single value n times (n is clamped to at least 1).
import { Array, Iterable } from "effect"
console.log(Array.fromIterable(Iterable.replicate("a", 3)))// => ["a", "a", "a"]repeat
Section titled “repeat”Repeats the entire contents of an iterable n times. Lazy — a fresh iterator is taken
from the source for each pass, so the source must be re-iterable.
import { Array, Iterable } from "effect"
console.log(Array.fromIterable(Iterable.repeat([1, 2], 3)))// => [1, 2, 1, 2, 1, 2]forever
Section titled “forever”Repeats an iterable without bound. Always pair it with a terminating consumer such as
take.
import { Array, Iterable } from "effect"
console.log(Array.fromIterable(Iterable.take(Iterable.forever([1, 2]), 5)))// => [1, 2, 1, 2, 1]fromRecord
Section titled “fromRecord”Turns a record into an iterable of [key, value] entry tuples.
import { Array, Iterable } from "effect"
console.log(Array.fromIterable(Iterable.fromRecord({ a: 1, b: 2 })))// => [["a", 1], ["b", 2]]An iterable that yields nothing. Useful as a base case or as the “skip” result of a
flatMap.
import { Array, Iterable } from "effect"
console.log(Array.fromIterable(Iterable.empty<number>()))// => []Wraps a single value in an iterable.
import { Array, Iterable } from "effect"
console.log(Array.fromIterable(Iterable.of(42)))// => [42]unfold
Section titled “unfold”Generates values from a seed: f returns Option.some([value, nextSeed]) to continue
or Option.none() to stop. The dual of reduce.
import { Array, Iterable, Option } from "effect"
const countdown = Iterable.unfold(3, (n) => n > 0 ? Option.some([n, n - 1] as const) : Option.none())console.log(Array.fromIterable(countdown))// => [3, 2, 1]Concatenation
Section titled “Concatenation”prepend
Section titled “prepend”Yields one element before the existing elements.
import { Array, Iterable } from "effect"
console.log(Array.fromIterable(Iterable.prepend([2, 3], 1)))// => [1, 2, 3]prependAll
Section titled “prependAll”Yields all elements of another iterable before self.
import { Array, Iterable } from "effect"
console.log(Array.fromIterable(Iterable.prependAll([1, 2], ["a", "b"])))// => ["a", "b", 1, 2]append
Section titled “append”Yields self, then one trailing element. If self never completes, the element is
never reached.
import { Array, Iterable } from "effect"
console.log(Array.fromIterable(Iterable.append([1, 2, 3], 4)))// => [1, 2, 3, 4]appendAll
Section titled “appendAll”Concatenates two iterables lazily; that is not touched until self is exhausted.
import { Array, Iterable } from "effect"
console.log(Array.fromIterable(Iterable.appendAll([1, 2], [3, 4])))// => [1, 2, 3, 4]Slicing
Section titled “Slicing”Keeps at most the first n elements. The canonical way to bound an infinite iterable.
import { Array, Iterable } from "effect"
console.log(Array.fromIterable(Iterable.take(Iterable.range(1), 3)))// => [1, 2, 3]takeWhile
Section titled “takeWhile”Keeps the longest leading run for which the predicate holds, then stops. Supports type refinements.
import { Array, Iterable } from "effect"
console.log(Array.fromIterable(Iterable.takeWhile([2, 4, 6, 3, 8], (n) => n % 2 === 0)))// => [2, 4, 6]Skips the first n elements and yields the rest. Combine with take to slice.
import { Array, Iterable } from "effect"
console.log(Array.fromIterable(Iterable.take(Iterable.drop([1, 2, 3, 4, 5], 1), 3)))// => [2, 3, 4]chunksOf
Section titled “chunksOf”Splits into arrays of length n; the final chunk may be shorter.
import { Array, Iterable } from "effect"
console.log(Array.fromIterable(Iterable.chunksOf([1, 2, 3, 4, 5], 2)))// => [[1, 2], [3, 4], [5]]Accessing and size (consumers)
Section titled “Accessing and size (consumers)”isEmpty
Section titled “isEmpty”Checks whether the iterable yields no elements by pulling a single value. Acts as a type
guard narrowing to Iterable<never>.
import { Iterable } from "effect"
console.log(Iterable.isEmpty([]))// => trueconsole.log(Iterable.isEmpty([1]))// => falseCounts elements by walking the entire iterable. Never call on an unbounded source.
import { Iterable } from "effect"
console.log(Iterable.size([1, 2, 3, 4, 5]))// => 5Returns the first element as an Option, None if empty.
import { Iterable } from "effect"
console.log(Iterable.head([1, 2, 3]))// => { _id: 'Option', _tag: 'Some', value: 1 }console.log(Iterable.head([]))// => { _id: 'Option', _tag: 'None' }headUnsafe
Section titled “headUnsafe”Returns the first element directly, throwing on an empty iterable. Use only when you know the iterable is non-empty.
import { Iterable } from "effect"
console.log(Iterable.headUnsafe([1, 2, 3]))// => 1// Iterable.headUnsafe([]) // throws Error: "headUnsafe: empty iterable"Searching (consumers)
Section titled “Searching (consumers)”findFirst
Section titled “findFirst”Returns the first matching element as an Option. Accepts a boolean predicate, a type
refinement, or an Option-returning function (which also transforms the result).
import { Iterable } from "effect"
console.log(Iterable.findFirst([1, 3, 4, 6], (n) => n % 2 === 0))// => { _id: 'Option', _tag: 'Some', value: 4 }findLast
Section titled “findLast”Like findFirst but returns the last match. Always walks the whole iterable.
import { Iterable } from "effect"
console.log(Iterable.findLast([1, 4, 6, 8, 2], (n) => n % 2 === 0))// => { _id: 'Option', _tag: 'Some', value: 2 }contains
Section titled “contains”Tests membership using Effect’s default Equal equivalence (so it works with
value-equal data types, not just primitives).
import { Iterable } from "effect"
console.log(Iterable.contains([1, 2, 3], 2))// => trueconsole.log(Iterable.contains([1, 2, 3], 9))// => falsecontainsWith
Section titled “containsWith”Builds a contains that uses a custom equivalence function.
import { Iterable } from "effect"
const containsCi = Iterable.containsWith<string>( (a, b) => a.toLowerCase() === b.toLowerCase())console.log(containsCi(["Hello", "World"], "hello"))// => trueMapping and folding
Section titled “Mapping and folding”Transforms each element. The callback receives the index. Lazy.
import { Array, Iterable } from "effect"
console.log(Array.fromIterable(Iterable.map([1, 2, 3], (n) => n * n)))// => [1, 4, 9]flatMap
Section titled “flatMap”Maps each element to an iterable, then concatenates the results. Lazy.
import { Array, Iterable } from "effect"
console.log(Array.fromIterable(Iterable.flatMap([1, 2, 3], (n) => Iterable.range(1, n))))// => [1, 1, 2, 1, 2, 3]flatten
Section titled “flatten”Concatenates an iterable of iterables, one level deep. Lazy.
import { Array, Iterable } from "effect"
console.log(Array.fromIterable(Iterable.flatten([[1, 2], [3, 4], [5]])))// => [1, 2, 3, 4, 5]filterMap
Section titled “filterMap”Maps and filters in one pass using a Result: Result.succeed(b) keeps b,
Result.failVoid drops the element. Lazy.
import { Array, Iterable, Result } from "effect"
const parsed = Iterable.filterMap(["1", "x", "3"], (s) => { const n = Number(s) return Number.isNaN(n) ? Result.failVoid : Result.succeed(n)})console.log(Array.fromIterable(parsed))// => [1, 3]filterMapWhile
Section titled “filterMapWhile”Like filterMap, but stops at the first Result.failVoid instead of skipping it.
import { Array, Iterable, Result } from "effect"
const upToInvalid = Iterable.filterMapWhile(["1", "2", "x", "4"], (s) => { const n = Number(s) return Number.isNaN(n) ? Result.failVoid : Result.succeed(n)})console.log(Array.fromIterable(upToInvalid))// => [1, 2]filter
Section titled “filter”Keeps elements matching a predicate; supports type refinements. Lazy.
import { Array, Iterable } from "effect"
console.log(Array.fromIterable(Iterable.filter([1, 2, 3, 4], (n) => n % 2 === 0)))// => [2, 4]forEach
Section titled “forEach”Runs a side-effecting callback for each element. A consumer; returns void.
import { Iterable } from "effect"
Iterable.forEach(["a", "b"], (x, i) => console.log(i, x))// => 0 a// => 1 breduce
Section titled “reduce”Folds the iterable left-to-right into a single value. A consumer.
import { Iterable } from "effect"
console.log(Iterable.reduce([1, 2, 3, 4], 0, (acc, n) => acc + n))// => 10Like reduce, but yields every intermediate accumulator (starting with the seed). Lazy.
import { Array, Iterable } from "effect"
console.log(Array.fromIterable(Iterable.scan([1, 2, 3], 0, (acc, n) => acc + n)))// => [0, 1, 3, 6]Returns true if any element matches the predicate. Short-circuits on the first match.
import { Iterable } from "effect"
console.log(Iterable.some([1, 3, 4], (n) => n % 2 === 0))// => truecountBy
Section titled “countBy”Counts how many elements satisfy the predicate. A consumer.
import { Iterable } from "effect"
console.log(Iterable.countBy([1, 2, 3, 4, 5], (n) => n % 2 === 0))// => 2Option and Result interop
Section titled “Option and Result interop”getSomes
Section titled “getSomes”Keeps only the Some values from an iterable of Options, unwrapping them. Lazy.
import { Array, Iterable, Option } from "effect"
console.log( Array.fromIterable( Iterable.getSomes([Option.some(1), Option.none(), Option.some(2)]) ))// => [1, 2]getFailures
Section titled “getFailures”Keeps only the failure values from an iterable of Results. Lazy.
import { Array, Iterable, Result } from "effect"
console.log( Array.fromIterable( Iterable.getFailures([Result.succeed(1), Result.fail("err"), Result.succeed(2)]) ))// => ["err"]getSuccesses
Section titled “getSuccesses”Keeps only the success values from an iterable of Results. Lazy.
import { Array, Iterable, Result } from "effect"
console.log( Array.fromIterable( Iterable.getSuccesses([Result.succeed(1), Result.fail("err"), Result.succeed(2)]) ))// => [1, 2]flatMapNullishOr
Section titled “flatMapNullishOr”Maps with a function that may return null / undefined, dropping those results.
Handy with APIs like Map.get. Lazy.
import { Array, Iterable } from "effect"
const lookup = new Map([["a", 1], ["b", 2]])const found = Iterable.flatMapNullishOr(["a", "x", "b"], (k) => lookup.get(k))console.log(Array.fromIterable(found))// => [1, 2]Zipping and pairing
Section titled “Zipping and pairing”Pairs corresponding elements of two iterables; stops at the shorter one. Lazy.
import { Array, Iterable } from "effect"
console.log(Array.fromIterable(Iterable.zip([1, 2, 3], ["a", "b"])))// => [[1, "a"], [2, "b"]]zipWith
Section titled “zipWith”Like zip, but combines each pair with a function instead of tupling.
import { Array, Iterable } from "effect"
console.log(Array.fromIterable(Iterable.zipWith([1, 2, 3], [10, 20, 30], (a, b) => a + b)))// => [11, 22, 33]cartesian
Section titled “cartesian”Every pair [a, b] across the two iterables (the cross product). Lazy.
import { Array, Iterable } from "effect"
console.log(Array.fromIterable(Iterable.cartesian([1, 2], ["a", "b"])))// => [[1, "a"], [1, "b"], [2, "a"], [2, "b"]]cartesianWith
Section titled “cartesianWith”Like cartesian, but combines each cross-product pair with a function.
import { Array, Iterable } from "effect"
console.log(Array.fromIterable(Iterable.cartesianWith([1, 2], [10, 20], (a, b) => a * b)))// => [10, 20, 20, 40]intersperse
Section titled “intersperse”Inserts a separator between adjacent elements (none before the first or after the last). Lazy.
import { Array, Iterable } from "effect"
console.log(Array.fromIterable(Iterable.intersperse([1, 2, 3], 0)))// => [1, 0, 2, 0, 3]Grouping and dedupe
Section titled “Grouping and dedupe”Groups runs of consecutive equal elements (default Equal equivalence) into
non-empty arrays. Lazy.
import { Array, Iterable } from "effect"
console.log(Array.fromIterable(Iterable.group([1, 1, 2, 3, 3])))// => [[1, 1], [2], [3, 3]]groupWith
Section titled “groupWith”Like group, but with a custom equivalence function.
import { Array, Iterable } from "effect"
const grouped = Iterable.groupWith( ["a", "A", "b"], (x, y) => x.toLowerCase() === y.toLowerCase())console.log(Array.fromIterable(grouped))// => [["a", "A"], ["b"]]groupBy
Section titled “groupBy”Groups all elements (not just consecutive ones) into a record keyed by f. A
consumer — it walks the whole iterable.
import { Iterable } from "effect"
console.log(Iterable.groupBy([1, 2, 3, 4], (n) => (n % 2 === 0 ? "even" : "odd")))// => { odd: [1, 3], even: [2, 4] }dedupeAdjacent
Section titled “dedupeAdjacent”Collapses runs of adjacent equal elements (default Equal equivalence) to a single
element. Lazy.
import { Array, Iterable } from "effect"
console.log(Array.fromIterable(Iterable.dedupeAdjacent([1, 1, 2, 2, 3, 1])))// => [1, 2, 3, 1]dedupeAdjacentWith
Section titled “dedupeAdjacentWith”Like dedupeAdjacent, but with a custom equivalence function.
import { Array, Iterable } from "effect"
const deduped = Iterable.dedupeAdjacentWith( ["Hello", "HELLO", "world"], (a, b) => a.toLowerCase() === b.toLowerCase())console.log(Array.fromIterable(deduped))// => ["Hello", "world"]The NonEmptyIterable module
Section titled “The NonEmptyIterable module”A small module providing the NonEmptyIterable<A> type and the one operation that
exploits its guarantee.
NonEmptyIterable (interface)
Section titled “NonEmptyIterable (interface)”An Iterable<A> carrying a type-level brand (readonly [nonEmpty]: A) that proves at
least one element exists. It is structurally an Iterable, so it can be passed
anywhere one is expected; the brand only matters at compile time.
import { Array, NonEmptyIterable } from "effect"
// A plain iterable known to be non-empty is asserted into the branded type.const ne = Array.make(1, 2, 3) as unknown as NonEmptyIterable.NonEmptyIterable<number>unprepend
Section titled “unprepend”Splits a non-empty iterable into its first element and an Iterator over the rest.
Safe because the type guarantees a head. Note the second element is a raw iterator
(stateful, already advanced past the head).
import { Array, NonEmptyIterable } from "effect"
const ne = Array.make(1, 2, 3) as unknown as NonEmptyIterable.NonEmptyIterable<number>const [first, rest] = NonEmptyIterable.unprepend(ne)console.log(first)// => 1
// Wrap the iterator to consume the remaining elements.console.log(Array.fromIterable({ [Symbol.iterator]: () => rest }))// => [2, 3]