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.
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
| Situation | Use |
|---|---|
| Intermediate products stay below 2^53 | Number |
Modular arithmetic with mod < 2^26 | Number (products stay below 2^52) |
Modular arithmetic with mod >= 2^26 | BigInt |
Bit manipulation on values <= 32 bits | Number (bitwise ops, int32) |
Bit manipulation on values > 32 bits | BigInt |
| Counting, indexing, lengths | Number |
| Cryptographic exponentiation, factorials, combinations | BigInt |
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 →