One input, one output
Every student in a school has exactly one birth month. A vending machine gives exactly one snack for each button that works. A thermometer shows exactly one temperature at each moment.
Each of these is a rule that takes something in and gives exactly one thing out. Rules like this are everywhere: in science, in economics, in computer programs, and in every branch of mathematics. They are called functions.
What a function is
Definition— function
A function from a set to a set , written , assigns to every element exactly one element .
- is the domain — the allowed inputs.
- is the codomain — where the outputs are allowed to live.
- The image is the set of outputs that actually occur. It is a subset of the codomain, but it need not be all of it.
Two conditions hide in the definition, and both matter:
- Every input has an output — nothing in is left out.
- Each input has exactly one output — no input is sent to two places.
Explore: draw your own function
In an arrow diagram, each element of sends an arrow to its output in . Draw arrows below and watch the explorer check the two conditions — and more.
Draw a function
Click an element of A, then an element of B, to draw an arrow. Click the same pair again to remove it.
Click an element of A to start an arrow.
- Is it a function from A to B?Yes: every element of A has exactly one arrow.
- Injective (one-to-one)?No two arrows land on the same element.
- Surjective (onto)?Every element of B is hit.
- Bijective?A perfect pairing: you could reverse every arrow.
Examples and non-examples
Example— Functions
- , . Every integer has exactly one output; the image is the set of odd integers.
- , . The image is , which is smaller than the codomain .
- The rule sending each student to their birth month is a function from the set of students to the set of twelve months.
Example— Not functions
- "Send each person to their sibling" is not a function from people to people: some people have several siblings, and some have none.
- "Send each integer to a number whose square is " fails twice: has two square roots, and , and has none in .
Injective, surjective, bijective
Definition— injective (one-to-one)
is injective if different inputs always give different outputs: whenever , we have .
Definition— surjective (onto)
is surjective if every element of the codomain is an output: for every there is some with .
Definition— bijective
is bijective if it is both injective and surjective. A bijection pairs the elements of and perfectly, so it can be reversed.
Surjectivity depends on the codomain you choose. is not surjective as a function , but it is surjective onto its image .
Proving the properties
Theorem— injective
, , is injective.
Proof
Suppose , that is, . Subtracting gives , and dividing by gives .
∎
Theorem— not injective
, , is not injective.
Proof
One counterexample is enough: , but .
∎
Theorem— not surjective
, , is not surjective.
Proof
Every output is even, so the odd number is never an output.
∎
Functions in Lean
Lean's core library defines Function.Injective f to mean ∀ a b, f a = f b → a = b — exactly our definition. Here are the proofs above, machine-checked:
/-- `n ↦ 2n + 1` is injective: if `2a + 1 = 2b + 1`, subtract 1 and divide by 2. -/
theorem injective_two_mul_add_one : Injective (fun n : Int => 2 * n + 1) := by
intro a b h
-- `h : 2 * a + 1 = 2 * b + 1`
have h1 : 2 * a = 2 * b := (Int.add_left_inj 1).mp h -- subtract 1
exact Int.eq_of_mul_eq_mul_left (by decide) h1 -- divide by 2 (and 2 ≠ 0)/-- `n ↦ n²` is **not** injective on the integers: `1² = (-1)²` but `1 ≠ -1`. -/
theorem not_injective_square : ¬ Injective (fun n : Int => n * n) := by
intro h
have h1 : (1 : Int) = -1 := h (by decide) -- the hypothesis needed is `1 * 1 = -1 * -1`
exact absurd h1 (by decide)/-- `n ↦ 2n` is **not** surjective on the integers: nothing is sent to `1`, because there is
no integer half (`Lumenary.Integers.no_int_half`). -/
theorem not_surjective_two_mul : ¬ Surjective (fun n : Int => 2 * n) := by
intro h
obtain ⟨x, hx⟩ := h 1 -- `hx : 2 * x = 1`
exact Lumenary.Integers.no_int_half ⟨x, hx⟩/-- Negation `n ↦ -n` is a bijection of the integers (it is its own inverse). -/
theorem bijective_neg : Bijective (fun n : Int => -n) := by
constructor
· -- Injective: from `-a = -b`, negate both sides.
intro a b h
exact Int.neg_inj.mp h
· -- Surjective: `-(-y) = y`.
intro y
exact ⟨-y, Int.neg_neg y⟩A function is a set of ordered pairs
In set theory, a function is not a "machine" or a "formula" — it is a set. The graph of is
A subset is the graph of a function exactly when each appears as the first coordinate of exactly one pair in . That is the definition of a function used throughout modern mathematics: it is precise, and it doesn't care whether the rule has a formula.
/-- A graph is **functional**: each input `a` is paired with `f a`, and with nothing else. -/
theorem graph_functional (f : α → β) (a : α) :
graph f (a, f a) ∧ ∀ b, graph f (a, b) → b = f a := by
constructor
· -- `graph f (a, f a)` says `f a = f a`.
rfl
· intro b h
-- `h : f a = b`
exact Eq.symm hNote
Lean is built on type theory rather than set theory, so its functions are a basic notion rather than sets of pairs. The graph above shows that the two viewpoints agree on what a function does. They are different foundations, not interchangeable ones.
How many functions are there?
If and , each of the inputs independently chooses one of outputs, so there are functions . Injective functions are rarer: the first input has choices, the next , and so on, giving .
"""Counting functions between small finite sets.
A function f: A -> B picks one output in B for every input in A. If A has m
elements and B has n, we can list every function as a tuple
(f(a_1), ..., f(a_m)) using ``itertools.product``. We count how many are
injective (one-to-one), surjective (onto) and bijective, and compare the
counts with the standard formulas:
all functions n^m
injective n (n-1) ... (n-m+1) (the falling factorial)
surjective sum_k (-1)^k C(n,k) (n-k)^m (inclusion-exclusion)
bijective m! when m = n, otherwise 0
Runs unchanged in the browser (Pyodide) using only the standard library.
"""
from itertools import product
from math import comb, factorial
import textwrap
def all_functions(domain, codomain):
"""Return every function domain -> codomain as a dict {input: output}.
There are len(codomain) ** len(domain) of them; for an empty domain there is
exactly one (the empty function).
"""
domain, codomain = list(domain), list(codomain)
return [dict(zip(domain, outputs)) for outputs in product(codomain, repeat=len(domain))]
def is_injective(f):
"""True when different inputs always give different outputs."""
outputs = list(f.values())
return len(set(outputs)) == len(outputs)
def is_surjective(f, codomain):
"""True when every element of the codomain is an output of f."""
return set(f.values()) == set(codomain)
def is_bijective(f, codomain):
"""True when f is both injective and surjective."""
return is_injective(f) and is_surjective(f, codomain)
def count_by_enumeration(m, n):
"""Enumerate all functions {0..m-1} -> {0..n-1}; return a dict of counts."""
domain, codomain = range(m), range(n)
functions = all_functions(domain, codomain)
return {
"all": len(functions),
"injective": sum(is_injective(f) for f in functions),
"surjective": sum(is_surjective(f, codomain) for f in functions),
"bijective": sum(is_bijective(f, codomain) for f in functions),
}
def falling_factorial(n, k):
"""n (n-1) ... (n-k+1), the number of ways to choose k distinct items in order.
Equals 1 when k = 0 and 0 when k > n.
"""
if n < 0 or k < 0:
raise ValueError("n and k must be non-negative")
result = 1
for i in range(k):
result *= n - i
return result
def surjection_formula(m, n):
"""Number of surjections from an m-set onto an n-set, by inclusion-exclusion.
sum over k = 0..n of (-1)^k C(n, k) (n - k)^m, using Python's 0 ** 0 == 1.
"""
if m < 0 or n < 0:
raise ValueError("m and n must be non-negative")
return sum((-1) ** k * comb(n, k) * (n - k) ** m for k in range(n + 1))
def formula_counts(m, n):
"""The counts predicted by the formulas, in the same shape as :func:`count_by_enumeration`."""
return {
"all": n ** m,
"injective": falling_factorial(n, m),
"surjective": surjection_formula(m, n),
"bijective": factorial(m) if m == n else 0,
}
def main():
sizes = [(m, n) for m in range(1, 5) for n in range(1, 5)]
print("Every function A -> B, |A| = m and |B| = n, listed with itertools.product.")
print("Each cell shows: counted by enumeration / predicted by the formula.")
print()
print(f"{'m':>2} {'n':>2} | {'all (n^m)':>11} | {'injective':>9} | {'surjective':>10} | {'bijective':>9}")
print("-" * 57)
agree = 0
listed = 0
for m, n in sizes:
counted, predicted = count_by_enumeration(m, n), formula_counts(m, n)
listed += counted["all"]
agree += counted == predicted
cells = [f"{counted[k]}/{predicted[k]}" for k in ("all", "injective", "surjective", "bijective")]
print(f"{m:>2} {n:>2} | {cells[0]:>11} | {cells[1]:>9} | {cells[2]:>10} | {cells[3]:>9}")
print()
print(f"Listed {listed} functions; enumeration and formulas agree for {agree} of {len(sizes)} size pairs.")
empty = [count_by_enumeration(0, n)["all"] for n in range(5)]
print("Edge cases: from the empty set to a set of size 0, 1, 2, 3, 4 there are")
print(f"{empty} functions (exactly one: the empty function);")
print(f"from a 2-element set to the empty set there are {count_by_enumeration(2, 0)['all']}.")
print()
print(textwrap.fill(
f"Epistemic status: exhaustive enumeration proves these counts for the "
f"{len(sizes)} size pairs shown, and the formulas match them exactly. That the "
"formulas hold for every m and n is a theorem proved by counting "
"arguments; this table is consistent with it but does not prove it.",
width=78,
))
if __name__ == "__main__":
main()Exercises
1. Functions
Warm-upHumanLet and . Which of these rules is a function from to ?
2. Functions
CoreHumanHow many functions are there from a set with elements to a set with elements?
3. Functions
CoreHumanHow many injective functions are there from a 2-element set to a 4-element set?
4. Functions
CoreHumanWhich of these functions are surjective? (Choose all that apply.)
5. Functions
StretchHumanA school has 400 students. Why must at least two of them share a birthday (day and month)?
6. Functions
StretchComputationalA finite function can be stored as a Python dictionary, e.g. {1: 'a', 2: 'b', 3: 'a'}. Write is_injective(f) returning True exactly when no two keys have the same value.
Common misconceptions
Misconception
"A function must have a formula." The birth-month function has no formula. A function is any rule — or any set of pairs — giving exactly one output per input.
Misconception
"The codomain is the same as the image." The codomain is where outputs are allowed to be; the image is where they actually are. Whether is surjective is precisely the question of whether they coincide.
Misconception
"Exactly one output means exactly one input." A function may send many inputs to the same output ( does). That is allowed; it just means the function is not injective.
Misconception
" for every function." True for , false for or . Functions that respect addition are special — they are called homomorphisms.
Wonder Trail
How can you describe the set of pairs without listing its elements — just by how functions go into it?
Wonder Trail · Optional · never gradedThe product, by its mapsCan you describe the set of pairs without ever looking inside it — only by how functions go into it?Follow the trailFunctions as arrows
Arrow diagrams are more than a teaching picture. In much of modern mathematics, structures are studied through the arrows between them — functions between sets, structure-preserving maps between groups, continuous maps between spaces. The crucial operation on arrows is following one arrow and then another: composition, the subject of the next lesson.
History and global context
- Tables recording how one quantity depends on another are ancient. In India, Āryabhaṭa's Āryabhaṭīya (499 CE) includes a table for computing sines, and astronomers in many cultures tabulated the positions of the Sun, Moon and planets over time.
- In the 14th century, Nicole Oresme drew diagrams showing how a quality varies with another quantity — an early ancestor of the graph.
- Gottfried Wilhelm Leibniz used the word function in the late 17th century; Leonhard Euler introduced the notation in 1734. In 1837 Peter Gustav Lejeune Dirichlet described functions as arbitrary correspondences, not necessarily given by formulas.
- In the early 20th century, mathematicians began defining functions as sets of ordered pairs, the definition used in this lesson.
Further reading
- Richard Hammack, Book of Proof (3rd edition, 2018) — free to read; chapters on functions and on proof techniques.
- Daniel J. Velleman, How to Prove It (2nd edition, 2006).