Combinatorics nCr Modulo Prime Explained — Pascal Triangle, Factorial Inverse, Lucas Theorem [LC 62, Google, Meta]

Sanjeev SharmaSanjeev Sharma
8 min read

Advertisement

Algorithm Statement

Compute the binomial coefficient (nr)=n!r!(nr)!\binom{n}{r} = \dfrac{n!}{r!(n-r)!}, often modulo a prime pp. Three regimes:

  1. Small nn (up to a few thousand): Pascal's triangle in O(n2)O(n^2).
  2. Medium nn (up to 10610^6) with prime modulus: precompute factorials and inverse factorials in O(n)O(n), then answer each query in O(1)O(1).
  3. Huge nn (up to 101810^{18}) with small prime modulus: Lucas's theorem reduces to base-pp digits in O(p+logpn)O(p + \log_p n).

Constraints typical in interviews:

  • 1n,r1061 \le n, r \le 10^6 and rnr \le n.
  • Modulus is usually p=109+7p = 10^9 + 7 (prime).

Example:

n = 5, r = 2, p = 10^9 + 7
C(5, 2) = 5! / (2! * 3!) = 120 / 12 = 10
Output: 10

Why This Problem Matters

Counting problems dominate the medium and hard tiers of every interview platform. LC 62 Unique Paths asks for (m+n2m1)\binom{m+n-2}{m-1}. LC 1922 Count Good Numbers asks for 4(n+1)/25n/24^{(n+1)/2} \cdot 5^{n/2} but extends to factorial-based variants. LC 1359 Count All Valid Pickup and Delivery Options is the central binomial coefficient times factorials. Google and Meta interviewers love these because they reward candidates who recognise the closed form, then compute it correctly under modular arithmetic without overflow.

The mistake I see most often: candidates know the formula but compute factorials naively, forget modular inverse, or use Fermat with a composite modulus. This blog cleans that up.

The Core Insight

Three approaches scale differently:

Pascal's triangle. Use the recurrence (nr)=(n1r1)+(n1r)\binom{n}{r} = \binom{n-1}{r-1} + \binom{n-1}{r} with base cases (n0)=(nn)=1\binom{n}{0} = \binom{n}{n} = 1. Build a 2D DP table in O(n2)O(n^2) time and O(n2)O(n^2) or O(n)O(n) space. Works for any modulus, prime or not.

Factorial precomputation. When the modulus pp is prime, compute fact[i] for iNi \le N in O(N)O(N). Then (nr)=n!(r!)1((nr)!)1modp\binom{n}{r} = n! \cdot (r!)^{-1} \cdot ((n-r)!)^{-1} \bmod p. The two inverse factorials come from Fermat's little theorem: (k!)1(k!)p2(modp)(k!)^{-1} \equiv (k!)^{p-2} \pmod{p}. A clever trick computes all inverse factorials in O(N)O(N) by inverting only N!N! and walking backwards: inv_fact[i] = inv_fact[i+1] * (i+1) mod p.

Lucas's theorem. For n,rn, r up to 101810^{18} with a small prime pp, write nn and rr in base pp:

n=nkpk+nk1pk1++n0,r=rkpk++r0.n = n_k p^k + n_{k-1} p^{k-1} + \dots + n_0, \qquad r = r_k p^k + \dots + r_0.

Then

(nr)i=0k(niri)(modp).\binom{n}{r} \equiv \prod_{i=0}^{k} \binom{n_i}{r_i} \pmod{p}.

Each (niri)\binom{n_i}{r_i} has ni,ri<pn_i, r_i < p, computable directly. Total time O(p+logpn)O(p + \log_p n).

Why the inverse-factorial trick works. From (i+1)!=(i+1)i!(i+1)! = (i+1) \cdot i! we get (i!)1=(i+1)((i+1)!)1(i!)^{-1} = (i+1) \cdot ((i+1)!)^{-1}. Compute the largest inverse with one power call, then everything else with NN multiplications.

Visual Dry Run

Compute (62)mod(109+7)\binom{6}{2} \bmod (10^9 + 7) using factorial precomputation.

fact[0..6] = [1, 1, 2, 6, 24, 120, 720]
 
inv_fact[6] = power(720, MOD - 2, MOD)  → some big value F6
inv_fact[5] = inv_fact[6] * 6
inv_fact[4] = inv_fact[5] * 5
inv_fact[3] = inv_fact[4] * 4
inv_fact[2] = inv_fact[3] * 3
 
C(6, 2) = fact[6] * inv_fact[2] * inv_fact[4] mod p
        = 720 * (1/2) * (1/24) mod p
        = 720 / 48 = 15

Verify with Pascal: (62)=(51)+(52)=5+10=15\binom{6}{2} = \binom{5}{1} + \binom{5}{2} = 5 + 10 = 15. Correct.

For Lucas, compute (1000300)mod7\binom{1000}{300} \bmod 7. Base 7: 1000=262671000 = 2626_7 and 300=06067300 = 0606_7.

Digits:  n = [2, 6, 2, 6]   r = [0, 6, 0, 6]
C(2,0) * C(6,6) * C(2,0) * C(6,6)
  = 1 * 1 * 1 * 1 = 1
 
So C(1000, 300) mod 7 = 1.

Solution (Optimal)

Python — Factorial Precomputation

MOD = 10**9 + 7
N = 10**6 + 5
 
fact = [1] * N
inv_fact = [1] * N
 
def precompute():
    for i in range(1, N):
        fact[i] = fact[i - 1] * i % MOD
    inv_fact[N - 1] = pow(fact[N - 1], MOD - 2, MOD)
    for i in range(N - 2, -1, -1):
        inv_fact[i] = inv_fact[i + 1] * (i + 1) % MOD
 
def nCr(n: int, r: int) -> int:
    if r < 0 or r > n:
        return 0
    return fact[n] * inv_fact[r] % MOD * inv_fact[n - r] % MOD
 
precompute()

Complexity: O(N)O(N) precompute, O(1)O(1) per query, O(N)O(N) memory.

Python — Lucas's Theorem

def nCr_small(n: int, r: int, p: int) -> int:
    if r > n: return 0
    num = den = 1
    for i in range(r):
        num = num * ((n - i) % p) % p
        den = den * (i + 1) % p
    return num * pow(den, p - 2, p) % p
 
def lucas(n: int, r: int, p: int) -> int:
    result = 1
    while n > 0 or r > 0:
        ni, ri = n % p, r % p
        if ri > ni:
            return 0
        result = result * nCr_small(ni, ri, p) % p
        n //= p
        r //= p
    return result

Complexity: O(p+logpn)O(p + \log_p n) per query.

JavaScript — Pascal's Triangle (small n)

function pascalsTriangle(n) {
    const C = Array.from({ length: n + 1 }, (_, i) =>
        new Array(i + 1).fill(1n)
    );
    for (let i = 2; i <= n; i++) {
        for (let j = 1; j < i; j++) {
            C[i][j] = C[i - 1][j - 1] + C[i - 1][j];
        }
    }
    return C;
}

JavaScript — Factorial Precomputation

const MOD = 1_000_000_007n;
const N = 1_000_005;
const fact = new Array(N).fill(1n);
const invFact = new Array(N).fill(1n);
 
function power(a, n, m) {
    a %= m; let r = 1n;
    while (n > 0n) {
        if (n & 1n) r = r * a % m;
        a = a * a % m; n >>= 1n;
    }
    return r;
}
 
for (let i = 1; i < N; i++) fact[i] = fact[i - 1] * BigInt(i) % MOD;
invFact[N - 1] = power(fact[N - 1], MOD - 2n, MOD);
for (let i = N - 2; i >= 0; i--)
    invFact[i] = invFact[i + 1] * BigInt(i + 1) % MOD;
 
function nCr(n, r) {
    if (r < 0 || r > n) return 0n;
    return fact[n] * invFact[r] % MOD * invFact[n - r] % MOD;
}

Common Mistakes

  1. Computing factorials without modulo. 1000!1000! overflows long before you finish. Apply % MOD after every multiplication.
  2. Dividing instead of multiplying by inverse. Modular arithmetic does not support division. Use the inverse from Fermat or Extended Euclidean.
  3. Wrong base case in Pascal. (n0)=(nn)=1\binom{n}{0} = \binom{n}{n} = 1, and (nr)=0\binom{n}{r} = 0 when r>nr > n or r<0r < 0.
  4. Using Fermat with a composite modulus. Fermat requires pp prime. For composite, fall back to Lucas's generalisation or precompute via Pascal.
  5. Building Pascal's triangle when NN is large. A 106×10610^6 \times 10^6 table is impossible; switch to factorial precomputation.
  6. Lucas with r>nr > n at any digit. The base-pp digit comparison must short-circuit to 0 the moment some ri>nir_i > n_i.
  7. JavaScript Number precision. All factorials must be BigInt; otherwise mod(109+7)\bmod (10^9 + 7) silently corrupts.

Interview Tips

  • Lead with the formula: "I will precompute factorials and inverse factorials in O(N)O(N), then answer each (nr)\binom{n}{r} in O(1)O(1)." This signals fluency.
  • Mention the inverse-factorial trick: only one power call total, the rest is a backward sweep.
  • If nn is large but the modulus is small, pivot to Lucas — never try to factorial 101810^{18}.
  • For combinatorics on grids (paths) and lattices (Catalan), state the closed form before any code.

Follow-up Questions

Q1: Compute (nr)\binom{n}{r} when the modulus is not prime, like m=100m = 100. A: Use Pascal's triangle, or factor mm into prime powers and apply Lucas's generalisation plus the Chinese Remainder Theorem.

Q2: Compute the central binomial coefficient (2nn)\binom{2n}{n} for n=106n = 10^6. A: Same factorial precomputation; the answer is fact[2n]invfact[n]2modp\text{fact}[2n] \cdot \text{invfact}[n]^2 \bmod p.

Q3: Compute Catalan number Cn=1n+1(2nn)C_n = \frac{1}{n+1}\binom{2n}{n}. A: Compute (2nn)\binom{2n}{n} as above, then multiply by the modular inverse of n+1n+1.

Q4: Why is Lucas's theorem useful only for small primes? A: Lucas needs to compute (niri)\binom{n_i}{r_i} for digits ni,ri<pn_i, r_i < p. If pp is too big, the inner factorial precomputation does not fit in memory.

Key Takeaways

  • (nr)\binom{n}{r} has three computational regimes: Pascal for tiny nn, factorial precomputation for n106n \le 10^6, Lucas for nn up to 101810^{18}.
  • Factorial precomputation runs (nr)\binom{n}{r} queries in O(1)O(1) after O(N)O(N) setup, using one power call and a backward sweep for inverse factorials.
  • Fermat's little theorem turns modular division into modular exponentiation: (k!)1(k!)p2(modp)(k!)^{-1} \equiv (k!)^{p-2} \pmod{p}.
  • Lucas's theorem factors the binomial coefficient over the base-pp digits — essential for giant arguments with a small prime modulus.
  • Always guard the boundary cases r &lt; 0, r>nr > n, and (n0)=1\binom{n}{0} = 1 before indexing into the factorial table.
  • For composite moduli, fall back to Pascal's triangle or Lucas's generalisation paired with the Chinese Remainder Theorem.

Advertisement

Sanjeev Sharma

Written by

Sanjeev Sharma

Full Stack Engineer · E-mopro

Related reading