Understand
30-second explanation
Build the interview-ready mental model before comparing implementations.
The factorial-inverse method for nCr mod p assumes p > n. When n can be far larger than p (and p is small), that breaks — fact[n] mod p hits a factor of p and becomes 0, so its inverse doesn't exist. Lucas' theorem rescues this: write n and r in base p as digits (nₖ…n₁n₀) and (rₖ…r₁r₀). Then C(n, r) ≡ ∏ C(nᵢ, rᵢ) (mod p) — the product of the small digit-wise binomials. Each C(nᵢ, rᵢ) has both arguments < p, so the standard factorial+Fermat method (or a tiny table) computes it. If any digit has rᵢ > nᵢ, that factor is 0, so the whole answer is 0.
Interview contextWhat is testedWhere candidates slipHow to stand outExplore interview contextHide interview context
What's being tested
Whether you recognise that the factorial-inverse nCr method requires p > n, why it fails when n ≥ p (fact[n] ≡ 0 mod p has no inverse), and that Lucas' theorem decomposes the coefficient into digit-wise binomials in base p.
Where candidates slip
Applying the factorial method with n ≥ p (division by a non-invertible factorial), forgetting that rᵢ > nᵢ in any digit makes the whole result 0, or using Lucas with a non-prime modulus.
How to stand out
Explain WHY n ≥ p breaks the factorial method, state Lucas as C(n,r) ≡ ∏ C(nᵢ,rᵢ) (mod p), note the 0 short-circuit, and mention CRT-over-primes for composite moduli.
Choose
Compare the available approaches
Use constraints and trade-offs to select the route you can defend in an interview.
Interactive decision treeInteractiveChoose the right approachFirst decision previewlucas(n, r, p)r == 0 ?YesNoExplore the full treeCollapse the tree
Learn & trace
Build each solution step by step
Move from the core idea to a visual trace, implementation, proof, and edge cases.
Approach 1 of 2
Factorial + Fermat Inverse (Baseline — fails when n ≥ p)
1Hint 1Explore hintHide hint
The standard nCr mod p uses fact[n]·invFact[r]·invFact[n−r] with Fermat inverses. What two things go wrong when n is 10¹⁸ and p is small?
2Hint 2Explore hintHide hint
First, you can't allocate a factorial table of length 10¹⁸. Second — subtler — once n ≥ p, n! is divisible by p, so fact[n] ≡ 0 mod p and has NO inverse.
3Hint 3Explore hintHide hint
So the method is both infeasible and mathematically invalid here. You need a formula that works digit-by-digit in base p, keeping every factorial argument below p.
A · Core idea
The textbook method computes C(n, r) mod p = fact[n]·invFact[r]·invFact[n−r] mod p, with inverse-factorials from Fermat's little theorem. It is the correct, fast answer when `p > n`. The point of this baseline is to see exactly why it fails outside that regime.
Failure 1 — the table can't exist. With n up to 10¹⁸, a factorial array of length n+1 is impossible to allocate. There isn't enough memory in the universe for 10¹⁸ entries.
Failure 2 — the factorial has no inverse. Even setting memory aside, the math breaks. Once n ≥ p, the product n! = 1·2·…·n includes the factor p itself, so n! ≡ 0 (mod p). A value of 0 modulo p has no multiplicative inverse — Fermat's a^(p−2) gives 0, not a true inverse. The formula fact[n]·invFact[r]·… would multiply and 'invert' zeros and produce garbage. This is the deep reason the method is restricted to p > n, where no factorial argument ever reaches p.
What we actually need. A method whose every factorial argument stays below `p`, so no factor of p ever enters and every inverse exists. Lucas' theorem delivers exactly that by working on the base-p digits of n and r, each of which is in [0, p).
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
n = 10, r = 3, p = 3build fact
fact[3] = 6 ≡ 0 (mod 3)
already a zeromethod invalid for n ≥ pC · Code with teaching comments
Reference implementation preview// Valid ONLY when p > n. Shown to expose the failure mode for n >= p. public long nCrFactorial(int n, int r, long p, long[] fact, long[] invFact) { if (r < 0 || r > n) return 0; // If n >= p, fact[n] % p == 0 (no inverse) and the table can't even be built for huge n. return fact[n] * invFact[r] % p * invFact[n - r] % p;Explore the full codeHide the full code
D · Why it works and complexityO(n) precompute, O(1) query (when valid) timeO(n) spaceOpen the invariant and proofExplore the proofHide the proof
This baseline is the right tool only when p > n. The instant n ≥ p it fails twice over — the table is unbuildable and fact[n] ≡ 0 mod p is non-invertible. Diagnosing those two failures is what tells you to reach for Lucas, which keeps every factorial argument under p.
ComplexityO(n) precompute, O(1) query (when valid) time, O(n) space — Precomputing fact[0..n] is O(n) time and space, with O(1) per query — but only when p > n. When n ≥ p the table is too large to build and fact[n] is 0 mod p (not invertible), so the approach simply doesn't work.
E · Edge cases and pitfalls3Using it when n ≥ p — fact[n] ≡ 0 mod p is not invertible.Explore edge casesHide edge cases
Pitfalls
- Using it when n ≥ p — fact[n] ≡ 0 mod p is not invertible.
- Trying to allocate a factorial table for n = 10¹⁸.
- Assuming Fermat gives an inverse of 0 — it returns 0, not a real inverse.
Approach 2 of 2 · Optimal
Lucas' Theorem (Optimal)
1Hint 1Explore hintHide hint
Write n and r in base p. Lucas' theorem says C(n, r) ≡ ∏ C(nᵢ, rᵢ) (mod p) over the base-p digits. Each digit is < p — what does that buy you?
2Hint 2Explore hintHide hint
Precompute factorials and inverse-factorials only up to p−1 (one Fermat inverse + backward sweep). Then each digit's C(nᵢ, rᵢ) is O(1).
3Hint 3Explore hintHide hint
Peel digits with n % p and r % p, multiply the digit binomials, recurse on n / p and r / p. If any rᵢ > nᵢ the digit binomial is 0, so the whole answer is 0 — short-circuit.
A · Core idea
Lucas' theorem reduces a binomial coefficient modulo a prime to a product of much smaller binomials — the binomials of the base-p digits.
The statement. Write n and r in base p:
n = nₖ·pᵏ + … + n₁·p + n₀, r = rₖ·pᵏ + … + r₁·p + r₀, with each digit in [0, p). Then
C(n, r) ≡ C(nₖ, rₖ) · … · C(n₁, r₁) · C(n₀, r₀) (mod p).
Every factor C(nᵢ, rᵢ) has both arguments below p, so no factorial argument ever reaches p — the inverse problem that killed the baseline never arises.
Why each digit binomial is cheap. Since nᵢ, rᵢ < p, precompute fact[0..p−1] and inverse-factorials up to p−1 once (one Fermat inverse for fact[p−1], then the backward sweep), and C(nᵢ, rᵢ) = fact[nᵢ]·invFact[rᵢ]·invFact[nᵢ−rᵢ] mod p is O(1). The setup is O(p), not O(n) — that's what makes huge n feasible.
Why a 0 digit zeroes everything. If for some position rᵢ > nᵢ, then C(nᵢ, rᵢ) = 0 (you can't choose more than you have), and a single zero factor makes the entire product 0 mod p. So the algorithm can short-circuit and return 0 the moment it sees rᵢ > nᵢ. This is why coefficients like C(10, 3) mod 3 come out 0: a base-3 digit of 3 exceeds the matching digit of 10.
The recursion. Peel the lowest digits with nᵢ = n % p, rᵢ = r % p; multiply the running product by C(nᵢ, rᵢ); recurse (or loop) on n / p, r / p. Stop when r becomes 0 — all higher digits of r are 0, and C(anything, 0) = 1, so they contribute nothing. There are only O(log_p n) digits, so a query is fast even for n = 10¹⁸.
Why p must be prime. Lucas' theorem is a statement modulo a prime; the proof uses (1 + x)^p ≡ 1 + x^p (mod p), the Freshman's-dream identity that holds only for prime p. For a prime power p^a there's Granville's generalisation; for a general composite, factor it, apply Lucas modulo each prime (power), and recombine with the Chinese Remainder Theorem.
Overflow safety. Digit binomials are products of values below p; with p up to ~10⁶ the products fit comfortably in a 64-bit long as long as you reduce mod p after each multiply. Python integers are unbounded, so the modulus is for correctness and speed, not overflow.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
n = 10, r = 3, p = 3Array state
digits
n=(1,0,1)₃, r=(0,1,0)₃ (low→high)
0C · Code with teaching comments
D · Why it works and complexityO(p + log_p(n)) — O(p) precompute, O(log_p n) digits per query timeO(p) spaceOpen the invariant and proofExplore the proofHide the proof
Lucas turns 'huge n, small prime p' into 'a handful of tiny binomials' by reading n and r in base p. The two facts that make it practical: every digit binomial has arguments below p (so a length-p factorial table suffices), and any digit with rᵢ > nᵢ collapses the whole product to 0. It's the natural partner of nCr-mod-p: use factorials+Fermat when p > n, Lucas when p ≤ n, and CRT to glue several primes for composite moduli.
ComplexityO(p + log_p(n)) — O(p) precompute, O(log_p n) digits per query time, O(p) space — Precomputing factorials up to p−1 is O(p) time and space (plus one O(log p) Fermat inverse). A query peels O(log_p n) base-p digits, each costing O(1) via the small tables. So per-query time is O(log_p n) after the O(p) setup.
E · Edge cases and pitfalls9n < p and r < p — Single digit: Lucas reduces to the ordinary C(n, r) mod p.Explore edge casesHide edge cases
Edge cases handled
n < p and r < pSingle digit: Lucas reduces to the ordinary C(n, r) mod p.r = 0All digits of r are 0, every C(nᵢ, 0) = 1 → result 1.r > nSome digit has rᵢ > nᵢ (or higher r digits exceed n's) → 0.p = 2Lucas in base 2 gives Kummer's parity: C(n,r) is odd iff r's bits are a subset of n's.
Pitfalls
- Using Lucas with a non-prime modulus — it only holds mod a prime; use Granville + CRT otherwise.
- Forgetting the rᵢ > nᵢ short-circuit — must contribute a 0 factor.
- Precomputing factorials up to n instead of p−1 — defeats the purpose for huge n.
- Stopping the loop too early — continue while either n or r still has digits.
- Letting digit products overflow in Java — reduce mod p after each multiply, use long.
Practice
Try the solution yourself
Edit the optimal implementation, test another input, and observe the result.
Editable code previewimport java.util.*; class Lucas { private final long p; private final long[] fact, invFact;Explore the playgroundClose the playground
Avoid mistakes
Recognize the interview traps
Understand why common attempts fail before reviewing the corrected implementation.
1Factorial method with n ≥ p
When n ≥ p, n! contains a factor of p, so fact[n] ≡ 0 (mod p) and has no inverse — Fermat returns 0, not a real inverse. The result is meaningless. Use Lucas, which keeps every factorial argument below p.
Explore the code comparisonHide the code comparison
Factorial method with n ≥ p
When n ≥ p, n! contains a factor of p, so fact[n] ≡ 0 (mod p) and has no inverse — Fermat returns 0, not a real inverse. The result is meaningless. Use Lucas, which keeps every factorial argument below p.
Don't write this
# n = 10, p = 3:
return fact[n] * inv_fact[r] % p * inv_fact[n - r] % p # fact[10] % 3 == 0Write this instead
lucas = Lucas(p)
return lucas.n_cr(n, r) # digit-wise binomials, every argument < p2Missing the zero-digit short-circuit
If any base-p digit of r exceeds the matching digit of n, C(nᵢ, rᵢ) = 0 and the full product must be 0. Skipping that check (e.g. clamping) gives a wrong nonzero answer.
Explore the code comparisonHide the code comparison
Missing the zero-digit short-circuit
If any base-p digit of r exceeds the matching digit of n, C(nᵢ, rᵢ) = 0 and the full product must be 0. Skipping that check (e.g. clamping) gives a wrong nonzero answer.
Don't write this
long ni = n % p, ri = r % p;
result = result * smallNcr(ni, ri) % p; // smallNcr must return 0 when ri>niWrite this instead
long ni = n % p, ri = r % p;
if (ri > ni) return 0; // explicit short-circuit
result = result * smallNcr(ni, ri) % p;3Lucas on a composite modulus
Lucas' theorem holds modulo a prime (its proof uses (1+x)^p ≡ 1+x^p mod p). For a composite m, factor m into prime powers, apply Lucas/Granville per factor, then CRT-combine.
Explore the code comparisonHide the code comparison
Lucas on a composite modulus
Lucas' theorem holds modulo a prime (its proof uses (1+x)^p ≡ 1+x^p mod p). For a composite m, factor m into prime powers, apply Lucas/Granville per factor, then CRT-combine.
Don't write this
lucas = Lucas(12) # 12 is not prime — invalidWrite this instead
# factor 12 = 4 * 3; compute C(n,r) mod 4 (Granville) and mod 3 (Lucas),
# then combine with CRT to get C(n,r) mod 12Revise
Carry the pattern into the interview
Pattern to remember
Reduce a huge nCr mod a small prime to a product of digit-wise binomials in base p
Code pattern to memorize
write n = (n_k ... n_1 n_0) and r = (r_k ... r_1 r_0) in base p
C(n, r) mod p = product over i of C(n_i, r_i) mod p
if any r_i > n_i: the product (and the answer) is 0
each C(n_i, r_i) with n_i, r_i < p via factorials + Fermat inverseRules to remember
- 1Use Lucas when n can exceed p (p small). The plain factorial method needs
p > n; Lucas removes that limit. - 2Process
nandrdigit by digit in base p (repeatedly take% pthen/= p). The recursion bottoms out when both reach 0. - 3If at any digit
rᵢ > nᵢ, thenC(nᵢ, rᵢ) = 0, which forces the whole product to 0 — short-circuit and return 0. - 4Each per-digit
C(nᵢ, rᵢ)hasnᵢ, rᵢ < p, so precompute factorials and inverse-factorials up to p−1 once (Fermat inverse) and answer each digit in O(1). - 5The modulus must be prime (Lucas is a statement mod a prime). For prime powers use the generalised Granville/Andrew theorem; combine several primes with CRT. Use long in Java for digit products.
Use this pattern when
- Compute `C(n, r) mod p` where `n` is astronomically large (up to 10¹⁸) and `p` is a **small prime**
- Counting problems mod a small prime where n far exceeds the modulus
- Combined with CRT to get nCr modulo a composite by working over its prime factors
- Anywhere the plain factorial-inverse method fails because `p ≤ n`
Choose another pattern when
- `p > n` (e.g. p = 1e9+7 and n ≤ 1e6) → the plain factorial + Fermat method is simpler and direct
- The modulus is **not prime** → Lucas doesn't apply; use Granville's generalisation or CRT over prime factors
- You need an **exact** (non-modular) binomial → big integers / Pascal, no Lucas
- n is small enough to build a Pascal triangle mod p → just do that
Interview takeaway
Lucas' theorem says a binomial coefficient modulo a prime is the product of the binomials of the base-p digits. It's the tool for nCr mod p exactly when the factorial method dies — n bigger than p. Precompute factorials and inverses up to p−1 once, peel off base-p digits of n and r together, multiply the per-digit C(nᵢ, rᵢ), and short-circuit to 0 the moment a digit has rᵢ > nᵢ. Pair it with CRT to handle composite moduli.
Continue
Transfer the pattern to another problem
Use a direct variation or pattern sibling to prove the idea is reusable.
Transferable patternLucas' theorem — nCr modulo a small prime when n exceeds p. Recognise it whenever the factorial method's p > n precondition fails. Decompose into base-p digit binomials, precompute factorials only up to p−1, and short-circuit on a zero digit. Pair with CRT for composite moduli; it's the large-n counterpart of nCr mod p.
Practice queueDirect variations and pattern siblings
Direct follow-ups
- nCr mod pThe p > n companion — factorials + Fermat; Lucas reuses the same per-digit binomial.
- Chinese Remainder TheoremCombine Lucas results modulo several primes to get nCr modulo a composite.
- Modular ExponentiationThe Fermat inverse fact[p−1]^(p−2) used to seed the inverse-factorial table is one modpow.
- Kummer's TheoremComing soonCounts the power of p dividing C(n, r) via base-p carries — the companion to Lucas mod p.