Lazy enumerators delay processing elements until needed, allowing efficient chaining and partial evaluation.
Execution Sample
Ruby
numbers = (1..Float::INFINITY).lazy
result = numbers.select { |x| x % 2 == 0 }.map { |x| x * 10 }.first(5)
This code creates an infinite lazy enumerator of numbers, filters even numbers, multiplies them by 10, and takes the first 5 results.
Execution Table
Step
Operation
Current Element
Condition/Action
Output/Result
1
Start lazy enumerator
1
No output yet
Enumerator created
2
Select even numbers
1
1 % 2 == 0? No
Skip
3
Select even numbers
2
2 % 2 == 0? Yes
Pass
4
Map multiply by 10
2
2 * 10
20
5
Collect first element
20
Add to result
[20]
6
Select even numbers
3
3 % 2 == 0? No
Skip
7
Select even numbers
4
4 % 2 == 0? Yes
Pass
8
Map multiply by 10
4
4 * 10
40
9
Collect second element
40
Add to result
[20, 40]
10
Select even numbers
5
5 % 2 == 0? No
Skip
11
Select even numbers
6
6 % 2 == 0? Yes
Pass
12
Map multiply by 10
6
6 * 10
60
13
Collect third element
60
Add to result
[20, 40, 60]
14
Select even numbers
7
7 % 2 == 0? No
Skip
15
Select even numbers
8
8 % 2 == 0? Yes
Pass
16
Map multiply by 10
8
8 * 10
80
17
Collect fourth element
80
Add to result
[20, 40, 60, 80]
18
Select even numbers
9
9 % 2 == 0? No
Skip
19
Select even numbers
10
10 % 2 == 0? Yes
Pass
20
Map multiply by 10
10
10 * 10
100
21
Collect fifth element
100
Add to result
[20, 40, 60, 80, 100]
22
Stop
N/A
Collected 5 elements
Result ready
💡 Collected 5 elements, so evaluation stops early without processing infinite elements.
Variable Tracker
Variable
Start
After Step 4
After Step 9
After Step 13
After Step 17
After Step 21
Final
current_element
1
2
4
6
8
10
10
result
[]
[20]
[20, 40]
[20, 40, 60]
[20, 40, 60, 80]
[20, 40, 60, 80, 100]
[20, 40, 60, 80, 100]
Key Moments - 3 Insights
Why doesn't the code try to process all infinite numbers?
Because the lazy enumerator only processes elements as needed. The 'first(5)' call stops after collecting 5 results, so it never evaluates the infinite rest. See execution_table rows 21-22.
Why do some numbers get skipped in the process?
Numbers that don't meet the 'select' condition (even numbers) are skipped immediately without mapping. For example, 1 is skipped at step 2. This saves work and is shown in execution_table rows 2, 6, 10, 14, 18.
When does the 'map' operation happen?
The 'map' runs only on numbers that passed the 'select' filter. For example, 2 passes select and then is multiplied by 10 at step 4. This chaining is lazy and happens element by element.
Visual Quiz - 3 Questions
Test your understanding
Look at the execution_table, what is the output after step 9?
A[20, 40]
B[20]
C[20, 40, 60]
D[]
💡 Hint
Check the 'Output/Result' column at step 9 in the execution_table.
At which step does the lazy enumerator stop processing elements?
AStep 10
BStep 22
CStep 21
DStep 5
💡 Hint
Look for the step where the exit note says 'Result ready' in the execution_table.
If we change 'first(5)' to 'first(3)', what would be the output after step 9?
A[20, 40]
B[20]
C[20, 40, 60]
D[]
💡 Hint
Refer to variable_tracker and execution_table to see how many elements are collected by step 9.
Concept Snapshot
Lazy enumerators delay processing until needed.
Use 'lazy' on enumerables to chain operations efficiently.
Operations like 'map' and 'select' are chained but not run immediately.
Trigger evaluation with methods like 'first', 'take', or 'to_a'.
Useful for infinite or large data to avoid unnecessary work.
Full Transcript
This visual trace shows how Ruby's lazy enumerators work step-by-step. We start with an infinite range and call 'lazy' to create a lazy enumerator. Then we chain 'select' to filter even numbers and 'map' to multiply by 10. The key is that no elements are processed until we call 'first(5)', which triggers evaluation. The enumerator processes elements one by one, skipping odd numbers and transforming even numbers, collecting results until it has 5 elements. It stops early without processing the infinite rest. Variables like 'current_element' and 'result' update as we go. This approach saves time and memory by doing only what is needed.
Practice
(1/5)
1. What is the main benefit of using .lazy enumerators in Ruby?
easy
A. They sort elements in ascending order by default.
B. They immediately compute all elements for faster access.
C. They convert arrays into hashes automatically.
D. They delay computation until results are needed, saving memory and time.
Solution
Step 1: Understand what lazy enumerators do
Lazy enumerators delay the processing of elements until you actually ask for them, which helps save memory and time especially with large or infinite collections.
Step 2: Compare options with this behavior
Only They delay computation until results are needed, saving memory and time. describes this delaying behavior. Other options describe immediate computation, conversion, or sorting, which are not the main features of lazy enumerators.
Final Answer:
They delay computation until results are needed, saving memory and time. -> Option D
Quick Check:
Lazy enumerators delay work = A [OK]
Hint: Lazy means delay work until needed, not immediate [OK]
Common Mistakes:
Thinking lazy enumerators compute all elements immediately
Confusing lazy enumerators with sorting or conversion methods
Assuming lazy enumerators change data structure type
2. Which of the following is the correct way to create a lazy enumerator from an array [1, 2, 3] in Ruby?
easy
A. [1, 2, 3].to_lazy
B. lazy([1, 2, 3])
C. [1, 2, 3].lazy
D. Array.lazy([1, 2, 3])
Solution
Step 1: Recall the syntax for creating lazy enumerators
In Ruby, calling .lazy on an enumerable creates a lazy enumerator.
Step 2: Check each option's syntax
Only [1, 2, 3].lazy is valid Ruby syntax. The others are not valid methods or functions.
Final Answer:
[1, 2, 3].lazy -> Option C
Quick Check:
Use .lazy on enumerable = A [OK]
Hint: Use .lazy method on enumerable to create lazy enumerator [OK]
Common Mistakes:
Trying to call non-existent methods like to_lazy
Using lazy as a standalone function
Confusing class methods with instance methods
3. What is the output of the following Ruby code?
result = (1..Float::INFINITY).lazy.select { |x| x % 3 == 0 }.map { |x| x * 2 }.first(5)
puts result.inspect
medium
A. [3, 6, 9, 12, 15]
B. [6, 12, 18, 24, 30]
C. [2, 4, 6, 8, 10]
D. RuntimeError: infinite loop
Solution
Step 1: Understand the lazy chain
The code creates an infinite range from 1 upwards, then lazily selects numbers divisible by 3, then doubles them, and finally takes the first 5 results.
Step 2: Calculate the first 5 values
Numbers divisible by 3 starting from 1 are 3, 6, 9, 12, 15. Doubling each gives 6, 12, 18, 24, 30.
Final Answer:
[6, 12, 18, 24, 30] -> Option B
Quick Check:
Lazy select multiples of 3, map *2, first 5 = B [OK]
Hint: Trace lazy steps and apply first(n) to get output [OK]
Common Mistakes:
Forgetting to double the numbers after selection
Confusing selected numbers with original range
Expecting an error from infinite range without lazy
4. Identify the error in this Ruby code using lazy enumerators:
A. The take method should be called with .to_a to get results.
B. The code will run fine and print the first 5 doubled numbers.
C. The code will raise a syntax error because lazy is not defined.
D. Calling take returns an Enumerator, not an Array, causing puts to raise an error.
Solution
Step 1: Understand what take returns on a lazy enumerator
Calling take(n) on a lazy enumerator returns another lazy enumerator, not an array.
Step 2: How to get actual values
To get an array of results, you must call to_a after take(5), like numbers.take(5).to_a.
Final Answer:
The take method should be called with .to_a to get results. -> Option A
Quick Check:
Use take(n).to_a to get array from lazy enumerator [OK]
Hint: Add .to_a after take(n) to get array output [OK]
Common Mistakes:
Expecting take(n) alone to return an array
Confusing lazy enumerator with normal enumerator
Assuming lazy is undefined or causes syntax error
5. Given a large array arr = (1..1000000).to_a, which Ruby code snippet efficiently finds the first 3 even numbers greater than 500000 using lazy enumerators?
hard
A. arr.lazy.select { |x| x > 500000 && x.even? }.first(3)
B. arr.select { |x| x > 500000 && x.even? }.first(3)
C. arr.lazy.map { |x| x * 2 }.first(3)
D. arr.take(3).lazy.select(&:even?)
Solution
Step 1: Understand the goal and data size
We want the first 3 even numbers greater than 500,000 from a large array. Using lazy enumerators avoids processing the entire array.
Step 2: Analyze each option
arr.lazy.select { |x| x > 500000 && x.even? }.first(3) uses lazy, filters with select for the condition, then takes first 3 - efficient and correct. arr.select { |x| x > 500000 && x.even? }.first(3) processes entire array eagerly, inefficient. arr.lazy.map { |x| x * 2 }.first(3) maps all elements, not filtering by condition. arr.take(3).lazy.select(&:even?) takes first 3 elements then filters, which misses numbers > 500,000.
Final Answer:
arr.lazy.select { |x| x > 500000 && x.even? }.first(3) -> Option A
Quick Check:
Lazy select with condition then first(3) = C [OK]
Hint: Use lazy select with condition, then first(n) for efficiency [OK]
Common Mistakes:
Using eager select causing slow processing
Filtering after taking first 3 elements incorrectly
Using map instead of select for filtering
Recommended
Ready to prove it?
Pass the test and this pattern moves unknownstrongwhile earning +1 Grip toward your certificate.