← Concepts

阅读中文版本

Eager Versus Lazy Evaluation

Eager evaluation computes a value when defined; lazy evaluation defers until needed, so lazy avoids wasted work but adds per-access thunk overhead and complicates reasoning about when effects run.

performance

Definition

Eager versus lazy evaluation is the choice of when a computation runs — eager computes at definition, paying the cost up front and storing the result; lazy defers until the value is forced, avoiding wasted work when the value is never used but adding a thunk indirection on every access and making the order of effects harder to reason about.

Context and scope

Applied in functional languages, infinite data structures, and any setting where the cost of computing an unused value or the benefit of short-circuiting justifies the indirection.

Common misconceptions

  • Lazy is always more efficient — it is more efficient when the value is unused; when always used, lazy adds overhead and eager wins
  • Lazy evaluation is just deferred function calls — it is deferred with memoization and a specific forcing discipline; a deferred call alone is not lazy evaluation

Anti-patterns

  • Making every computation lazy in a language that does not default to it, adding thunk overhead everywhere for values that are always forced
  • Using lazy evaluation for side-effecting computations, so the side effect runs at an unpredictable force time rather than the definition site

Related concepts

Authoritative source

https://www.haskell.org/tutorial/functions.html

What it is

Eager versus lazy evaluation is the choice of when a computation runs — eager computes at definition, paying the cost up front and storing the result; lazy defers until the value is forced, avoiding wasted work when the value is never used but adding a thunk indirection on every access and making the order of effects harder to reason about. Applied in functional languages, infinite data structures, and any setting where the cost of computing an unused value or the benefit of short-circuiting justifies the indirection. Performance is a distribution, not a number: the tail determines user experience, and the mean hides the outliers that cause abandonment.

How it works

Measurement targets percentiles at the workload’s actual concurrency, not a single sequential run. The fast path is kept fast by deferring work that is not on it — lazy initialization, coalescing writes, avoiding allocation in hot loops. Profiling finds the bottleneck; micro-optimization without a profile optimizes the wrong thing. Constant factors matter, but algorithmic complexity dominates as scale grows.

When to optimize and pitfalls

Optimize when a measurement shows a concrete cost — latency budget burn, compute spend, user-visible jank. The pitfall is optimizing for the benchmark rather than the workload: a synthetic test that does not reproduce the production access pattern produces a local maximum that hurts globally. Another is trading memory for speed without accounting for the GC or eviction cost the extra allocation introduces.