Skip to content

Untyped Lambda Calculus

by claude-opus-4-6
Learning objectives
  • Untyped Lambda Calculus
  • Beta Reduction
  • Church Encodings
Table of contents

Entry conditions

Use this lesson when you want to understand computation at its most fundamental level — before types, before programming languages, before machines. The lambda calculus is a formal system where every computation is expressed using only three constructs: variables, function definitions, and function application.

No prior knowledge of logic, type theory, or programming is assumed. If you have written functions in any programming language, the intuitions will be familiar.

The three building blocks

The lambda calculus has exactly three kinds of expression [@barendregt_LambdaCalculus_1984]:

  1. Variable: xx — a name that stands for a value.
  2. Abstraction: λx.M\lambda x.\, M — a function. Read: “the function that takes xx and returns MM.” The λ\lambda marks the beginning of a function definition, xx names the input, and MM is the body.
  3. Application: M  NM\; N — apply function MM to argument NN.

That is everything. There are no numbers, no strings, no if statements, no loops. All of these can be encoded using only the three constructs above — but first, we need to understand how computation happens.

Concrete intuition

Think of λx.M\lambda x.\, M as a machine with one input slot labeled xx and an output determined by MM. When you feed a value into the slot, every xx inside the machine is replaced by that value, and the machine produces its output.

Example. The identity function:

λx.x \lambda x.\, x

Takes an input and returns it unchanged. Feed it anything — it gives back the same thing.

Example. A function that applies its argument to itself:

λf.f  f \lambda f.\, f\; f

Takes a function ff and applies ff to ff. (Whether this makes sense depends on what ff is — the untyped calculus does not check.)

Example. A function that takes two arguments (by nesting):

λx.λy.x \lambda x.\, \lambda y.\, x

Takes xx, returns a function that takes yy and ignores it, returning xx. This is how multi-argument functions work in the lambda calculus — every function takes exactly one argument, and multi-argument functions are chains of single-argument functions. This technique is called currying.

Beta-reduction: how computation happens

The one computation rule is beta-reduction:

(λx.M)  N  β  M[N/x] (\lambda x.\, M)\; N \;\longrightarrow_\beta\; M[N/x]

When a function λx.M\lambda x.\, M is applied to an argument NN, substitute NN for every occurrence of xx in MM. The notation M[N/x]M[N/x] means “the expression MM with every free occurrence of xx replaced by NN.”

Worked example. Apply the identity function to the value yy:

(λx.x)  y  β  y (\lambda x.\, x)\; y \;\longrightarrow_\beta\; y

Replace xx with yy in the body xx. Result: yy.

Worked example. Apply a “make-pair” function:

(λx.λy.x)  a  β  λy.a (\lambda x.\, \lambda y.\, x)\; a \;\longrightarrow_\beta\; \lambda y.\, a

Replace xx with aa in the body λy.x\lambda y.\, x. The inner λy\lambda y is unaffected — only xx is replaced. Result: a function that takes yy and returns aa.

Worked example. Multiple reductions:

(λx.λy.x)  a  b  β  (λy.a)  b  β  a (\lambda x.\, \lambda y.\, x)\; a\; b \;\longrightarrow_\beta\; (\lambda y.\, a)\; b \;\longrightarrow_\beta\; a

First application substitutes aa for xx. Second application substitutes bb for yy — but yy does not appear in the body aa, so bb is discarded.

Free and bound variables

A variable xx is bound in an expression if it appears inside a λx.\lambda x.\, \ldots that introduces it. Otherwise it is free.

In λx.x  y\lambda x.\, x\; y: xx is bound (introduced by λx\lambda x), yy is free (not introduced anywhere).

Beta-reduction only replaces free occurrences of the variable. This prevents accidental capture — substituting an expression into a scope where its variables would be accidentally bound by a different λ\lambda.

Alpha-equivalence. The names of bound variables do not matter. λx.x\lambda x.\, x and λy.y\lambda y.\, y are the same function — they differ only in the name chosen for the input slot. This renaming is called alpha-conversion.

Non-termination

The untyped lambda calculus permits expressions that reduce forever. The classic example is Omega:

Ω=(λx.x  x)  (λx.x  x) \Omega = (\lambda x.\, x\; x)\;(\lambda x.\, x\; x)

Apply λx.x  x\lambda x.\, x\; x to itself: replace xx with (λx.x  x)(\lambda x.\, x\; x) in the body x  xx\; x, yielding (λx.x  x)  (λx.x  x)(\lambda x.\, x\; x)\;(\lambda x.\, x\; x) — which is Ω\Omega again. Reduction loops forever.

This is not a bug — it is a feature. The ability to express non-termination is what makes the untyped lambda calculus Turing-complete: it can compute anything a Turing machine can compute. The simply typed lambda calculus eliminates non-termination by adding types, but at the cost of reducing expressiveness.

Church encodings: data from functions

Since the lambda calculus has no built-in data, data must be encoded as functions. These encodings are named after Alonzo Church, who invented the lambda calculus in the 1930s [@hindley_LambdaCalculusCombinators_2008].

Booleans.

TRUE=λt.λf.t \text{TRUE} = \lambda t.\, \lambda f.\, t

FALSE=λt.λf.f \text{FALSE} = \lambda t.\, \lambda f.\, f

A boolean is a function that takes two arguments and selects one. TRUE selects the first; FALSE selects the second. This is an if-then-else in disguise: TRUE  a  bβa\text{TRUE}\; a\; b \longrightarrow_\beta a and FALSE  a  bβb\text{FALSE}\; a\; b \longrightarrow_\beta b.

Natural numbers (Church numerals).

0=λf.λx.x \mathbf{0} = \lambda f.\, \lambda x.\, x

1=λf.λx.f  x \mathbf{1} = \lambda f.\, \lambda x.\, f\; x

2=λf.λx.f  (f  x) \mathbf{2} = \lambda f.\, \lambda x.\, f\;(f\; x)

n=λf.λx.fn  x \mathbf{n} = \lambda f.\, \lambda x.\, f^n\; x

A Church numeral n\mathbf{n} is a function that takes a function ff and a starting value xx, and applies ff to xx exactly nn times. The number is the iteration count.

Successor adds one more application of ff:

SUCC=λn.λf.λx.f  (n  f  x) \text{SUCC} = \lambda n.\, \lambda f.\, \lambda x.\, f\;(n\; f\; x)

Addition composes the iteration counts:

ADD=λm.λn.λf.λx.m  f  (n  f  x) \text{ADD} = \lambda m.\, \lambda n.\, \lambda f.\, \lambda x.\, m\; f\;(n\; f\; x)

Apply ff nn times starting from xx, then apply ff mm more times. The result applies ff a total of m+nm + n times.

The Y combinator: recursion without names

The lambda calculus has no built-in recursion — a function cannot refer to itself by name, because there are no names (only variables bound by λ\lambda). The Y combinator solves this:

Y=λf.(λx.f  (x  x))  (λx.f  (x  x)) Y = \lambda f.\, (\lambda x.\, f\;(x\; x))\;(\lambda x.\, f\;(x\; x))

For any function gg, Y  gY\; g reduces to g  (Y  g)g\;(Y\; g). The Y combinator feeds a function its own fixed point, enabling recursion without self-reference. This is the mechanism behind recursion in functional programming.

Normal forms and reduction strategies

An expression is in normal form if no more beta-reductions can be applied — there are no remaining applications of a λ\lambda-abstraction to an argument.

Not every expression has a normal form (Ω\Omega does not). When an expression does have a normal form, different reduction strategies may or may not find it:

  • Normal-order reduction: always reduce the leftmost, outermost application first. This strategy is guaranteed to find the normal form if one exists (by the Church-Rosser theorem).
  • Applicative-order reduction: evaluate arguments before applying functions (like most programming languages). This is more efficient when arguments are used multiple times, but it can fail to terminate even when a normal form exists.

The Church-Rosser theorem guarantees that if two different sequences of reductions both reach a normal form, they reach the same normal form. The lambda calculus is confluent — the order of reduction does not affect the final result, only whether you reach it.

Common mistakes

  • Forgetting that application is left-associative. M  N  PM\; N\; P means (M  N)  P(M\; N)\; P, not M  (N  P)M\;(N\; P). Apply MM to NN first, then apply the result to PP.
  • Forgetting that λ\lambda extends as far right as possible. λx.M  N\lambda x.\, M\; N means λx.(M  N)\lambda x.\, (M\; N), not (λx.M)  N(\lambda x.\, M)\; N. The body of the λ\lambda includes everything to its right.
  • Substituting into bound variables. When reducing (λx.λx.x)  N(\lambda x.\, \lambda x.\, x)\; N, the inner λx\lambda x shadows the outer one. The inner xx is bound by the inner λ\lambda and is not replaced.
  • Conflating the untyped calculus with programming. The lambda calculus is a mathematical formalism, not a programming language. It has no runtime, no memory, no I/O. Programming languages implement (fragments of) the lambda calculus, but they are not identical to it.

What comes next

The untyped lambda calculus is maximally expressive but allows nonsense — self-application, non-termination, and expressions with no clear meaning. The simply typed lambda calculus adds a type system that rules out ill-formed expressions at the cost of some expressiveness. That lesson is the next step.

Minimal data

  • Three expression forms: variable, abstraction (λx.M\lambda x.\, M), application (M  NM\; N).
  • One computation rule: beta-reduction ((λx.M)  NβM[N/x](\lambda x.\, M)\; N \longrightarrow_\beta M[N/x]).
  • Alpha-equivalence: bound variable names are interchangeable.
  • Church-Rosser theorem: reduction order does not affect the result (when a result exists).

Relations

Authors
Cites
  • The lambda calculus its syntax and semantics
  • An introduction to functional programming through lambda calculus
  • Lambda calculus and combinators an introduction
Date created
Teaches

Cite

@misc{claude-opus-4-62026-untyped-lambda-calculus,
  author    = {claude-opus-4-6},
  title     = {Untyped Lambda Calculus},
  year      = {2026},
  url       = {https://emsenn.net/library/math/domains/type-theory/texts/untyped-lambda-calculus/},
  publisher = {emsenn.net},
  license   = {CC BY-SA 4.0}
}