BigInt Tricks for Large Integers

Switch to BigInt When Values Exceed 2^53 — Exact Arithmetic at the Cost of Speed

BigInt Tricks for Large Integers

JavaScript's Number type loses precision above 2^53, making BigInt essential for contest-style modular arithmetic and cryptographic computations — but its performance cost means you should reach for it deliberately, not reflexively.

5 min read Level 3/5 #dsa#bigint#modular-arithmetic
What you'll learn
  • Identify when Number precision breaks down and BigInt is necessary
  • Implement modular exponentiation with BigInt for large moduli
  • Measure and reason about the performance gap between BigInt and Number

JavaScript’s Number type is a 64-bit IEEE 754 double. It can represent integers exactly up to 2^53 - 1 (about 9 quadrillion), stored in Number.MAX_SAFE_INTEGER. Beyond that threshold, consecutive integers can no longer be distinguished and arithmetic silently produces wrong answers. BigInt is the language’s built-in remedy: an arbitrary-precision integer type that never loses precision, at the cost of raw speed.

When to Switch

SituationUse
Intermediate products stay below 2^53Number
Modular arithmetic with mod < 2^26Number (products stay below 2^52)
Modular arithmetic with mod >= 2^26BigInt
Bit manipulation on values <= 32 bitsNumber (bitwise ops, int32)
Bit manipulation on values > 32 bitsBigInt
Counting, indexing, lengthsNumber
Cryptographic exponentiation, factorials, combinationsBigInt
console.log(Number.MAX_SAFE_INTEGER);          // 9007199254740991 (2^53 - 1)
console.log(9007199254740992 === 9007199254740993); // true — precision lost!

const big = 9007199254740992n;
console.log(big === 9007199254740993n);        // false — BigInt is exact

Modular Exponentiation

Computing base^exp mod m for large exp requires fast exponentiation (squaring) and BigInt to avoid overflow in the intermediate product base * base.

function modPow(base, exp, mod) {
  // All inputs must be BigInt
  base = BigInt(base);
  exp  = BigInt(exp);
  mod  = BigInt(mod);

  let result = 1n;
  base %= mod;

  while (exp > 0n) {
    if (exp & 1n) {          // if current bit of exp is set
      result = result * base % mod;
    }
    exp >>= 1n;              // shift right by 1 (BigInt shift)
    base = base * base % mod;
  }

  return result;
}

// 2^100 mod 1_000_000_007
console.log(modPow(2, 100, 1_000_000_007n)); // 976371285n

The key difference from a Number version: base * base can easily exceed 2^53 when base is close to mod, so BigInt multiplication is mandatory.

Performance: Number vs BigInt

BigInt operations are typically 5-10x slower than equivalent Number operations on modern V8, and the gap widens for very large values because BigInt uses heap-allocated multi-word integers.

const N = 1_000_000;

console.time('number');
let n = 1;
for (let i = 0; i < N; i++) n = (n * 2) % 1_000_000_007;
console.timeEnd('number'); // ~5 ms

console.time('bigint');
let b = 1n;
const MOD = 1_000_000_007n;
for (let i = 0; i < N; i++) b = b * 2n % MOD;
console.timeEnd('bigint'); // ~25-40 ms

For competitive-programming problems where the answer fits in a safe integer but intermediate products might overflow, one trick is to split the multiplication: compute Math.floor(a * b / mod) via BigInt for the quotient only, keeping everything else in Number. In most cases, simply using BigInt for the entire computation is cleaner and fast enough.

Mixing BigInt and Number

You cannot mix the two types in arithmetic without an explicit conversion, but conversions are cheap when you need them.

const result = modPow(2, 100, 1_000_000_007n);

// Convert BigInt result back to Number (safe here because result < 2^53)
const asNumber = Number(result);
console.log(asNumber); // 976371285

Up Next

With data structures and bit manipulation covered, the next section explores JavaScript’s concurrency model: the event loop and asynchronous execution.

The Event Loop →