Composable, lazy iterator adapters for Go 1.23+.
iterx fills the gap left by the stdlib: Go 1.23 introduced iter.Seq[V] as the standard iterator type, but provides no way to compose them — you cannot write .Filter().Map().Take() and there is no official adapter library (the x/exp/xiter proposal has been open since 2023). iterx is that library.
result := iterx.From(slices.Values(products)).
Filter(func(p Product) bool { return p.InStock }).
Take(10).
Collect()Zero dependencies · 100% test coverage · Works with stdlib for range
go get github.com/renaldid/iterxRequires Go 1.23 or later.
// Filter and collect
evens := iterx.Collect(iterx.Filter(iterx.Range(1, 11), func(n int) bool {
return n%2 == 0
}))
// [2 4 6 8 10]
// Map changes the element type
names := iterx.Collect(iterx.Map(slices.Values(users), func(u User) string {
return u.Name
}))
// Fluent chaining via Chain
top5 := iterx.From(slices.Values(scores)).
Filter(func(s int) bool { return s >= 60 }).
Drop(0).
Take(5).
Collect()
// Works with standard for range
for i, v := range iterx.Enumerate(slices.Values(items)) {
fmt.Printf("[%d] %v\n", i, v)
}
// Sum / Min / Max
total := iterx.Sum(iterx.Map(slices.Values(orders), func(o Order) float64 {
return o.Amount
}))
// Chunk into pages
for page := range iterx.Chunk(slices.Values(rows), 100) {
insertBatch(page)
}| Function | Signature | Description |
|---|---|---|
Filter |
(Seq[V], func(V) bool) → Seq[V] |
Keep elements where f is true |
Map |
(Seq[In], func(In) Out) → Seq[Out] |
Transform each element |
FlatMap |
(Seq[In], func(In) Seq[Out]) → Seq[Out] |
Map then flatten |
Flatten |
(Seq[Seq[V]]) → Seq[V] |
Merge nested iterators |
Peek |
(Seq[V], func(V)) → Seq[V] |
Side-effect per element; passes through unchanged |
Take |
(Seq[V], n) → Seq[V] |
First n elements |
Drop |
(Seq[V], n) → Seq[V] |
Skip first n elements |
TakeWhile |
(Seq[V], func(V) bool) → Seq[V] |
Take while predicate is true |
DropWhile |
(Seq[V], func(V) bool) → Seq[V] |
Drop while predicate is true |
StepBy |
(Seq[V], n) → Seq[V] |
Every n-th element (panics if n < 1) |
Chunk |
(Seq[V], n) → Seq[[]V] |
Non-overlapping slices of n elements |
Window |
(Seq[V], n) → Seq[[]V] |
Sliding windows of n elements |
Concat |
(Seq[V]...) → Seq[V] |
Concatenate multiple iterators |
Zip |
(Seq[A], Seq[B]) → Seq2[A, B] |
Pair elements; stops at shorter |
Enumerate |
(Seq[V]) → Seq2[int, V] |
Pair with zero-based index |
Unique |
(Seq[V]) → Seq[V] |
Remove duplicates (first occurrence kept) |
Sorted |
(Seq[V]) → Seq[V] |
Collect, sort ascending, re-iterate |
SortedBy |
(Seq[V], cmp) → Seq[V] |
Collect, sort with custom comparator |
| Function | Description |
|---|---|
Of(vals...) |
Iterator over literal values |
Empty[V]() |
Iterator that yields nothing |
Range(start, stop) |
Integers in [start, stop) |
RangeStep(start, stop, step) |
Integers with custom step (panics if step == 0) |
Repeat(v) |
Infinite repetition of v — use with Take |
RepeatN(v, n) |
Repeat v exactly n times |
Generate(seed, next) |
Infinite sequence — use with Take |
| Function | Description |
|---|---|
Collect(seq) |
→ []V |
First(seq) |
→ (V, bool) — first element |
Last(seq) |
→ (V, bool) — last element |
Nth(seq, n) |
→ (V, bool) — n-th element (0-based) |
Count(seq) |
→ int — number of elements |
Any(seq, f) |
→ bool — true if any element satisfies f (short-circuits) |
All(seq, f) |
→ bool — true if all elements satisfy f (short-circuits) |
None(seq, f) |
→ bool — true if no element satisfies f |
ForEach(seq, f) |
Call f on every element |
Reduce(seq, init, f) |
Left fold; accumulator type may differ from V |
GroupBy(seq, key) |
→ map[K][]V |
Partition(seq, f) |
→ (yes []V, no []V) |
| Function | Constraint | Description |
|---|---|---|
Sum[V] |
Number |
Sum of all elements |
Min[V] |
cmp.Ordered |
Smallest element |
Max[V] |
cmp.Ordered |
Largest element |
MinBy[V] |
any |
Smallest by custom comparator |
MaxBy[V] |
any |
Largest by custom comparator |
Number covers all built-in integer and float types (int, int8 … int64, uint … uintptr, float32, float64) including custom types built on them.
Functions that operate on iter.Seq2[K, V] (two-value iterators such as those produced by maps.All, Zip, or Enumerate):
| Function | Description |
|---|---|
Keys(seq2) |
Iterator over keys |
Values(seq2) |
Iterator over values |
CollectMap(seq2) |
→ map[K]V |
Filter2(seq2, f) |
Keep pairs where f(k, v) is true |
Map2(seq2, f) |
Transform each key-value pair |
Swap(seq2) |
Exchange key and value positions |
From(seq) wraps any iter.Seq[V] in a Chain[V], making same-type adapters available as methods:
iterx.From(slices.Values(nums)).
Filter(isPositive).
Drop(5).
Take(10).
Collect()Methods on Chain[V]: Filter, Take, Drop, TakeWhile, DropWhile, Peek, StepBy, Concat, Collect, First, Last, Count, Any, All, None, ForEach, Reduce, Seq.
Seq() unwraps the chain back to a plain iter.Seq[V], useful when handing off to a free function:
// Chain → free function → Chain again
iterx.From(
iterx.Map(
iterx.From(slices.Values(users)).Filter(isActive).Seq(),
func(u User) string { return u.Name },
),
).Take(10).Collect()
ChunkandWindoware not available asChainmethods because they change the element type to[]V, which causes an instantiation cycle in Go's generics system. Use them as free functions:iterx.From(iterx.Chunk(myChain.Seq(), 3))
Lazy by default. Every adapter returns a new iter.Seq without reading the source. Work only happens when you range over or collect the result.
Zero allocation on the hot path. Filter, Map, Take, Drop, and similar adapters create only a closure; no slices are allocated until you call Collect. Chunk, Window, Sorted, and Unique allocate internally because they must buffer elements.
Fail fast on invalid arguments. Chunk, Window, StepBy, and RangeStep panic immediately (before returning the iterator) on invalid input (n < 1 or step == 0). This surfaces programmer errors at the call site rather than silently during iteration.
Compatible with stdlib for range. Every iter.Seq[V] returned by this package works directly in Go 1.23 range loops:
for v := range iterx.Filter(slices.Values(items), pred) {
// ...
}No global state. All functions are pure. Multiple goroutines may safely call any function concurrently as long as they hold separate iterators — each call creates independent closure state.