Capture Avoidance
What goes wrong with naive substitution. Why $\alpha$-conversion is needed.
Substitution is the engine of computation in lambda calculus. But the definition we have learned so far has a dangerous edge case. If we are not careful, substitution can silently change the meaning of a term. This concept explains what goes wrong and how to fix it.
The Capture Problem
Consider the substitution (λy.x)[x := y]. Naively, we replace the
free x with y, giving λy.y. But this is wrong.
- Before:
λy.xis a function that ignores its argument and returns whateverxrefers to externally. - After (naive):
λy.yis the identity function -- it returns its argument.
The free variable y in the substituted term got captured by the
binder λy. A variable that was supposed to stay free became bound.
This changes the meaning of the term entirely.
Naive Substitution Fails
Here is a concrete example of capture in action.
Start with (λx.λy.x)[x := y]. The term λx.λy.x is a function
that takes x and returns a constant function that always returns x.
Naive substitution replaces the free x in λy.x with y:
λy.y -- but now the free y we inserted is bound by λy.
- Before:
λy.xignores its argument and returns externalx - After (naive):
λy.yreturns its argument -- the identity
The substitution changed the meaning of the inner function.
What Is Variable Capture?
Variable capture is the name for this specific error: a free variable in the substituted term accidentally becomes bound by a binder already present in the body.
In (λy.x)[x := y]:
yis free in the argumentyλyis a binder in the bodyλy.x- After naive substitution, the free
yfalls insideλy's scope - Result:
λy.y-- the freeyis now bound. Captured.
Capture happens when the argument contains a free variable whose name matches a binder in the body where the substitution lands.
Is This Capture?
To check whether a substitution M[x := N] risks capture, ask:
- Does
Ncontain any free variables? - Does any free variable in
Nshare a name with a binder inMthat is in scope wherexappears?
If both answers are yes, naive substitution would cause capture.
(λy.x)[x := y] -- y is free in the argument, λy binds in
the body where x appears. Capture risk.
(λy.x)[x := a] -- a is free in the argument, but no binder
in the body uses a. No capture risk.
The Fix -- Alpha-Rename First
The fix for variable capture is simple: $\alpha$-rename the conflicting binder before substituting.
(λy.x)[x := y] has a capture risk because the argument y
matches the binder λy. So rename λy to a fresh name first:
- $\alpha$-rename
λy.xtoλz.x(rename the binder fromytoz) - Now substitute:
(λz.x)[x := y] = λz.y
The result λz.y correctly keeps y free. The function still
ignores its argument (now called z) and returns the external y.
This is the right answer.
Safe Substitution
Capture-avoiding substitution follows two steps when there is a conflict:
- $\alpha$-rename the conflicting binder to a fresh name
- Substitute as usual
Example: (λx.λy.x)[x := y]
- The free
xappears insideλy's scope - The argument
ymatches the binderλy-- capture risk - Rename
λytoλz: the body becomesλz.x - Substitute:
(λz.x)[x := y] = λz.y
The result λz.y is correct: a function that ignores its argument
z and returns the external y.
When Does Capture Happen?
Not every substitution risks capture. Capture requires a specific collision: a free variable in the argument must share a name with a binder in the body that is in scope where the replaced variable appears.
Capture risk:
(λy.x)[x := y]-- freeyin argument,λyin body
No capture risk:
(λy.x)[x := a]-- freeain argument, but noλain body(λy.y)[y := a]--yis bound, not being replacedx[x := a]-- no binders at all
If the argument has no free variables, or its free variables do not collide with any binder in the body, substitution is safe.
No Capture Risk Here
Most substitutions do not require capture avoidance. When the argument's free variables do not collide with any binder in the body, you can substitute directly.
(λy.x)[x := a]
- The argument is
a(free variable) - The body's binder is
λy-- which bindsy, nota - No collision, so substitute directly:
λy.a
(λy.x y)[x := a]
- The argument
adoes not match the binderλy - Substitute directly:
λy.a y
$\alpha$-renaming is only needed when there is a genuine name collision between the argument's free variables and the body's binders.
Result After Renaming
Putting it all together: when you see a substitution that risks capture, the procedure is:
- Identify the conflict (which free variable matches which binder)
- $\alpha$-rename the conflicting binder to a fresh name
- Perform the substitution on the renamed term
Example: (λx.λy.x)[x := y]
- Conflict: free
yin argument, binderλyin body - Rename
λytoλz: body becomesλz.x - Substitute:
(λz.x)[x := y] = λz.y
The answer is a lambda expression -- but short enough to type.
Why Capture Avoidance Matters
Capture avoidance is not an optional safeguard or an edge-case workaround. It is part of the correct definition of substitution.
Without it, substitution can turn a constant function into the identity, or change which external value a term references. If substitution changes meaning, then $\beta$-reduction changes meaning, and computation itself becomes unreliable.
The full rule for substitution into an abstraction is:
(λy.M)[x := N] = λy.(M[x := N]) if y is not free in N
(λy.M)[x := N] = λz.(M[y := z][x := N]) if y is free in N
where z is a fresh variable. The second case is capture avoidance:
rename the binder, then substitute.
Ready to test your understanding?
Bitwit uses spaced repetition to help you truly master concepts like this—not just read about them. Each card generates with different values, so you can't just memorize answers.
Practice Capture Avoidance →