A Programmer's Guide to Concrete Mathematics
Most programmers hit a wall at some point with mathematics. You are reading about an algorithm, the analysis section opens with a summation or a recurrence relation, and suddenly you are lost. Concrete Mathematics by Graham, Knuth, and Patashnik is the book that fixes this. It was written specifically to bridge the gap between programming and the mathematics used in algorithm analysis, and it does so with a directness that most textbooks lack.
This post gives you a practical introduction to two of its core topics — binomial coefficients and sums of powers — implemented in Python with worked examples at every step.
What Is Concrete Mathematics?
The name is a deliberate blend of continuous and discrete mathematics. While college calculus courses focus on continuous functions and smooth curves, Concrete Mathematics focuses on the discrete, integer-valued problems that actually arise when you analyze algorithms: how many steps does this loop take, how many ways can this set be arranged, how does this recurrence grow?
The main topics covered in the book are:
| Topic | Why it matters for programmers |
|---|---|
| Summations | Counting the total work done by loops |
| Recurrence relations | Analyzing recursive algorithms like merge sort |
| Binomial coefficients | Counting combinations and subsets |
| Discrete probability | Analyzing randomized algorithms |
| Generating functions | Solving recurrences and counting problems |
Binomial Coefficients
What They Are
The binomial coefficient \(\binom{n}{k}\) — read "n choose k" — counts the number of ways to choose \(k\) items from a set of \(n\) items, where the order of selection does not matter.
For example, \(\binom{5}{2} = 10\) means there are 10 different ways to pick 2 items from a set of 5. The formula is:
$$\binom{n}{k} = \frac{n!}{k!\,(n-k)!}$$where \(n!\) means \(n \times (n-1) \times \cdots \times 2 \times 1\) — the factorial of \(n\). Let's verify the example: \(\binom{5}{2} = \frac{5!}{2!\,3!} = \frac{120}{2 \times 6} = 10\). ✓
Pascal's Triangle
Binomial coefficients form a pattern called Pascal's triangle, where each number is the sum of the two numbers directly above it:
$$\binom{n}{k} = \binom{n-1}{k-1} + \binom{n-1}{k}$$This is called the recurrence relation for binomial coefficients. Written out as a triangle:
| \(n=0\): | 1 | |||||
| \(n=1\): | 1 | 1 | ||||
| \(n=2\): | 1 | 2 | 1 | |||
| \(n=3\): | 1 | 3 | 3 | 1 | ||
| \(n=4\): | 1 | 4 | 6 | 4 | 1 | |
| \(n=5\): | 1 | 5 | 10 | 10 | 5 | 1 |
Each row \(n\) contains the values \(\binom{n}{0}, \binom{n}{1}, \ldots, \binom{n}{n}\). Pick any number in the interior of the triangle — say 10 in row 5 — and you can verify it equals the sum of the two numbers directly above it: \(4 + 6 = 10\). ✓
Computing Binomial Coefficients in Python
Python's standard library includes math.comb(n, k), which computes \(\binom{n}{k}\)
directly and efficiently. We can wrap it in our own function:
from math import comb
def binomial(n: int, k: int) -> int:
"""Return the binomial coefficient C(n, k) — the number of ways
to choose k items from n items without regard to order."""
return comb(n, k)
Let's see it in action with a concrete example:
# How many ways can you choose 2 items from n items?
print(f"{'n':>4} {'C(n,2)':>8} {'Meaning'}")
print("-" * 45)
for n in range(2, 8):
print(f"{n:>4} {binomial(n, 2):>8} ways to choose 2 from {n} items")
n C(n,2) Meaning
---------------------------------------------
2 1 ways to choose 2 from 2 items
3 3 ways to choose 2 from 3 items
4 6 ways to choose 2 from 4 items
5 10 ways to choose 2 from 5 items
6 15 ways to choose 2 from 6 items
7 21 ways to choose 2 from 7 items
Notice that \(\binom{n}{2} = \frac{n(n-1)}{2}\) — the same formula as the sum of integers \(0 + 1 + \cdots + (n-1)\). This is not a coincidence; it is one of many connections between binomial coefficients and summations that Concrete Mathematics explores.
Verifying the Recurrence Relation
We can verify that the Pascal's triangle rule \(\binom{n}{k} = \binom{n-1}{k-1} + \binom{n-1}{k}\) holds in Python too:
for n, k in [(5, 2), (6, 3), (10, 4)]:
left = binomial(n-1, k-1)
right = binomial(n-1, k)
total = left + right
direct = binomial(n, k)
print(f"C({n},{k}) = C({n-1},{k-1}) + C({n-1},{k}) = {left} + {right} = {total}, direct = {direct}, match = {total == direct}")
C(5,2) = C(4,1) + C(4,2) = 4 + 6 = 10, direct = 10, match = True
C(6,3) = C(5,2) + C(5,3) = 10 + 10 = 20, direct = 20, match = True
C(10,4) = C(9,3) + C(9,4) = 84 + 126 = 210, direct = 210, match = True
Sums of Powers
What They Are
A sum of powers is what you get when you add up the same power of every integer from 1 to \(n\). The most common example is the sum of squares:
$$\sum_{k=1}^{n} k^2 = 1^2 + 2^2 + 3^2 + \cdots + n^2 = \frac{n(n+1)(2n+1)}{6}$$The right-hand side is a closed form — a direct formula that gives the answer in a single calculation, without looping through every value. For \(n = 4\): \(\frac{4 \times 5 \times 9}{6} = 30\), and indeed \(1 + 4 + 9 + 16 = 30\). ✓
Computing It in Python
We can compute the sum of squares two ways — by brute force, and using the closed form — then verify they agree:
def sum_of_squares(n: int) -> int:
"""Brute-force sum of squares: 1^2 + 2^2 + ... + n^2."""
return sum(k * k for k in range(1, n + 1))
def sum_of_squares_closed(n: int) -> int:
"""Closed form: n(n+1)(2n+1) / 6."""
return n * (n + 1) * (2 * n + 1) // 6
k * k for k in range(1, n + 1) — a generator expression that
produces \(1^2, 2^2, 3^2, \ldots, n^2\) one at a time. sum() adds them all up.
// 6 — integer division. Since \(n(n+1)(2n+1)\) is always
exactly divisible by 6 (one of \(n\) or \(n+1\) is even, and among any three consecutive
integers one is divisible by 3), this gives an exact integer result with no rounding.
print(f"{'n':>4} {'brute force':>12} {'closed form':>12} {'match':>6}")
print("-" * 42)
for n in range(1, 11):
bf = sum_of_squares(n)
cf = sum_of_squares_closed(n)
print(f"{n:>4} {bf:>12} {cf:>12} {str(bf == cf):>6}")
n brute force closed form match
------------------------------------------
1 1 1 True
2 5 5 True
3 14 14 True
4 30 30 True
5 55 55 True
6 91 91 True
7 140 140 True
8 204 204 True
9 285 285 True
10 385 385 True
Every row matches. For large \(n\), the closed form is dramatically faster — the brute-force version loops through \(n\) additions, while the closed form does the same job in three multiplications regardless of how large \(n\) is.
How These Topics Connect
One thing that makes Concrete Mathematics distinctive is that it shows how these topics are not isolated — they are facets of the same underlying ideas. For example:
The sum \(\sum_{k=1}^{n} k = \frac{n(n+1)}{2}\) is the same as \(\binom{n+1}{2}\). In other words, the sum of the first \(n\) integers equals the number of ways to choose 2 items from \(n+1\) items. We can verify this:
for n in range(1, 8):
sum_k = sum(k for k in range(1, n + 1))
binom = binomial(n + 1, 2)
print(f"n={n}: sum 1..n = {sum_k}, C({n+1},2) = {binom}, match = {sum_k == binom}")
n=1: sum 1..n = 1, C(2,2) = 1, match = True
n=2: sum 1..n = 3, C(3,2) = 3, match = True
n=3: sum 1..n = 6, C(4,2) = 6, match = True
n=4: sum 1..n = 10, C(5,2) = 10, match = True
n=5: sum 1..n = 15, C(6,2) = 15, match = True
n=6: sum 1..n = 21, C(7,2) = 21, match = True
n=7: sum 1..n = 28, C(8,2) = 28, match = True
This kind of connection — where a summation identity turns out to be a combinatorial identity in disguise — is exactly what Concrete Mathematics is about. Recognising these patterns is what allows a skilled programmer to replace an \(O(n)\) loop with an \(O(1)\) formula.
Where to Go Next
The two examples in this post barely scratch the surface. Concrete Mathematics goes on to cover recurrence relations (how to find closed forms for recursive algorithms), generating functions (a powerful tool for solving counting problems), and asymptotic analysis (how to compare the growth rates of different algorithms). Each chapter builds on the previous ones, and Python makes an excellent companion for checking your work at every step.
If you are building up your Python fundamentals alongside your mathematics, you might also find these articles useful:
Demystifying @classmethod in Python · Choosing Between cls and self Parameters in Python Classes
Comments