Skip to Content
Course content
Click on the "Edit" button in the top corner of the screen to edit your slide content.

I was working on a script the other day that needed to process a massive set of IDs—millions of them—to find the first few that matched a specific, somewhat expensive criteria. I wrote a quick chain of methods, hit run, and my laptop fan immediately started sounding like a jet engine. It just hung there.

The performance wall

Here is the code I wrote. I wanted to take a huge range of numbers, find the ones divisible by 7, square them, and just grab the first 5 results.


(1..10_000_000).select { |n| 
  puts "Checking #{n}..."
  n % 7 == 0 
}.map { |n| 
  puts "Squaring #{n}..."
  n * n 
}.first(5)

If you run this, you'll notice something frustrating. It doesn't just find five numbers and stop. It prints "Checking..." ten million times. It processes the entire range through the select block, creates a massive intermediate array of all numbers divisible by 7, then maps over that entire intermediate array, and only at the very end does first(5) throw away 99.9% of the work.

This is "eager evaluation." Ruby is trying to be helpful by completing each step of the chain fully before moving to the next. But when your dataset is huge, this is a disaster.

Bringing in .lazy

I realized I didn't actually need to filter ten million numbers; I only needed to filter enough to find five matches. I wondered if I could tell Ruby to stop being so eager. That's where .lazy comes in.

Let's try the exact same logic, but I'll insert .lazy right after the range:


(1..10_000_000).lazy.select { |n| 
  puts "Checking #{n}..."
  n % 7 == 0 
}.map { |n| 
  puts "Squaring #{n}..."
  n * n 
}.first(5)

The difference is night and day. If you watch the output now, you'll see: "Checking 1...", "Checking 2...", all the way to 7. Then "Squaring 7...". Then it continues checking until it finds the next match, squares it, and repeats this until it hits exactly five results. Then it stops completely.

By calling .lazy, I changed the behavior from "batch processing" to "pipelining." Instead of the select method handling the whole list, it now handles one element at a time, passing it to map, which passes it to first. As soon as first(5) is satisfied, it sends a signal up the chain to stop the entire process.

The magic of infinite sequences

This isn't just about speed; it's about things that are literally impossible with eager enumeration. I've always liked the idea of an "infinite" list. In standard Ruby, this would crash your program by consuming all your RAM:


# This will hang your terminal/IDE
(1..Float::INFINITY).select { |n| n % 2 == 0 }.first(5)

But with a lazy enumerator, infinity is totally fine because Ruby never actually tries to "finish" the list. It just keeps pulling the next item until the terminal condition (like first) is met.


(1..Float::INFINITY).lazy.select { |n| n % 2 == 0 }.first(5)
# => [2, 4, 6, 8, 10]

One quick warning: remember that .lazy returns an Enumerator::Lazy object, not an Array. If you call a method that doesn't "trigger" the evaluation (like first, to_a, or reduce), nothing will happen. You'll just have a lazy object sitting there, waiting for someone to actually ask for the data.




📋 Practical Task

Filtering a Mock Sensor Stream

Imagine you are monitoring a high-frequency sensor that produces an infinite stream of temperature readings. You need to find the first 3 readings that are "Critical" (above 100 degrees) and convert them to Fahrenheit for a report.

Your Task:

  • Create an infinite range of numbers starting from 1 (using Float::INFINITY).
  • Use a lazy enumerator to filter for numbers that are divisible by 13 (we'll pretend these are the "Critical" spikes).
  • Map those filtered numbers by multiplying them by 1.8 and adding 32 (the Celsius to Fahrenheit formula).
  • Retrieve only the first 3 results.
  • Print the final array to the console.

Hint: Make sure you call .lazy before your select call, otherwise your program will never finish executing!

Rating
0 0

There are no comments for now.

to be the first to leave a comment.