Node.js
computation
binomial coefficient
n choose k
JavaScript efficiency

Efficient computation of n choose k in Node.js

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Computing n choose k looks harmless until the numbers get large and the obvious factorial formula starts overflowing or losing precision. In Node.js, the practical approach is to avoid full factorials, exploit the symmetry of the binomial coefficient, and use BigInt whenever exact integer results matter. That combination gives you a helper that is fast enough for most workloads and still mathematically trustworthy.

Avoid the Factorial Formula

The textbook identity

n choose k = n! / (k! * (n-k)!)

is mathematically correct, but it is a poor implementation strategy. Full factorials become enormous very quickly, and JavaScript Number cannot represent all large integers exactly. Even if the final answer would fit in theory, the intermediate factorials can already be far beyond the safe integer range.

The usual replacement is a multiplicative loop that builds the result gradually.

Use Symmetry to Reduce the Work

The first optimization is simple:

C(n, k) = C(n, n-k)

That means you only need to loop min(k, n-k) times.

javascript
1function nChooseK(n, k) {
2  if (!Number.isInteger(n) || !Number.isInteger(k) || n < 0 || k < 0) {
3    throw new Error("n and k must be non-negative integers");
4  }
5
6  if (k > n) return 0n;
7  if (k === 0 || k === n) return 1n;
8
9  const kk = Math.min(k, n - k);
10  let result = 1n;
11
12  for (let i = 1; i <= kk; i += 1) {
13    result = (result * BigInt(n - kk + i)) / BigInt(i);
14  }
15
16  return result;
17}
18
19console.log(nChooseK(52, 5).toString());

This is efficient because it does not build giant factorials and only performs the minimum required number of loop iterations.

Why BigInt Matters in Node.js

For small values, Number may seem fine. The problem is that JavaScript integers become unsafe past Number.MAX_SAFE_INTEGER. Binomial coefficients exceed that threshold sooner than many developers expect.

That makes BigInt the right default when the result must be exact. The main rule to remember is that BigInt and Number do not mix automatically in arithmetic. If one side is BigInt, convert the other side deliberately.

This matters especially in API code, where a seemingly correct combinatorics helper can quietly return rounded values if it was built on floating-point math.

Validate Edge Cases Explicitly

A helper like this should state its behavior clearly:

  • 'k > n returns zero.'
  • 'k = 0 or k = n returns one.'
  • Negative or non-integer inputs should fail fast.

Those cases are not just defensive programming. They define the contract of the function and keep the behavior stable when the helper is reused elsewhere.

If the function is part of a service, consider whether the return type should always be a string at the API boundary, since JSON does not natively serialize BigInt.

Add Caching Only When Repetition Exists

Many workloads compute the same coefficients repeatedly. In that case, memoization can help. It is not always necessary, but it is useful when the function is called from dynamic programming, probability code, or combinatorial search that revisits the same pairs again and again.

javascript
1const cache = new Map();
2
3function cachedNChooseK(n, k) {
4  const key = `${n}:${k}`;
5  if (!cache.has(key)) {
6    cache.set(key, nChooseK(n, k));
7  }
8  return cache.get(key);
9}

The important point is to add caching because of repeated query patterns, not because it sounds like a general optimization. If every call uses a new pair, the cache only adds memory overhead. Measure the traffic pattern before keeping a long-lived cache in a server process.

Modular Arithmetic Is a Different Problem

Some use cases do not want the exact value at all. They want n choose k mod p, often in competitive programming or large-scale counting problems. That is a different API and should usually live in a separate function rather than being mixed into the exact version.

javascript
1function modPow(base, exp, mod) {
2  let b = BigInt(base) % mod;
3  let e = BigInt(exp);
4  let result = 1n;
5
6  while (e > 0n) {
7    if (e & 1n) result = (result * b) % mod;
8    b = (b * b) % mod;
9    e >>= 1n;
10  }
11
12  return result;
13}

Keeping exact arithmetic and modular arithmetic separate avoids confusion about what the caller is actually getting back.

Common Pitfalls

The most common mistake is using factorials with Number and assuming the results stay exact. Another is forgetting the symmetry optimization and doing twice as much work as necessary. Teams also run into trouble by mixing BigInt and Number carelessly, or by returning huge exact values through JSON without converting them to strings. A quieter mistake is benchmarking only tiny examples and never noticing that exact combinatorics can become a throughput issue under real concurrency.

Summary

  • Avoid the factorial formula for practical Node.js implementations.
  • Use symmetry so the loop runs only min(k, n-k) steps.
  • Prefer BigInt when exact results matter.
  • Define edge-case behavior clearly instead of leaving it implicit.
  • Treat exact combinatorics and modular combinatorics as separate APIs.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.