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.