Skip to content
The Lumenary
Grades 9–12Ages 14–18 50 min

Composition

Doing one function and then another — why order matters, why brackets don't, and how this simple idea opens the door to category theory.

Formally verified · 8 LeanComputationally tested · 1 PythonAI-drafted · awaiting human reviewMathematically reviewed: Awaiting human review

Your guides: Chloe, Margaret, Gideon

MotivationH

One thing after another

To convert a price from Kenyan shillings to Indian rupees, a travel app might first convert shillings to US dollars, then dollars to rupees. To edit a photo, you might rotate it and then crop it. To make chapati, you mix, then knead, then roll, then cook.

Each step is a function, and doing them in sequence produces a new function. This act of chaining is called composition. It is one of the simplest ideas in mathematics — and, as we'll see, one of the deepest.

DefinitionsH

The composite of two functions

Definition— composition

If f:A→Bf : A \to B and g:B→Cg : B \to C, their composite g∘f:A→Cg \circ f : A \to C is the function

(g∘f)(x)=g(f(x)).(g \circ f)(x) = g\big(f(x)\big).

Read g∘fg \circ f as "gg after ff": apply ff first, then gg.

The composite only makes sense when the codomain of ff matches the domain of gg — the output of the first step must be a valid input for the second.

Definition— identity function

For every set AA, the identity function idA:A→A\mathrm{id}_A : A \to A is defined by idA(x)=x\mathrm{id}_A(x) = x. It does nothing — and that turns out to be important.

Explore

Explore: composition machines

Composition machines

Feed a number through two machines. Does the order matter?

g ∘ ff first, then g

3

Add 1

x ↦ x + 1

→ 4

Double

x ↦ 2x

→ 8

f ∘ gg first, then f

3

Double

x ↦ 2x

→ 6

Add 1

x ↦ x + 1

→ 7

Different results: 8 versus 7. The order of composition matters.

Worked examplesH

Worked examples

Example— Order matters

Let f(x)=x+1f(x) = x + 1 and g(x)=2xg(x) = 2x, both from Z\Z to Z\Z. Then

(g∘f)(x)=2(x+1)=2x+2,(f∘g)(x)=2x+1.(g \circ f)(x) = 2(x + 1) = 2x + 2, \qquad (f \circ g)(x) = 2x + 1.

At x=0x = 0 these give 22 and 11. So g∘f≠f∘gg \circ f \neq f \circ g: composition is not commutative.

Example— A three-step chain

Let h(x)=x2h(x) = x^2 as well. Then (h∘g∘f)(x)=h(g(f(x)))=(2(x+1))2=4(x+1)2(h \circ g \circ f)(x) = h(g(f(x))) = \big(2(x + 1)\big)^2 = 4(x + 1)^2. At x=2x = 2: f(2)=3f(2) = 3, g(3)=6g(3) = 6, h(6)=36h(6) = 36.

Example— Undoing

Let p(x)=x+5p(x) = x + 5 and q(x)=x−5q(x) = x - 5. Then q∘p=idZq \circ p = \mathrm{id}_\Z and p∘q=idZp \circ q = \mathrm{id}_\Z: each undoes the other. Functions like this are called inverses of one another.

ProofH

The laws of composition

Theorem— associativity

For all f:A→Bf : A \to B, g:B→Cg : B \to C, h:C→Dh : C \to D: h∘(g∘f)=(h∘g)∘f\quad h \circ (g \circ f) = (h \circ g) \circ f.

Proof

Both sides are functions from AA to DD, so we compare them at an arbitrary x∈Ax \in A:

(h∘(g∘f))(x)=h((g∘f)(x))=h(g(f(x))),((h∘g)∘f)(x)=(h∘g)(f(x))=h(g(f(x))).\big(h \circ (g \circ f)\big)(x) = h\big((g \circ f)(x)\big) = h\big(g(f(x))\big), \qquad \big((h \circ g) \circ f\big)(x) = (h \circ g)\big(f(x)\big) = h\big(g(f(x))\big).

They agree at every xx, so they are the same function.

∎

Because of associativity we can safely write h∘g∘fh \circ g \circ f with no brackets at all.

Theorem— identity laws

For every f:A→Bf : A \to B: f∘idA=f\quad f \circ \mathrm{id}_A = f and idB∘f=f\mathrm{id}_B \circ f = f.

Proof

(f∘idA)(x)=f(idA(x))=f(x)(f \circ \mathrm{id}_A)(x) = f(\mathrm{id}_A(x)) = f(x) and (idB∘f)(x)=idB(f(x))=f(x)(\mathrm{id}_B \circ f)(x) = \mathrm{id}_B(f(x)) = f(x) for every x∈Ax \in A.

∎

Theorem— composites of injections

If f:A→Bf : A \to B and g:B→Cg : B \to C are both injective, then g∘fg \circ f is injective.

Proof

Suppose (g∘f)(x)=(g∘f)(y)(g \circ f)(x) = (g \circ f)(y), i.e. g(f(x))=g(f(y))g(f(x)) = g(f(y)). Since gg is injective, f(x)=f(y)f(x) = f(y). Since ff is injective, x=yx = y.

∎

The same pattern of reasoning shows that composites of surjections are surjective, and therefore composites of bijections are bijective.

Formal mathematicsF

Composition in Lean

In Lean, g∘fg \circ f is written g ∘ f, and it is defined as fun x => g (f x). That makes associativity true by definition — Lean accepts the proof rfl, "both sides are literally the same":

comp_assocLean 4Proved here from basic factsFormally verified
/-- Composition is **associative**: `h ∘ (g ∘ f) = (h ∘ g) ∘ f`.

Why does `rfl` prove it? By definition `(g ∘ f) = fun x => g (f x)`. Unfolding the
definition on both sides, each one becomes `fun x => h (g (f x))` — literally the same
function. Lean accepts `rfl` ("both sides are the same by definition") because its kernel
unfolds definitions and simplifies `(fun x => …) a` automatically. -/
theorem comp_assoc (f : α → β) (g : β → γ) (h : γ → δ) : h ∘ (g ∘ f) = (h ∘ g) ∘ f := rfl
Axioms used: No axiomsformal/Lumenary/Composition.leanTry it in the Lean web editor
id_compLean 4Proved here from basic factsFormally verified
/-- Doing nothing first changes nothing: `id ∘ f = f`. Both sides send `x` to `f x`, and
Lean identifies `fun x => f x` with `f` (η-conversion), so `rfl` works. -/
theorem id_comp (f : α → β) : id ∘ f = f := rfl
Axioms used: No axiomsformal/Lumenary/Composition.leanTry it in the Lean web editor
comp_not_commLean 4Proved here from basic factsFormally verified
/-- Composition is **not commutative**: "double, then add 1" differs from
"add 1, then double". At `0` the first gives `1` and the second gives `2`. -/
theorem comp_not_comm :
    (fun n : Int => n + 1) ∘ (fun n : Int => 2 * n) ≠ (fun n : Int => 2 * n) ∘ (fun n : Int => n + 1) := by
  intro h
  -- Evaluate both sides at `0`.
  have h0 : (2 * 0 + 1 : Int) = 2 * (0 + 1) := congrFun h 0
  -- But `2 * 0 + 1 = 1` and `2 * (0 + 1) = 2`.
  exact absurd h0 (by decide)
Axioms used: No axiomsformal/Lumenary/Composition.leanTry it in the Lean web editor
injective_compLean 4Proved here from basic factsFormally verified
/-- A composite of injective functions is injective.
If `g (f a) = g (f b)`, injectivity of `g` gives `f a = f b`, and then injectivity of `f`
gives `a = b`. (Core analogue: `Function.Injective.comp`.) -/
theorem injective_comp {f : α → β} {g : β → γ} (hg : Injective g) (hf : Injective f) :
    Injective (g ∘ f) := by
  intro a b h
  -- `h : g (f a) = g (f b)`
  have hfab : f a = f b := hg h
  exact hf hfab
Axioms used: No axiomsformal/Lumenary/Composition.leanTry it in the Lean web editor
surjective_compLean 4Proved here from basic factsFormally verified
/-- A composite of surjective functions is surjective.
To hit `c`, first find `b` with `g b = c`, then `a` with `f a = b`; then `g (f a) = c`.
(Core analogue: `Function.Surjective.comp`.) -/
theorem surjective_comp {f : α → β} {g : β → γ} (hg : Surjective g) (hf : Surjective f) :
    Surjective (g ∘ f) := by
  intro c
  obtain ⟨b, hb⟩ := hg c      -- `hb : g b = c`
  obtain ⟨a, ha⟩ := hf b      -- `ha : f a = b`
  refine ⟨a, ?_⟩
  show g (f a) = c
  rw [ha, hb]
Axioms used: No axiomsformal/Lumenary/Composition.leanTry it in the Lean web editor
ComputationC

Checking every function on a small set

Composing functions: associative, but order mattersPythonExhaustive finite checkCounterexampleComputationally tested
"""Composing finite functions: associativity, identities and order.

A function on a finite set X is stored as a dict {x: f(x)}. The composite
g o f ("g after f") sends x to g(f(x)). On the 3-element set X = {0, 1, 2} we
check associativity h o (g o f) = (h o g) o f for every triple of functions,
check the identity laws, and search for functions with g o f != f o g.

Runs unchanged in the browser (Pyodide) using only the standard library.
"""

from itertools import product
import textwrap

X = (0, 1, 2)

def all_functions(domain, codomain=None):
    """Return every function domain -> codomain (default: domain -> domain) as a dict."""
    domain = list(domain)
    codomain = domain if codomain is None else list(codomain)
    return [dict(zip(domain, values)) for values in product(codomain, repeat=len(domain))]

def identity(domain):
    """The identity function on ``domain``: every x goes to itself."""
    return {x: x for x in domain}

def compose(g, f):
    """Return g o f, the function x -> g(f(x)) ("first f, then g").

    Raises ValueError if some output of f is not an input of g, because then
    g o f is not defined.
    """
    missing = set(f.values()) - set(g)
    if missing:
        raise ValueError(f"g o f is undefined: g has no value at {sorted(missing)}")
    return {x: g[f[x]] for x in f}

def show(f):
    """Write a function on 0..n-1 as its list of values, e.g. [1, 0, 2] means 0->1, 1->0, 2->2."""
    return "[" + ", ".join(str(f[x]) for x in sorted(f)) + "]"

def check_associativity(functions):
    """Check h o (g o f) == (h o g) o f for all triples; return (triples, failures)."""
    triples = 0
    failures = []
    for f, g, h in product(functions, repeat=3):
        triples += 1
        if compose(h, compose(g, f)) != compose(compose(h, g), f):
            failures.append((f, g, h))
    return triples, failures

def check_identity_laws(functions, domain):
    """Check id o f == f == f o id for every f; return (functions_checked, failures)."""
    ident = identity(domain)
    failures = [f for f in functions if not (compose(ident, f) == f == compose(f, ident))]
    return len(functions), failures

def commuting_pairs(functions):
    """Return the list of ordered pairs (f, g) with g o f == f o g."""
    return [(f, g) for f, g in product(functions, repeat=2) if compose(g, f) == compose(f, g)]

def first_noncommuting_pair(functions):
    """Return the first ordered pair (f, g) with g o f != f o g, or None if all commute."""
    for f, g in product(functions, repeat=2):
        if compose(g, f) != compose(f, g):
            return f, g
    return None

def main():
    functions = all_functions(X)
    n = len(functions)
    print(f"X = {{0, 1, 2}} has 3^3 = {n} functions X -> X.")
    print("A function is written as its list of values: [1, 0, 2] means 0->1, 1->0, 2->2.")
    print("g o f means 'first f, then g': (g o f)(x) = g(f(x)).")
    print()
    triples, failures = check_associativity(functions)
    print(f"Associativity h o (g o f) = (h o g) o f: {triples} triples checked, {len(failures)} failures")
    checked, failures = check_identity_laws(functions, X)
    print(f"Identity laws id o f = f = f o id:     {checked} functions checked, {len(failures)} failures")
    print()
    f, g = first_noncommuting_pair(functions)
    print("But order matters. First pair found with g o f != f o g:")
    print(f"  f = {show(f)}   g = {show(g)}")
    print(f"  g o f = {show(compose(g, f))}   f o g = {show(compose(f, g))}")
    swap, cycle = {0: 1, 1: 0, 2: 2}, {0: 1, 1: 2, 2: 0}
    print("A friendlier pair: s = swap 0 and 1, r = rotate 0->1->2->0:")
    print(f"  s = {show(swap)}   r = {show(cycle)}")
    print(f"  r o s = {show(compose(cycle, swap))}   s o r = {show(compose(swap, cycle))}")
    pairs = commuting_pairs(functions)
    print(f"Only {len(pairs)} of the {n * n} ordered pairs commute.")
    print()
    print("Why associativity always holds: both sides send x to h(g(f(x))).")
    print()
    print(textwrap.fill(
        f"Epistemic status: all {triples} triples and all {n} functions on this "
        "3-element set were checked, a proof by exhaustion for this set only; the "
        "one-line argument above proves associativity for all functions. A single "
        "non-commuting pair is a genuine counterexample: composition is not "
        "commutative in general.",
        width=78,
    ))

if __name__ == "__main__":
    main()
Runs entirely in your browser, in an isolated worker. Nothing is sent to a server.
Category-theoretic connectionH

The first category

Step back and look at what we have:

  • objects: sets A,B,C,…A, B, C, \dots;
  • arrows between them: functions f:A→Bf : A \to B;
  • a way to compose arrows when they line up: g∘fg \circ f;
  • an identity arrow idA\mathrm{id}_A for every object;

and two laws: composition is associative, and identities are neutral. Any collection of objects and arrows with these features is called a category. Sets and functions form the category usually called Set\mathbf{Set}.

This is not just a new name. Groups with homomorphisms, vector spaces with linear maps, shapes with continuous maps, even the integers ordered by ≤\le — each of these forms a category, and theorems proved about categories in general apply to all of them at once.

In Lean, types and functions form a category in exactly this sense. The proofs of the laws are the ones you saw above:

typesCategoryLean 4Formally verified
/-- **Types and functions form a category.** Objects are Lean types (in universe `u`),
arrows `X → Y` are functions, and `comp f g` is `g ∘ f` (first `f`, then `g`).
All three laws hold by `rfl` (see `Lumenary.Composition.comp_assoc` for why).

This is the category of Lean *types*, the type-theoretic analogue of the category **Set**
of sets and functions used in set-theoretic mathematics; Lean types are not ZFC sets, but
they play the same role here. (Mathlib analogue: `CategoryTheory.types`.) -/
instance typesCategory : Category.{u, u + 1} (Type u) where
  Hom X Y := X → Y
  id _ := fun x => x
  comp f g := g ∘ f
  id_comp _ := rfl
  comp_id _ := rfl
  assoc _ _ _ := rfl
Axioms used: No axiomsformal/Lumenary/Category.leanTry it in the Lean web editor

Analogy, not a formal construction

Our earlier "machines" picture is a helpful analogy, but a category is defined only by objects, arrows, composition, identities and the two laws. Many categories have arrows that are not functions at all — for example, in a category built from an ordered set, there is an arrow a→ba \to b exactly when a≤ba \le b.

Exercises

Exercises

1. Composition

Warm-upHuman

Let f(x)=x+3f(x) = x + 3 and g(x)=x2g(x) = x^2. Compute (g∘f)(2)(g \circ f)(2).

2. Composition

Warm-upHuman

If f:A→Bf : A \to B and g:B→Cg : B \to C, what are the domain and codomain of g∘fg \circ f?

Choose one

3. Composition

CoreHuman

Let f(x)=3xf(x) = 3x and g(x)=x−2g(x) = x - 2. Compute (g∘f)(4)−(f∘g)(4)(g \circ f)(4) - (f \circ g)(4).

4. Composition

StretchHuman

Suppose g∘fg \circ f is injective. Which of these must be true? (Choose all that apply.)

Choose all that apply

5. Composition

CoreFormal

In Lean, comp_assoc is proved by rfl. What does that tell us?

Choose one

6. Composition

StretchComputational

Finite functions can be stored as dictionaries. Write compose(g, f) returning a dictionary for g∘fg \circ f (apply f first!). You may assume every value of f is a key of g.

Runs entirely in your browser, in an isolated worker. Nothing is sent to a server.
Common misconceptionsH

Common misconceptions

Misconception

Reading g∘fg \circ f as "gg first". It means ff first, then gg — the function written closest to xx in g(f(x))g(f(x)) acts first.

Misconception

"Composition is multiplication of formulas." (g∘f)(x)(g \circ f)(x) means substituting f(x)f(x) into gg; it is not g(x)⋅f(x)g(x) \cdot f(x).

Misconception

"If g∘fg \circ f is injective, both ff and gg must be." Only ff must be. Try f:{1}→{1,2}f : \{1\} \to \{1, 2\} with f(1)=1f(1) = 1 and g:{1,2}→{1}g : \{1, 2\} \to \{1\}: g∘fg \circ f is injective, but gg is not.

Misconception

"Associative means commutative." Associativity is about brackets (h∘(g∘f)h \circ (g \circ f) vs (h∘g)∘f(h \circ g) \circ f); commutativity is about order. Composition has the first property but not the second.

Wonder Trails

Wonder Trails

Set-theoretic connectionH

Set-theoretic connection

In terms of graphs (sets of ordered pairs), the composite of f⊆A×Bf \subseteq A \times B and g⊆B×Cg \subseteq B \times C is

g∘f={ (a,c)∈A×C∣there is b∈B with (a,b)∈f and (b,c)∈g }.g \circ f = \{\, (a, c) \in A \times C \mid \text{there is } b \in B \text{ with } (a, b) \in f \text{ and } (b, c) \in g \,\}.

The same formula composes arbitrary relations, not just functions — and relations with this composition form another category, usually called Rel\mathbf{Rel}.

History & global context

History and global context

  • Repeating and chaining transformations is old: geometers and astronomers combined rotations and reflections long before there was a word for composition.
  • In the 19th century, Évariste Galois, Arthur Cayley and others studied groups of permutations and transformations, where composition is the basic operation.
  • Samuel Eilenberg and Saunders Mac Lane introduced categories in 1945, in a paper on "natural equivalences" in algebraic topology. Their idea — study objects through the arrows between them — has since spread across mathematics, computer science and logic.
Further reading

Further reading

  • F. William Lawvere and Stephen H. Schanuel, Conceptual Mathematics: A First Introduction to Categories (2nd edition, 2009) — written for beginners.
  • Tom Leinster, Basic Category Theory (2014) — free to read online.
  • Emily Riehl, Category Theory in Context (2016).