Evaluation Strategies: Call-by-Value, Name, and Need
Consider the expression `f(1 + 2, loop_forever())` where `f` ignores its second argument. Should the interpreter evaluate `loop_forever()` before calling `f`? That would make it hang forever. Or should it call `f` first and never touch that argument at all? This single question is central. It concerns when an argument's value is computed relative to when the function starts running. It is one of the most consequential design decisions in the semantics of a programming language. Evaluation strategy determines whether a program terminates. It determines how many times a side effect fires. It determines how much memory a computation uses. It also determines whether two expressions that look "equal" actually behave the same way. It is the reason Haskell can define an infinite list of primes and still work. It is the reason C and Python programmers must be careful about evaluating expensive arguments unconditionally. It is the reason short-circuit `&&`/`||` exist as special cases carved out of otherwise-eager languages. Compiler writers must pick a strategy, prove it is well-defined, and reason about its cost. Language designers must decide which strategy matches the mental model they want programmers to have. This article builds the theory of reduction strategies for the untyped and simply-typed Lambda Calculus. This calculus is the formal core underlying The Functional Paradigm. We define call-by-value (eager evaluation), call-by-name (normal-order, re-evaluated on each use), and call-by-need (lazy evaluation, evaluated at most once and cached). We treat these as precise reduction disciplines rather than vague intuitions. We connect each to real language implementations. C, Scheme, and ML use call-by-value. Haskell uses call-by-need. Call-by-name survives mostly as a theoretical idealization and in macro-like constructs. By the end you will be able to trace, step by step, how the same source expression behaves differently. It produces different numbers of reductions under each strategy. It shows different termination behavior. It shows different sharing of computation. You will also understand the correctness theorem behind this. This includes the standardization and Church–Rosser results. These results make call-by-name "safe" in a precise, provable sense. Call-by-value does not share that same guarantee.
- Evaluation strategy fixes when an argument expression is reduced relative to the function call that uses it.
- Call-by-value (CBV) reduces arguments to values before substituting them into the function body; it is eager.
- Call-by-name (CBN) substitutes the unevaluated argument expression directly and re-evaluates it every time it is used.
- Call-by-need (CBN-lazy) behaves like call-by-name but memoizes: each argument is evaluated at most once, then cached.
- Call-by-name (hence call-by-need) is normalizing whenever any strategy is — a term with a normal form always reaches it — while call-by-value can diverge on terms that do have a value.
- Call-by-value is simpler to implement and predict operationally; call-by-need trades that simplicity for termination guarantees and avoided recomputation, at the cost of space leaks and harder reasoning about when side effects occur.
Evaluation strategy is a choice of reduction order in the lambda calculus, and that choice trades termination guarantees against predictability of effects and cost.
The idea
An evaluation strategy (also called a reduction strategy or parameter-passing discipline at the semantic level) is a deterministic rule for choosing, at each step of computation, which subexpression to reduce next when several redexes (reducible expressions) are available. In the pure Lambda Calculus, a term — call it a beta-redex — can be reduced by substituting `a` for `x` in `e`, written . But if `a` itself contains reducible subexpressions, and `e` may or may not use `x`, there is a genuine choice about what to do first. That choice is the evaluation strategy. Three strategies dominate the theory and practice of programming languages:
Call-by-value (CBV). Reduce the argument `a` to a value (a normal form that cannot be reduced further, such as a number or a lambda abstraction) before performing the substitution. Formally, the reduction rule is restricted:
If the argument is not yet a value, it must be reduced first. This is the strategy of C, Java, Python, OCaml, Scheme, and most mainstream languages. It corresponds operationally to the everyday notion of "evaluate the arguments, then call the function."
Call-by-name (CBN). Substitute the argument expression unevaluated directly into the body:
If `x` occurs multiple times in `e`, each occurrence carries its own copy of `a`, and each is evaluated independently, redundantly, whenever it is actually needed. If `x` does not occur in `e` at all, `a` is never evaluated — dead arguments cost nothing and cannot cause divergence or unwanted side effects.
Call-by-need. Operationally identical to call-by-name in what gets evaluated and when it is first demanded, but the argument is evaluated at most once: the first time it is forced, its value is computed and then memoized (cached) in a shared location, so every other occurrence of `x` reuses the cached value instead of recomputing. This is lazy evaluation as implemented in Haskell (via thunks, suspended computations with a mutable cell that is overwritten once forced). The essential distinction is often phrased as: CBV evaluates eagerly and unconditionally; CBN evaluates on demand but repeatedly; call-by-need evaluates on demand and at most once. All three agree on programs that terminate and never inspect an argument more than once with no side effects — they differ precisely on the cases that matter: non-terminating arguments, unused arguments, and expensive-to-recompute arguments.
How it works
The clean way to describe each strategy is as a deterministic small-step operational semantics: a relation that picks out exactly one redex to fire, together with a notion of evaluation context that says where in the syntax tree that redex is allowed to occur.
Call-by-value contexts. An evaluation context `E` marks the "hole" where the next reduction must happen. For CBV with left-to-right argument evaluation: ``` E ::= [ ] -- the hole itself | E a -- reduce the function position first | v E -- once function position is a value, reduce the argument ``` The rule `(λx.e) v → e[x := v]` applies only when both the operator has become a lambda and the operand has become a value `v`. This forces every subexpression passed as an argument to be fully reduced to a value before the call proceeds.
Call-by-name contexts. Only the function position is ever a reduction site at the top level; arguments are never touched until a substituted copy of them is itself in function position or is otherwise demanded structurally: ``` E ::= [ ] | E a -- only ever reduce the operator position ``` The single rule `(λx.e) a → e[x := a]` fires unconditionally — `a` is copied into the body verbatim, reduced only later, and only where actually used.
Call-by-need machinery. Call-by-need cannot be described with pure substitution alone, because substitution copies the argument expression, which would defeat sharing. Instead, implementations use a heap of thunks: a mutable store mapping variables (or addresses) to either an unevaluated expression plus its environment, or a already-computed value. This is the essence of Wadsworth/Launchbury's graph-reduction semantics that underlies the Spineless Tagless G-Machine used by GHC. ``` PSEUDOCODE — three evaluators for a small applicative language // Shared abstract syntax Expr = Var(name) | Lam(param, body) | App(fn, arg) | Lit(number) | Prim(op, e1, e2) // ---------- CALL BY VALUE ---------- function evalCBV(e, env): match e: case Lit(n): return n case Var(x): return env[x] // already a value case Lam(p, b): return Closure(p, b, env) case Prim(op, a, b): va = evalCBV(a, env) vb = evalCBV(b, env) return apply_prim(op, va, vb) case App(f, a): vf = evalCBV(f, env) // reduce operator va = evalCBV(a, env) // reduce operand EAGERLY (p, body, cenv) = vf return evalCBV(body, cenv.extend(p, va)) // substitute the VALUE // ---------- CALL BY NAME ---------- function evalCBN(e, env): match e: case Lit(n): return n case Var(x): return force(env[x]) // env holds unevaluated thunks case Lam(p, b): return Closure(p, b, env) case Prim(op, a, b): va = force(evalCBN_thunk(a, env)) vb = force(evalCBN_thunk(b, env)) return apply_prim(op, va, vb) case App(f, a): vf = evalCBN(f, env) (p, body, cenv) = vf thunk_a = Thunk(expr=a, env=env) // NOT evaluated yet return evalCBN(body, cenv.extend(p, thunk_a)) function force(thunk): if thunk.isValue: return thunk.value return evalCBN(thunk.expr, thunk.env) // re-evaluated EVERY force // ---------- CALL BY NEED (lazy, memoized) ---------- function evalNeed(e, env): match e: case Lit(n): return n case Var(x): return forceNeed(env[x]) // env holds mutable thunk cells case Lam(p, b): return Closure(p, b, env) case Prim(op, a, b): va = forceNeed(mkThunk(a, env)) vb = forceNeed(mkThunk(b, env)) return apply_prim(op, va, vb) case App(f, a): vf = evalNeed(f, env) (p, body, cenv) = vf cell = mkThunk(a, env) // shared mutable cell, UNEVALUATED return evalNeed(body, cenv.extend(p, cell)) function forceNeed(cell): if cell.state == EVALUATED: return cell.value // reuse cached result — no recompute v = evalNeed(cell.expr, cell.env) cell.state = EVALUATED cell.value = v // OVERWRITE cell: memoize return v ``` The pivotal difference is entirely in `App`: CBV computes `va` immediately and substitutes a value; CBN builds a `thunk_a` and substitutes an unevaluated expression, re-running `force` every time `x` is read; call-by-need builds a mutable shared cell, and the first `forceNeed` call permanently updates that cell so subsequent reads are lookups rather than re-derivations.
Complexity & correctness
Time complexity — the key asymmetry. Let a subexpression `a` passed as an argument have evaluation cost and be referenced times in the function body.
- Under CBV, `a` is evaluated exactly once, cost , regardless of , but this cost is paid even when .
- Under CBN, `a` is evaluated once per occurrence that is actually forced; in the worst case this is , which can be exponential in the size of the source program when redexes nest (the classic example is applied to something expensive, or Church-numeral-style duplication). If , cost is zero.
- Under call-by-need, `a` is evaluated at most once no matter how large is: cost is if , and if . Call-by-need therefore dominates call-by-value on "wasted work" (unused arguments) and dominates call-by-name on "duplicated work" (repeated arguments) — it gets the better of both, at the price of needing a mutable heap (thunks) rather than a pure substitution model, plus the bookkeeping of forcing and updating cells.
Space complexity and space leaks. CBV's stack discipline is straightforward: values live on a call stack or are immediately consumable, and garbage collection can reclaim them once no longer referenced. Call-by-need's memoization is exactly what can go wrong: the thunk holding an unevaluated computation may be reachable long after it's needed, holding onto whatever data it closed over, and if never forced it silently retains a chain of un-evaluated expressions rather than the (smaller) value it would produce — the well-known Haskell space leak pattern (`foldl (+) 0 [1..n]` builds a chain of unevaluated thunks before forcing produces a stack overflow, whereas `foldl'`, a strict fold, is bounded in space).
Correctness — the central theorem. The deep reason call-by-name (and hence call-by-need, which computes the same values when it terminates) is theoretically preferred is a termination guarantee that call-by-value provably lacks: > Theorem (Standardization / normal-order theorem). If a lambda term has a normal form (i.e., some reduction sequence from terminates), then the normal-order reduction strategy — always reduce the leftmost-outermost redex first, which is precisely call-by-name — reaches that normal form. This is a consequence of the Church–Rosser theorem (confluence: if and , there exists with and ) combined with a standardization argument showing outermost reduction cannot get "stuck" inside a subterm that is never demanded. Call-by-value, by contrast, insists on reducing the argument first, even when the argument is never used or is inside a branch that will discard it — so CBV can diverge on terms for which a normal form provably exists. The canonical witness:
where is the diverging term. Under call-by-name, `y` never occurs in the body `λy. x`, so `Ω` is discarded unevaluated and the whole expression reduces to `λy. 5` — wait, more precisely , terminating cleanly since `Ω` is substituted for `y` but `y` never appears, so it's simply never forced. Under call-by-value, the reducer must first fully evaluate `Ω` to a value before it can even begin applying the outer function — and `Ω` has no normal form — so the CBV computation diverges on a term whose CBN evaluation is total and immediate.
Why call-by-need is safe to adopt in place of call-by-name. Call-by-need only ever performs a subset of the reductions call-by-name performs (it shares rather than duplicates), so any argument that call-by-name would evaluate to a value, call-by-need also evaluates to the same value (this is proved via a bisimulation between the substitution-based CBN semantics and the heap-based CBN-need semantics, originally due to Ariola–Felleisen and Launchbury). Consequently call-by-need inherits CBN's termination guarantee — if a normal form exists, call-by-need finds it — while matching CBV's "at most once per argument" work bound. This is why lazy functional languages can claim both properties simultaneously, at the engineering cost of a heap of updatable thunks instead of pure substitution.
Honest tradeoffs. Call-by-value is simpler to reason about operationally (the order of side effects is exactly the textual order of evaluation), is what hardware and most runtime stacks are built around, and avoids the space-leak failure mode entirely, since nothing is deferred. Call-by-need is more powerful semantically (more programs terminate, no work is wasted or duplicated) but harder to predict the timing of side effects and memory usage — which is precisely why Haskell segregates effects into a separate Monads and Effect Systems discipline (`IO`) rather than allowing arbitrary interleavings of unpredictable evaluation order with mutation.
Worked example
Take the term (using `let` sugar for a top-level application, and a strict primitive `+`): ``` let twice = λf. λx. f (f x) in twice (λy. y + 1) (double(5)) ``` where `double(n) = n + n` and, crucially, evaluating `double(5)` prints a debug message `"evaluating!"` as a side effect before returning `10`. We trace how many times `"evaluating!"` prints, and the total reduction step count, under each strategy. `f` is applied twice inside `twice`, so the argument `x = double(5)` is referenced twice — this is exactly the scenario that separates the three strategies.
| Step | Call-by-value | Call-by-name | Call-by-need |
|---|---|---|---|
| 1 | Evaluate `double(5)` immediately: prints once, gives `10` | Substitute unevaluated `double(5)` for `x` everywhere | Substitute a shared thunk `T = double(5)` for `x` |
| 2 | `twice (λy.y+1) 10` | `twice (λy.y+1) (double(5))` | `twice (λy.y+1) T` |
| 3 | `(λy.y+1) ((λy.y+1) 10)` | `(λy.y+1) ((λy.y+1) (double(5)))` | `(λy.y+1) ((λy.y+1) T)` |
| 4 | inner: `(λy.y+1) 10 → 11` | inner: force `(λy.y+1) (double(5))` — must evaluate `double(5)`: prints (1st time), gives `10`, then `10+1 = 11` | inner: force `(λy.y+1) T` — must evaluate `T`: prints (1st time), gives `10`; T is memoized as 10 |
| 5 | outer: `(λy.y+1) 11 → 12` | outer: `(λy.y+1) 11 → 12` (uses already-computed `11`, but note the inner redex touched `double(5)` fresh once) | outer: `(λy.y+1) (result 11) → 12`, `T` already `10`, no re-evaluation needed downstream |
| — | but suppose instead `f` used `x` twice inside one call, e.g. `f = λy. y + y`... | ...then the outer occurrence of `x` (the second `f x` argument copy) is a second, independent substituted copy of `double(5)`, forcing re-evaluation: prints (2nd time) | ...the outer occurrence reads the same cell, already marked evaluated: cache hit, no second print |
| Final print count | 1 | 2 (recomputed once per textual copy reached) | 1 (evaluated once, shared) |
| Final value | `12` | `12` | `12` |
The bottom rows are the ones that matter: if the doubling substitution genuinely creates two syntactic copies of `double(5)` that each get demanded (which happens whenever a variable is used more than once and both uses are forced), call-by-name evaluates the side-effecting expression twice, printing `"evaluating!"` twice, while call-by-value and call-by-need both print it exactly once — CBV because it evaluates eagerly before any copies are made, call-by-need because it shares one memoized cell across all copies. This is the concrete sense in which call-by-need "gets CBV's sharing behavior with CBN's laziness." Now consider the companion case that shows the opposite asymmetry — an argument that is never used: ``` let const5 = λx. 5 in const5 (loop_forever()) ```
- Call-by-value: evaluates `loop_forever()` first — the program hangs, never reaching `5`.
- Call-by-name: substitutes the unevaluated call for `x`; since `x` never appears in the body `5`, it is never forced; the program returns `5` immediately.
- Call-by-need: identical to call-by-name here — the thunk is built but never forced, so it is simply garbage-collected; returns `5` immediately.
These two examples together are the complete story: CBV can do wasted work (or diverge) on unused arguments; CBN can do duplicated work on multiply-used arguments; call-by-need avoids both failure modes simultaneously by combining "evaluate on demand" with "cache the result."
Common pitfalls & traps
| Pitfall | Why it happens | Fix / mitigation | ||
|---|---|---|---|---|
| Assuming CBN and CBN-lazy always give identical observable behavior | They compute the same values, but differ in how many times side effects fire and how much work is repeated | Track side-effect count separately from final value; never reason about laziness while ignoring effects | ||
| Space leaks from unforced thunk chains | Call-by-need builds up long chains of un-evaluated thunks (e.g., `foldl (+)` over a huge list) instead of computing eagerly | Use strict variants (`foldl'`, `seq`, bang patterns) to force evaluation at controlled points | ||
| Believing eager evaluation is "always faster" | CBV pays for arguments that are never used; on average this is fine but is a real cost on conditionally-dead branches | Recognize CBV trades guaranteed-once cost for potentially-wasted cost; profile, don't assume | ||
| Treating short-circuit `&&`/` | ` as ordinary CBV function calls | In a strict language these operators must be special forms (or macros), because a true CBV function call would evaluate both arguments | Implement them as control constructs (desugar to `if`), not as ordinary strict function application | |
| Forgetting that call-by-name re-executes effects | Reasoning "the expression only appears once in the source" is not the same as "it's evaluated once" under substitution-based semantics | Distinguish syntactic occurrence in the program text from dynamic occurrence after substitution/copying | ||
| Confusing evaluation strategy with parameter-passing mechanism | "Call-by-value" and "call-by-reference" describe aliasing, not when evaluation happens — a different, related axis | Keep the two questions separate; see Parameter Passing Mechanisms for the aliasing dimension |
Pioneers & history
Evaluation order was studied formally from the earliest days of the lambda calculus, well before "eager" and "lazy" became implementation vocabulary. Alonzo Church's original calculus made no commitment to a strategy at all — it was a system of terms and equalities — and it took later work by Curry, Church's students, and eventually the programming-languages community to turn "which redex do we reduce" into an operational, implementable discipline with a provable correctness story tied to the Church–Rosser property.
Origins · 1 minChristopher Strachey and how this was discovered
Strachey coined and popularized the terms "call-by-value" and "call-by-name" in the context of describing ALGOL-like languages, and — with Dana Scott — helped found denotational semantics, the mathematical framework in which the meaning of a strategy could be stated precisely rather than by example.
Where this matters
Every mainstream systems and application language — C, C++, Java, Python, JavaScript, OCaml, Scheme, Rust — is fundamentally call-by-value, which is why these languages evaluate function arguments eagerly, left to right (or in an unspecified but always-complete order), before a call proceeds; this makes cost and side-effect ordering predictable, which matters enormously for systems programming, debugging, and reasoning about performance. Haskell is the flagship call-by-need language, and its entire idiom — infinite data structures (`[1..]`), `takeWhile`, short-circuiting boolean combinators defined as ordinary functions rather than special forms, and guarded recursive definitions — depends on laziness with sharing; the runtime's Spineless Tagless G-Machine is a direct engineering realization of call-by-need graph reduction. Call-by-name survives largely as the theoretical idealization used in textbook proofs of the standardization theorem, in the semantics of C/C++ macros and Algol-style "thunked" parameters, and conceptually in default-argument or lazy-parameter features bolted onto otherwise-strict languages (Scala's `=> T` by-name parameters, C#'s `Lazy`, and default-argument thunks in some scripting languages). Compiler optimizations such as strictness analysis exist precisely to detect when a lazily-bound argument is guaranteed to be used, so the compiler can safely switch it to call-by-value at compile time and avoid the overhead of building a thunk — a concrete bridge between the theory in this article and production compiler engineering.
Ask the tutor: Can you show how the standardization theorem's proof sketch explains why leftmost-outermost reduction never gets "stuck" inside a discarded argument?
Is call-by-need the same thing as "lazy evaluation"?
Essentially yes, in modern usage — "lazy evaluation" almost always refers to call-by-need with memoization, which is what Haskell and similar languages implement. Historically "lazy" was sometimes used loosely for call-by-name too, since both delay evaluation until a value is demanded. The distinguishing technical detail is sharing: true call-by-need guarantees each argument expression is evaluated at most once via an updatable thunk, while a naive call-by-name implementation re-evaluates on every use. When people say a language is "lazy," they almost always mean the memoized, shared version, because pure call-by-name without sharing is rarely implemented in practice due to its wasted recomputation.
Why don't mainstream imperative languages just use call-by-name or call-by-need?
Predictability of side effects. In an imperative language, evaluation order determines exactly when mutations, I/O, and exceptions occur, and programmers rely on that order being simple and stated up front (left-to-right, before the call). Call-by-name or call-by-need make the timing of side effects depend on when a value is demanded, which can be far removed textually from where the expression appears, making debugging and reasoning about mutable state substantially harder. Call-by-value's eager, textual-order evaluation aligns naturally with imperative reasoning about sequences of state changes, which is why virtually every language with pervasive mutable state chooses it as the default strategy.
Do short-circuit operators like `&&` and `||` contradict a language being call-by-value?
They don't contradict it, but they are a deliberate exception carved out as special forms rather than ordinary function calls. If `&&` were an ordinary strict function, both operands would always be evaluated before the call, defeating the purpose of short-circuiting. Instead, compilers desugar `a && b` into something like `if a then b else false`, where `if` itself is a control construct with conditional evaluation, not a strict function application. This shows that even strict languages selectively introduce non-strict, name-like evaluation for specific, carefully chosen constructs, without abandoning call-by-value as the default.
How does strictness analysis relate to these strategies?
Strictness analysis is a compiler technique, common in lazy-language compilers like GHC, that determines at compile time whether a function is guaranteed to use (force) a given argument on every possible execution path. If so, the compiler can safely evaluate that argument eagerly — effectively switching it from call-by-need to call-by-value for that specific call site — without changing the observable result, because a guaranteed-forced argument produces identical behavior either way except for the overhead avoided. This recovers much of call-by-value's efficiency (no thunk allocation, no indirect force-and-cache overhead) while preserving call-by-need's semantics for arguments that are not always used.
Can call-by-value ever fail to terminate on a term that has a value under call-by-name?
Yes, and this is exactly the theoretical gap between the two strategies. The canonical example applies a function that ignores its second parameter to a divergent argument, such as `(λx. λy. x) 5 Ω` where `Ω` is a term with no normal form. Under call-by-name, `Ω` is never forced because `y` never appears in the body, and the whole expression reduces cleanly to `5`. Under call-by-value, the evaluator must fully reduce `Ω` to a value before the outer application can even proceed, and since `Ω` has no normal form, the call-by-value computation diverges forever on a term that genuinely has a value.
What is the relationship between evaluation strategy and parameter-passing mechanisms like call-by-reference?
They answer different questions and are often conflated because of overlapping vocabulary. Evaluation strategy (this article's topic) asks when an argument expression is reduced relative to the function call. Parameter-passing mechanism asks what is actually transmitted into the function — a value, a reference/alias to a mutable location, or something else — and whether mutations inside the function are visible to the caller. A language can be call-by-value while still supporting reference-like behavior via explicit pointers or reference types; the two axes are related but logically independent, and the aliasing dimension is developed further under Parameter Passing Mechanisms.
Why is confluence (Church–Rosser) relevant to evaluation strategy at all?
Confluence guarantees that if a term can be reduced to two different intermediate forms by different reduction choices, those forms can always be reconciled to a common further result. This means the lambda calculus's equational meaning — what a term is "equal to" — does not depend on which redex you reduce first, only on whether you eventually reach a normal form at all. Evaluation strategy then becomes a question of efficiency and termination rather than correctness of the final answer: confluence is why call-by-value and call-by-name compute the same value whenever both terminate, and standardization is the additional theorem pinning down which strategy is guaranteed to terminate whenever termination is possible at all.