Lazy evaluation: computing a value only when, and if, it's actually needed
JavaScript's own generators lesson covered the mechanism directly, without necessarily naming the broader principle it's one instance of. Lazy evaluation is that principle: delay computing something until the exact moment its value is actually demanded, and never compute it at all if it never is.
Intermediate
3 min read
Eager evaluation: the default most code uses without thinking about it
def get_all_users(): return db.query("SELECT * FROM users") # runs IMMEDIATELY, fetches EVERYTHINGusers = get_all_users()first_user = users[0] # we only needed ONE, but already paid for fetching ALL of them
Most ordinary code is eagerly evaluated: an expression's value is computed the moment it's assigned or returned, whether or not anything ever actually uses the full result. get_all_users() fetches every row immediately, even if the caller only ever looks at the first one — the work of fetching rows 2 through however-many was entirely wasted.
Lazy evaluation: defer the work until a value is actually pulled
def get_all_users_lazy(): offset = 0 while True: batch = db.query(f"SELECT * FROM users LIMIT 10 OFFSET {offset}") if not batch: return for user in batch: yield user # computed (and fetched) ONE AT A TIME, on demand offset += 10users = get_all_users_lazy()first_user = next(users) # fetches ONLY the first batch, nothing more
A generator (Python's yield, JavaScript's own generators lesson covers the identical mechanism) produces values lazily — nothing is computed until the caller explicitly asks for the next one via next() (or a for loop doing so implicitly). first_user = next(users) here only triggers fetching the first batch of 10 rows from the database; the remaining rows are never fetched at all unless something later keeps pulling more values.
Infinite sequences: lazy evaluation's most dramatic, concrete payoff
def naturals(): n = 0 while True: yield n n += 1first_five = []for n in naturals(): if n >= 5: break first_five.append(n)# [0, 1, 2, 3, 4] — a genuinely INFINITE generator, used safely,# because nothing beyond what was actually requested ever gets computed
naturals() describes an infinite sequence — it would never terminate if eagerly evaluated all at once (there's no "last" natural number to stop at). Because it's lazy, this is completely safe to define and use: only as many values as are actually pulled ever get produced, and the loop above simply stops pulling once it has the five it needed. An eagerly-evaluated equivalent couldn't exist as a plain list at all — you'd need to decide upfront how many values to compute, defeating the point of representing "the natural numbers" as a single, general, reusable thing.
Where Python's own standard library leans on this deliberately
filter(is_even, range(1_000_000)) # a LAZY filter object, nothing computed yetlist(filter(is_even, range(1_000_000))) # NOW it actually runs, computing every element
filter() and map() in Python 3 return lazy iterator objects, not immediately-computed lists — this is a deliberate design choice, letting several lazy operations chain together (map(f, filter(g, data))) with the entire pipeline only actually running once something finally asks for a concrete result, rather than each step eagerly building and discarding an intermediate full list.