Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Data Structures & Algorithms (DSA)  Module 18 – Maths for DSA

Counting: nCr modulo p and Catalan numbers

Compute binomial coefficients modulo a prime with factorial tables, use Pascal's triangle for any modulus, and spot Catalan numbers in counting problems.

  • Intermediate
  • 30 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Node.js 24.21.0, quickjs 0.32.0, Java 25, Clang 21.0.0 and Go 1.27.1
  • By MySmartCoPilot
Free core, Pro depth: 11 of 30 minutes free
Free: What C(n, k) counts · Factorial tables modulo a prime · Pascal's triangle for any modulus · 2 more sections. With a pass: 3 in-depth parts and 3 interview answers, about 19 minutes.

What you will learn

  • Compute binomial coefficients modulo a prime with factorial and inverse-factorial tables
  • Use Pascal's triangle when the modulus is not prime
  • Recognise Catalan numbers in trees, brackets and paths
  • Apply Lucas's theorem when n is larger than the prime

Before you start

C(n, k), read “n choose k”, counts the ways to choose k items from n when the order does not matter: C(n, k) = n! / (k! × (n − k)!). Its values grow fast, and C(1000, 500) already has 300 digits, so programs usually compute it modulo a prime p such as 10⁹ + 7. Modulo a prime, build tables of n! and of the inverses of the factorials once, in O(n), and every query is then two multiplications. When the modulus is not prime, Pascal’s rule C(n, k) = C(n − 1, k − 1) + C(n − 1, k) uses additions only and works for any modulus, in O(n × k). The Catalan numbers, C(2n, n) / (n + 1) = 1, 1, 2, 5, 14, 42 and so on, count the shapes of binary search trees, balanced strings of brackets and many other structures.

What C(n, k) counts

Choosing k items is the same as leaving out the other n − k, so C(n, k) = C(n, n − k), and there is no way to choose more items than there are, so C(n, k) = 0 when k > n:

Choosing items, listed and counted Python · count/choose.py
import math
from itertools import combinations

print("3 of the 5 letters a to e, order ignored:")
print(" ", " ".join("".join(c) for c in combinations("abcde", 3)))
print(f"  that is C(5, 3) = {math.comb(5, 3)}, and C(5, 2) = {math.comb(5, 2)}: keeping 3 is leaving out 2")
print(f"6 numbers of 49: C(49, 6) = {math.comb(49, 6):,}")
print(f"C(1000, 500) has {len(str(math.comb(1000, 500)))} digits")
print(f"C(4, 7) = {math.comb(4, 7)}: there is no way to choose 7 items from 4")

Output

3 of the 5 letters a to e, order ignored:
  abc abd abe acd ace ade bcd bce bde cde
  that is C(5, 3) = 10, and C(5, 2) = 10: keeping 3 is leaving out 2
6 numbers of 49: C(49, 6) = 13,983,816
C(1000, 500) has 300 digits
C(4, 7) = 0: there is no way to choose 7 items from 4

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 choose.py

Python’s math.comb returns the exact value at any size, which makes it the reference to test modular code against. Exact values are expensive, though: C(1000, 500) has 300 digits, and a program that answers a hundred thousand such queries works with remainders instead.

Factorial tables modulo a prime

Division modulo p means multiplying by an inverse, so C(n, k) mod p = n! × (k!)⁻¹ × ((n − k)!)⁻¹ mod p. Building the tables takes two passes. The first goes up: fact[i] = fact[i − 1] × i mod p. The second goes down from a single inverse: inv_fact[N] = fact[N]^(p − 2) by Fermat’s little theorem, and then inv_fact[i − 1] = inv_fact[i] × i, since 1 / (i − 1)! = i / i!. One fast exponentiation and 2N multiplications build both tables.

Factorial tables up to 10⁶, with two independent checks Python · tables/factorial_tables.py
import math

P = 10**9 + 7
N = 1_000_000

# fact[i] = i! mod P, built upwards; inv_fact[i] = (i!)^-1 mod P, built downwards from one Fermat inverse.
fact = [1] * (N + 1)
for i in range(1, N + 1):
    fact[i] = fact[i - 1] * i % P
inv_fact = [1] * (N + 1)
inv_fact[N] = pow(fact[N], P - 2, P)
for i in range(N, 0, -1):
    inv_fact[i - 1] = inv_fact[i] * i % P


def comb_mod(n, k):
    if k < 0 or k > n:
        return 0
    return fact[n] * inv_fact[k] % P * inv_fact[n - k] % P


def comb_by_product(n, k):
    """An independent check: n(n-1)...(n-k+1) / k!, with a single inverse at the end."""
    top = bottom = 1
    for i in range(k):
        top = top * (n - i) % P
        bottom = bottom * (i + 1) % P
    return top * pow(bottom, P - 2, P) % P


print(f"C(49, 6) mod P = {comb_mod(49, 6):,}")
print(f"C(100000, 50000) mod P = {comb_mod(10**5, 5 * 10**4)}, exact math.comb agrees: {comb_mod(10**5, 5 * 10**4) == math.comb(10**5, 5 * 10**4) % P}")
print(f"C(1000000, 500000) mod P = {comb_mod(N, N // 2)}, the product formula agrees: {comb_mod(N, N // 2) == comb_by_product(N, N // 2)}")
print(f"C(10, 11) = {comb_mod(10, 11)}, C(10, -1) = {comb_mod(10, -1)}")
print(f"each query: 2 multiplications and 2 reductions after building {2 * N:,} table entries")

Output

C(49, 6) mod P = 13,983,816
C(100000, 50000) mod P = 149033233, exact math.comb agrees: True
C(1000000, 500000) mod P = 996692777, the product formula agrees: True
C(10, 11) = 0, C(10, -1) = 0
each query: 2 multiplications and 2 reductions after building 2,000,000 table entries

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 factorial_tables.py

Each query is now two multiplications, C(49, 6) comes back as 13,983,816, and the values past the modulus agree with exact arithmetic for C(100,000, 50,000) and with a separate product formula for C(1,000,000, 500,000). Queries with k < 0 or k > n must return 0 before any table lookup.

Pascal’s triangle for any modulus

Every entry of Pascal’s triangle is the sum of the two above it, because a set of k items from n either contains the last item, leaving k − 1 to choose from the first n − 1, or does not, leaving all k to choose from them:

Rows 0 to 6 of Pascal's triangle, with C(6, 3) = 20 highlighted as the sum of the two entries above it, 10 and 10.row 0row 1row 2row 3row 4row 5row 6111121133114641151010511615201561

Pascal's triangle, rows 0 to 6: each entry is the sum of the two above it

Text description of the diagram

The picture shows rows 0 to 6 of Pascal's triangle, one row per line, centred. Entry k of row n is C(n, k), the number of ways to choose k items from n.

  • Row 0: 1. Row 1: 1, 1. Row 2: 1, 2, 1. Row 3: 1, 3, 3, 1. Row 4: 1, 4, 6, 4, 1. Row 5: 1, 5, 10, 10, 5, 1. Row 6: 1, 6, 15, 20, 15, 6, 1.
  • Three entries are highlighted: the two 10s of row 5 and the 20 in the middle of row 6, with lines from each 10 down to the 20. They show Pascal's rule, C(6, 3) = C(5, 2) + C(5, 3) = 10 + 10 = 20: a set of 3 items from 6 either contains the sixth item, and then 2 more come from the first 5, or it does not, and then all 3 come from the first 5.

Every entry is built by additions only, so the triangle can be computed modulo any number, prime or not.

That rule needs no division, so it works modulo any number. Factorial tables fail modulo 10⁹ = 2⁹ × 5⁹, because every factorial from 40! on is a multiple of 10⁹ and has no inverse:

Row 1000 of Pascal's triangle modulo 10⁹ Python · pascal/pascal_mod.py
import math

M = 10**9  # not a prime: 10^9 = 2^9 × 5^9

print(f"40! mod 10^9 = {math.factorial(40) % M}: factorials are multiples of 10^9 from 40! on, so no inverse exists")

row = [1]  # row n of Pascal's triangle, modulo M: C(n, k) = C(n - 1, k - 1) + C(n - 1, k)
additions = 0
for n in range(1, 1001):
    row = [1] + [(row[k - 1] + row[k]) % M for k in range(1, n)] + [1]
    additions += n - 1
print(f"row 1000 built with {additions:,} additions")
print(f"C(1000, 500) mod 10^9 = {row[500]}")
print(f"every entry of row 1000 matches math.comb(1000, k) % 10^9: {all(row[k] == math.comb(1000, k) % M for k in range(1001))}")

Output

40! mod 10^9 = 0: factorials are multiples of 10^9 from 40! on, so no inverse exists
row 1000 built with 499,500 additions
C(1000, 500) mod 10^9 = 821216320
every entry of row 1000 matches math.comb(1000, k) % 10^9: True

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 pascal_mod.py

Row 1000 took 499,500 additions and matches math.comb(1000, k) % 10**9 in every entry. A table of rows up to n is O(n²) work and memory, fine for n of a few thousand.

Catalan numbers

Count the binary search trees that can hold the keys 1 to n. Choosing key r as the root leaves r − 1 keys for the left subtree and n − r for the right one, and any left shape goes with any right shape, so the counts satisfy Cₙ = Σ C(r − 1) × C(n − r) over every root r, starting from C₀ = 1. These are the Catalan numbers, and their closed form is C(2n, n) / (n + 1):

Shapes of binary search trees: DP, formula and a full listing Python · catalan/bst_shapes.py
import math
from functools import lru_cache


def shapes_by_dp(n):
    """count[i] = the number of BST shapes with i keys: pick a root, split the other keys left and right."""
    count = [1] + [0] * n
    for i in range(1, n + 1):
        count[i] = sum(count[left] * count[i - 1 - left] for left in range(i))
    return count


@lru_cache(maxsize=None)
def all_shapes(n):
    """Every BST shape with n keys, as nested tuples (left, right), for a brute-force count."""
    if n == 0:
        return [None]
    return [(l, r) for left in range(n) for l in all_shapes(left) for r in all_shapes(n - 1 - left)]


dp = shapes_by_dp(10)
formula = [math.comb(2 * n, n) // (n + 1) for n in range(11)]
print(" n  shapes by DP  C(2n, n)/(n + 1)  listed one by one")
for n in range(1, 11):
    listed = f"{len(all_shapes(n)):,}" if n <= 8 else "-"
    print(f"{n:>2}  {dp[n]:>12,}  {formula[n]:>16,}  {listed:>17}")

Output

 n  shapes by DP  C(2n, n)/(n + 1)  listed one by one
 1             1                 1                  1
 2             2                 2                  2
 3             5                 5                  5
 4            14                14                 14
 5            42                42                 42
 6           132               132                132
 7           429               429                429
 8         1,430             1,430              1,430
 9         4,862             4,862                  -
10        16,796            16,796                  -

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 bst_shapes.py

The DP, the formula and a brute-force listing agree: 14 trees for 4 keys and 16,796 for 10. The same numbers count the balanced strings of n pairs of brackets, the ways to cut a convex polygon with n + 2 corners into triangles, and the grid paths that never cross a diagonal.

GATE CS

The GATE 2027 syllabus for Computer Science lists counting and recurrence relations under Discrete Mathematics, and binary search trees under Programming and Data Structures. The tree count above is a recurrence of exactly that kind; practise solving it by hand for n up to 5.

Method Modulus Build Per query Use when
Factorial tables A prime larger than n O(n) O(1) Many queries, n up to about 10⁷
Pascal’s triangle Any modulus O(n²) O(1) A modulus that is not prime, small n
Exact math.comb None None Grows with the digits Testing, or one exact answer

Lucas’s theorem computes C(n, k) modulo a small prime p for huge n by splitting n and k into base-p digits.

Exercise and self-check

Answer many C(n, k) queries modulo 10⁹ + 7, building the tables once.

Exercise · Medium · Python, JavaScript

Answer many nCr queries modulo 10⁹ + 7

Write ncr_mod(queries) (JavaScript: ncrMod). queries is a list of pairs (n, k) (JavaScript: [n, k]) with 0 <= n <= 10⁶ and any whole number k, and the function returns, in order, C(n, k) modulo 10⁹ + 7 for each pair: 0 when k < 0 or k > n.

There can be 100,000 queries, so build the factorials and their inverses once, up to the largest n, and answer each query with a few multiplications. In JavaScript the product of two residues can reach 10¹⁸, past the 2⁵³ that a Number holds exactly: split one factor into 16-bit halves, or use BigInt for the products.

The sample tests check small values, k outside 0 to n, symmetry, C(10⁶, 5 × 10⁵), and 10,000 random queries compared with exact arithmetic.

Python · Starter code · ncr.py

def ncr_mod(queries):
    """C(n, k) mod 10**9 + 7 for each (n, k): 0 when k < 0 or k > n."""
    raise NotImplementedError("write ncr_mod")
The sample tests · test_ncr.py
import math
import random

from ncr import ncr_mod

P = 10**9 + 7


def test_small_values():
    """small binomial coefficients, below the modulus"""
    assert ncr_mod([(5, 2), (49, 6), (10, 0), (10, 10), (0, 0)]) == [10, 13_983_816, 1, 1, 1]


def test_k_outside_the_range():
    """k below 0 or above n gives 0"""
    assert ncr_mod([(4, 7), (4, -1), (0, 1), (3, 4)]) == [0, 0, 0, 0]


def test_symmetry():
    """C(n, k) = C(n, n - k)"""
    pairs = [(1000, 3), (1000, 997), (777, 300), (777, 477)]
    a, b, c, d = ncr_mod(pairs)
    assert a == b and c == d


def test_values_past_the_modulus():
    """C(1000, 500) and C(10^6, 5 × 10^5), whose exact values are far larger than P"""
    assert ncr_mod([(1000, 500)]) == [math.comb(1000, 500) % P]
    assert ncr_mod([(10**6, 5 * 10**5)]) == [996_692_777]


def test_ten_thousand_random_queries():
    """10,000 random queries with n up to 3,000, against exact arithmetic"""
    rng = random.Random(17)
    queries = []
    for _ in range(10_000):
        n = rng.randint(0, 3000)
        queries.append((n, rng.randint(-5, n + 5)))
    answers = ncr_mod(queries)
    assert sum(answers) == 4_836_647_905_907
    for (n, k), answer in list(zip(queries, answers))[:200]:
        assert answer == (math.comb(n, k) % P if 0 <= k <= n else 0), (n, k)

JavaScript · Starter code · ncr.mjs

/** C(n, k) mod 1,000,000,007 for each [n, k]: 0 when k < 0 or k > n. */
export function ncrMod(queries) {
  throw new Error('write ncrMod');
}
The sample tests · ncr.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { ncrMod } from './ncr.mjs';

/** A small seeded generator (Park and Miller), so every run builds the same queries. */
function generator(seed) {
  const random = () => (seed = (seed * 48271) % 2147483647) / 2147483647;
  return (k) => Math.floor(random() * k);
}

test('small binomial coefficients, below the modulus', () => {
  assert.deepEqual(ncrMod([[5, 2], [49, 6], [10, 0], [10, 10], [0, 0]]), [10, 13983816, 1, 1, 1]);
});

test('k below 0 or above n gives 0', () => {
  assert.deepEqual(ncrMod([[4, 7], [4, -1], [0, 1], [3, 4]]), [0, 0, 0, 0]);
});

test('C(n, k) = C(n, n - k)', () => {
  const [a, b, c, d] = ncrMod([[1000, 3], [1000, 997], [777, 300], [777, 477]]);
  assert.ok(a === b && c === d);
});

test('C(1000, 500) and C(10^6, 5 × 10^5), whose exact values are far larger than P', () => {
  assert.deepEqual(ncrMod([[1000, 500]]), [159835829]);
  assert.deepEqual(ncrMod([[1000000, 500000]]), [996692777]);
});

test('10,000 random queries with n up to 3,000: their answers add up to a known total', () => {
  const pick = generator(17);
  const queries = [];
  for (let i = 0; i < 10000; i++) {
    const n = pick(3001);
    queries.push([n, pick(n + 11) - 5]);
  }
  const answers = ncrMod(queries);
  assert.equal(answers.length, 10000);
  assert.equal(answers.reduce((s, x) => s + x, 0), 4837030980937);
});
A hint

Build fact[i] = i! mod P upwards, then inv_fact[N] = pow(fact[N], P - 2, P) and inv_fact[i - 1] = inv_fact[i] * i mod P downwards. A query is fact[n] * inv_fact[k] * inv_fact[n - k] mod P, reduced after each multiplication.

The sample tests run on this device, in your browser (Pyodide, QuickJS): nothing is sent to mysmartcopilot.com. The first run of each language downloads it: Python (about 13.5 MB) or JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Permutation & Combination Calculator Check a combination of your own, or compare small values with your tables.

Check yourself

5 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 5 In how many ways can you choose 3 books from a shelf of 6 if the order does not matter?

    Type a number.

    Show the answer to question 1

    Answer: 20 ways

    C(6, 3) = 6! / (3! × 3!) = 720 / 36 = 20. It is also the middle of row 6 of Pascal's triangle, 10 + 10 from the two entries above it.

  2. Question 2 of 5 Why can't factorial tables with inverses compute C(n, k) modulo 10⁹ for large n?

    Choose one answer.

    Show the answer to question 2

    Answer: From 40! on, n! is a multiple of 10⁹, so it is 0 modulo 10⁹ and has no inverse

    10⁹ = 2⁹ × 5⁹ is not prime, and 40! already contains nine factors of 5 and far more than nine of 2, so every factorial from 40! on leaves remainder 0. An inverse needs gcd(x, 10⁹) = 1. Pascal's rule uses additions only and works for any modulus.

  3. Question 3 of 5 After building factorial and inverse-factorial tables up to 10⁶ modulo a prime, what does one query C(n, k) cost?

    Choose one answer.

    Show the answer to question 3

    Answer: Two multiplications, each reduced modulo the prime

    C(n, k) = fact[n] × inv_fact[k] × inv_fact[n − k], two multiplications with a reduction after each. The single fast exponentiation happens once, when inv_fact[N] is built.

  4. Question 4 of 5 How many structurally different binary search trees hold the keys 1, 2, 3 and 4?

    Type a number.

    Show the answer to question 4

    Answer: 14 trees

    The fourth Catalan number: C(8, 4) / 5 = 70 / 5 = 14. By the recurrence, choosing each key as the root in turn gives 5 + 2 + 2 + 5 = 14, the products of the counts of the left and right subtrees.

  5. Question 5 of 5 Which of these is counted by the Catalan number C₃ = 5?

    Choose one answer.

    Show the answer to question 5

    Answer: The balanced strings of 3 pairs of brackets, such as (()()) and ()(())

    The five strings are ((())), (()()), (())(), ()(()) and ()()(). The other counts are C(5, 3) = 10, 3! = 6 and 2³ = 8.

Why the tables are right, and nCr in five languages Pro: opens with a Pro, Premium or Ultimate pass

The tables rest on one inverse and one identity, and they cost memory that depends on the language. This part proves both passes, runs the same tables in Python, JavaScript, Java, C++ and Go, reproduces the JavaScript product trap and measures what the tables cost in CPython.

In this part:

  • Both passes proved, and the cost counted
  • The same tables in five languages, with run records
  • The JavaScript product trap, reproduced
  • What the tables cost in CPython, measured
  • Build once for every test case

4 runnable examples (C++, Go, Java, JavaScript, Python) · about 3 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Catalan numbers proved two ways, and found in four problems Pro: opens with a Pro, Premium or Ultimate pass

The Catalan numbers turn up wherever a structure splits into two smaller ones of the same kind. This part proves the closed form with a reflection argument, checks four families of objects against it by brute force, and computes a Catalan number with a million in its index.

In this part:

  • The recurrence, from the root split
  • The closed form, proved by reflection
  • Four families, counted one by one
  • How fast they grow, and where exact values overflow

1 runnable example (Python) · about 2 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Lucas's theorem when n is at least p, and a practice set Pro: opens with a Pro, Premium or Ultimate pass

Factorial tables stop working once n reaches the prime, and some problems use small primes or astronomically large n. This part proves when Lucas’s theorem applies, checks it against exact values, computes C(10¹⁸, 10⁷) modulo a prime near 10⁶, and ends with a practice set.

In this part:

  • Lucas’s theorem digit by digit, with a proof
  • Edge cases worth a test
  • Practice set

1 runnable example (Python) · about 2 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Interview questions

  1. Fresher · Warm-up

    What does C(n, k) count, and why is C(n, k) equal to C(n, n − k)?

    C(n, k) counts the subsets of size k of a set of n distinct items, the ways to choose k of them when the order does not matter, and equals n! / (k! × (n − k)!). Choosing which k items to keep is the same decision as choosing which n − k items to leave out, so every choice of k matches exactly one choice of n − k and the two counts are equal: keeping 3 of 5 letters and leaving out 2 of them both give 10 ways. The symmetry also saves work, since C(1000, 997) is C(1000, 3) = 166,167,000, and C(n, k) is 0 when k is negative or larger than n.

  2. Mid

    Answer 10⁶ queries of C(n, k) modulo 10⁹ + 7 with n up to 10⁶. Give the design, its memory and its cost per query, and say what changes if the modulus is 10⁹, which is not prime.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. Fresher to mid

    How many structurally different binary search trees hold the keys 1 to n, and how would you list all of them for n = 15? Estimate the size of the output before writing any code.

    Model answer, Pro. First step and follow-ups

  4. Mid to senior

    A robot moves on a grid from (0, 0) to (n, n) with right and up steps and may never go above the diagonal y = x. How many paths are there for n = 10⁶, modulo 10⁹ + 7, and how do you show that the formula is right?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • C(n, k) = n! / (k! (n − k)!) = C(n, n − k), and it is 0 when k < 0 or k > n.
  • Modulo a prime p > n, build fact and inv_fact once, with a single Fermat inverse, and answer each query with two multiplications.
  • Pascal’s rule uses additions only, so it works for any modulus, at O(n²) for all rows up to n.
  • Catalan numbers, C(2n, n) / (n + 1), count BST shapes, balanced brackets, triangulations and paths that stay below a diagonal.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.