Custom Iterators & Enumerator
A custom iterator is a class that defines an each method and includes Enumerable , instantly gaining map, select, and dozens of other collection methods for free.
Learn Custom Iterators & Enumerator in our free Ruby course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick…
Part of the free Ruby course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
In this lesson you'll make your own objects iterable, return chainable Enumerator objects, and tame infinite sequences with lazy evaluation.
What You'll Learn in This Lesson
1️⃣ each + Enumerable = Free Superpowers
The deal Ruby offers is generous: write a single each method that yield s every element, include Enumerable , and your class gains map , select , count , include? , min_by , sort , and roughly fifty more — all implemented in terms of each .
2️⃣ Returning an Enumerator (No Block)
Ruby's own collections do something clever: when you call each with no block, they return an Enumerator instead of looping. That object can be chained ( .with_index , .lazy ) or stepped through manually with .next . Add return to_enum(:each) unless block_given? to give your class the same power.
3️⃣ Lazy Enumerators, each_with_object & with_index
Chaining map and select on an infinite range would hang forever — unless you call .lazy first, which pulls values one at a time only as far as you actually consume. Meanwhile each_with_object threads a result object through the loop, and with_index numbers items from any starting point.
🎯 Your Turn
One keyword is missing. Replace the ___ so each item is handed to the block, making the whole Enumerable toolkit light up.
Build an infinite Fibonacci sequence with Enumerator.new , then use first and lazy to pull slices out of it. Run with ruby fib.rb .
📋 Quick Reference — Iterators & Enumerator
Practice quiz
Which single method must you define to make a class iterable?
- map
- next
- each
- iterate
Answer: each. Define each (which yields elements) and the rest can build on it.
What does including Enumerable give a class that defines each?
- map, select, count and dozens more
- nothing extra
- only count
- automatic threading
Answer: map, select, count and dozens more. Enumerable supplies 50+ collection methods built on top of each.
Inside each, which keyword hands an element to the caller's block?
- return
- give
- pass
- yield
Answer: yield. yield passes each value to the block supplied at the call site.
When you call each with NO block, what should an idiomatic method return?
- nil
- an Enumerator
- an empty array
- false
Answer: an Enumerator. Returning an Enumerator (via to_enum) makes the method chainable.
What is the class of [1, 2, 3].each when called without a block?
- Enumerator
- Array
- NilClass
- Proc
Answer: Enumerator. A block-less each returns an Enumerator object.
Why call .lazy before chaining map/select on (1..Float::INFINITY)?
- to sort it
- to make it faster to print
- so values are pulled on demand instead of building an infinite array
- it is required syntax
Answer: so values are pulled on demand instead of building an infinite array. Lazy enumerators evaluate one element at a time, only as far as consumed.
What does [1,2,3].each_with_object(Hash.new(0)) do?
- returns a sorted array
- threads one mutable object through the loop and returns it
- raises an error
- creates three hashes
Answer: threads one mutable object through the loop and returns it. each_with_object carries a result object through iteration and returns it.
What does %w[gold silver].each.with_index(1) start counting from?
- 0
- the array length
- -1
- 1
Answer: 1. with_index(1) numbers items starting at 1 instead of 0.
How do you pull the next value from an Enumerator one at a time?
- .pop
- .next
- .shift
- .take
Answer: .next. External iteration uses .next, which remembers where it left off.
Calling .next past the last element of an Enumerator raises:
- NoMethodError
- ArgumentError
- StopIteration
- nothing
Answer: StopIteration. Exhausting an Enumerator with .next raises StopIteration.