Ruby
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Methods and Blocks
-
Section 4: Object-Oriented Ruby
-
Section 5: Metaprogramming
-
Section 6: Working with Data and Files
-
Section 7: Ruby Frameworks Overview
-
Section 8: Ecosystem and Testing
-
Section 9: Practical Projects
-
Section 10: Interview Practice
-
Section 11: Data Structures and Algorithms in Ruby
-
Section 12: More Practice Exercises
-
Section 13: Enumerable and Functional Style
-
Section 14: More OOP Practice
-
Section 15: More Testing
-
Section 16: Enumerable and Comparable Modules In Depth
-
Section 17: Ruby Standard Library: Core Utilities
-
Section 18: Ruby Standard Library: Data and Security
-
Section 19: Ruby Standard Library: CLI and Text
-
Section 20: Ruby Networking
-
Section 21: Ruby on Rails Deep Dive
-
Section 22: Ruby Metaprogramming Deep Dive
-
Section 23: Ruby Design Patterns
-
Section 24: Ruby Concurrency
-
Section 25: Ruby Testing Deep Dive
-
Section 26: Ruby Gems and Packaging
-
Section 27: Ruby Performance
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Rails API Development
-
Section 31: Rails Authentication and Authorization
-
Section 32: Rails Testing Deep Dive
-
Section 33: Rails Performance
-
Section 34: Rails Deployment
-
Section 35: Sinatra and Lightweight Ruby Web Apps
-
Section 36: More Ruby Language Deep Dive
-
Section 37: Ruby 3.x Modern Features
-
Section 38: More Data Structures in Ruby
-
Section 39: More Practical Projects
-
Section 40: Ruby Ecosystem Tools
-
Section 41: More Practice and Review
-
Section 42: Final Practice and Mastery
-
Section 43: Ruby Interview Deep Dive
-
Section 44: Ruby Background Processing Deep Dive
-
Section 45: Ruby GraphQL
-
Section 46: Ruby Object Model Deep Dive
-
Section 47: Ruby Hanami Framework Overview
-
Section 48: Ruby gRPC and Protocol Buffers
-
Section 49: Ruby Data Processing
-
Section 50: Ruby Search Integration
-
Section 51: Ruby File Upload and Media
-
Section 52: Ruby Email and Notifications
-
Section 53: Ruby Admin Panels
-
Section 54: Ruby Feature Flags and Experimentation
-
Section 55: Ruby Monitoring and Observability
-
Section 56: Ruby Docker and Deployment Deep Dive
-
Section 57: Ruby Security Deep Dive
-
Section 58: More Advanced Metaprogramming
-
Section 59: More Final Projects
48: Lazy Enumerators
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!
There are no comments for now.