Every time we use methods like map, collect, select, each we create an enumerator.
Because of enumerators, methods can be chained like

[1, 2, 3].map { .... }.select { .... }

Calling each or select returns an enumerator as shown below:

[1, 2, 3].each
=> #

[1, 2, 3].select
=> #

Normal Enumerator

Let’s say, we have to fetch 10 Twitter user profiles, who
have @rails in their profile bio.

Assume, we have 1000 twitter user IDs.
We may follow this approach to fetch the 10 users:

# Array of twitter user id's
twitter_user_ids = [...]

twitter_user_ids.map { |user_id| TwitterClient.user(user_id) }
  .select { |data| data[:description].includes?("@rails") }
  .first(10)

We iterated over 1000 users, even if the first 10 users had @rails in
their bio.

Lazy Enumerator

The above code can be improved by adding lazy as shown below:

# Array of twitter user id's
twitter_user_ids = [...]

twitter_user_ids.lazy
  .map { |user_id| TwitterClient.user(user_id) }
  .select { |data| data[:description].includes?("@rails") }
  .first(10)

Here, we fetch users one-by-one without iterating over all
1000 users at once. The loop ends, when 10 users are fetched with
the required condition.

Note:

Most Enumerable methods can be called with or without a block,
but Enumerator::Lazy will always require a block.

[1, 2, 3].map
#=> #

[1, 2, 3].lazy.map
# ArgumentError: tried to call lazy map without a block