JavaScript Integer Overflow: The Coding Interview Reference

June 19, 20268 min read
dsaalgorithmsinterview-prepjavascript
JavaScript Integer Overflow: The Coding Interview Reference
TL;DR
  • Number.MAX_SAFE_INTEGER is 2^53 − 1: JavaScript integers are exact below it and silently imprecise above it, with no exception or crash.
  • Bitwise operators coerce to 32-bit signed integers: &, |, ^, ~, <<, >> all truncate operands before operating, making left-shift and bitwise-complement produce unexpected results on large values.
  • The >>> unsigned right shift converts to 32-bit unsigned and always returns a non-negative result; (lo + hi) >>> 1 is the idiomatic safe midpoint for binary search.
  • JavaScript binary search does not overflow on realistic inputs because array indices stay well below 2^53, but >>> 1 signals platform awareness to the interviewer.
  • Multiplication before mod: if both operands can reach 10^9, their product exceeds MAX_SAFE_INTEGER — use BigInt(a) * BigInt(b) % BigInt(MOD) for that step only.
  • Avoid BigInt by default in interviews: it cannot mix with Number, breaks Math.*, and throws on JSON.stringify; prefer string arithmetic or modular restructuring instead.

JavaScript gives you one number type and asks you to trust it. For most problems that trust is fine. But somewhere between a binary search, a bitmask, and a modular arithmetic problem, it breaks quietly, without an exception, without a wrong-answer runtime error. JavaScript integer overflow is silent: the numbers just stop being correct.

You pass your test cases. The interviewer watches. Your code is wrong.

You Have One Number Type. It Lies Above 2^53.

Every numeric literal in JavaScript, whether 1, 1.5, or 9007199254740991, is a 64-bit IEEE 754 double-precision float. There is no int, no long, no float. Just Number.

That design works because a 64-bit double can represent integers exactly up to a point. Above that point, the mantissa runs out of bits and the representation starts skipping integers. The canonical proof:

console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991 console.log(Number.MAX_SAFE_INTEGER + 1); // 9007199254740992 console.log(Number.MAX_SAFE_INTEGER + 2); // 9007199254740992 ← same console.log(Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2); // true

This is the most common JavaScript number gotcha in interviews: precision loss above 2^53 is silent. No exception, no NaN, just a wrong value that looks plausible. Your code compiles. It runs. It returns the wrong answer. You declare it done.

In every other major interview language (Java, C++, Python 3 except Python auto-promotes), you'd get a wrapped-around value or an explicit error. In JavaScript, you get a lie.

The Safe Zone: What MAX_SAFE_INTEGER Means

Number.MAX_SAFE_INTEGER is 2^53 - 1, which equals 9,007,199,254,740,991. Its partner is Number.MIN_SAFE_INTEGER, which equals -(2^53 - 1).

"Safe" means two things at once: the integer is represented exactly, and no other integer maps to the same bit pattern. Both conditions break above the boundary.

Number.MAX_SAFE_INTEGER // 9007199254740991 Number.MIN_SAFE_INTEGER // -9007199254740991 Number.isSafeInteger(9007199254740991) // true Number.isSafeInteger(9007199254740992) // false

For most LeetCode problems, values stay well inside this range. Array lengths, node values, and graph weights are typically constrained to 10^9 or less, which is comfortably below the 9 * 10^15 ceiling. But there are two areas where you will hit the wall: large combinatorial outputs and modular arithmetic with multiplication.

The JavaScript is the attractive language at the games while COBOL, Python, and Java sit next to it

JavaScript: always the most popular one in the room, until you actually need integer precision.

Bitwise Operations Live in 32-Bit Land

Here is the part that surprises even experienced JavaScript developers. Every bitwise operator in JavaScript, including &, |, ^, ~, <<, and >>, silently converts its operands to a 32-bit signed integer before operating. The result is then converted back to a 64-bit float.

The 32-bit signed range is -2,147,483,648 to 2,147,483,647. Two immediate consequences:

// Trap 1: left-shifting into the sign bit 1 << 31 // -2147483648 (not 2147483648) // Trap 2: left-shifting wraps at 32 1 << 32 // 1 (not 4294967296) 1 << 33 // 2 // Trap 3: large values get truncated 0xFFFFFFFF | 0 // -1 (not 4294967295)

The ~ operator has its own trap: ~x equals -(x + 1) in two's complement. This breaks the old ~indexOf() check for inclusion, and it bites popcount implementations that assume a positive result.

The unsigned right shift >>> is the odd one out. It converts to a 32-bit unsigned integer before shifting, so zeros are always inserted from the left, and the result is always non-negative. >>> 0 is the canonical idiom to interpret a 32-bit pattern as an unsigned integer.

(-1) >>> 0 // 4294967295 (all 32 bits set, treated as unsigned)

Binary Search: The One Place Overflow Genuinely Bites You

In Java and C++, the classic midpoint bug is:

int mid = (lo + hi) / 2; // overflows if lo + hi > Integer.MAX_VALUE

In JavaScript this specific bug does not occur in practice. Since lo and hi are array indices bounded by 2^32, their sum stays well inside the safe integer range (2^53). Math.floor((lo + hi) / 2) is correct for all realistic interview inputs.

But the idiomatic JavaScript pattern you will see in reference solutions is:

const mid = (lo + hi) >>> 1;

This works because >>> first converts lo + hi to a 32-bit unsigned integer, then shifts right by one, which is unsigned division by two. It has two advantages: it avoids the float-to-int conversion that Math.floor requires, and it is provably correct even when lo + hi would exceed 32 bits (though as noted, that cannot happen with array indices). Use (lo + hi) >>> 1 for binary search midpoints. It is the clearest signal to an interviewer that you know the platform.

Tweet showing someone coding if (num == 0), if (num == 1) and the reply recommending the is-even npm package instead

The npm ecosystem has a package for checking whether a number equals two. JavaScript's relationship with numbers is a journey.

When to Reach for BigInt

BigInt, available since ES2020, gives you arbitrary-precision integers. The syntax is a trailing n:

const big = 9007199254740992n; // one past MAX_SAFE_INTEGER console.log(big + 1n); // 9007199254740993n (correct)

Three rules that bite newcomers:

1. You cannot mix BigInt and Number.

9n + 1 // TypeError: Cannot mix BigInt and other types 9n + BigInt(1) // 10n (correct)

2. The Math API does not work with BigInt.

Math.max(1n, 2n) // TypeError

3. JSON.stringify throws on BigInt values.

In a coding interview you will almost never need BigInt. When a problem involves numbers beyond 2^53, the standard approach is to encode them as strings ("Add Two Numbers", "Multiply Strings") or apply modular arithmetic at every step. BigInt is slower and introduces type-error landmines that are uniquely embarrassing to explain live. Use it only when the problem explicitly requires values outside the safe range and string arithmetic is not an option.

The JavaScript Integer Overflow Trap: Multiplication Before Mod

This is the silent overflow that gets you on dynamic programming problems with the mod 10^9+7 constraint.

Consider two values a and b, each up to 10^9. Their product is up to 10^18, which is above Number.MAX_SAFE_INTEGER (roughly 9 * 10^15). Multiplying first and then modding gives a wrong answer. No error. No warning. Just a number that looks completely reasonable and is completely wrong.

const MOD = 1_000_000_007; // Wrong: a * b overflows safe integer range const result = (a * b) % MOD; // Correct option 1: use BigInt for the multiplication only const result = Number(BigInt(a) * BigInt(b) % BigInt(MOD)); // Correct option 2: restructure to avoid large intermediates // (problem-specific, e.g. matrix exponentiation with modular reduction)

The general rule: if both operands can reach 10^9, their product needs BigInt or a restructured computation. Single-term additions and subtractions are always safe inside the standard interview constraint range.

Sign reading: Theory is when you know everything but nothing works. Practice is when you don't know anything yet everything works. In programming we combine both: nothing works and we don't know why.

JavaScript integer overflow in a nutshell. You ran the code. It passed. You have no idea why it's wrong.

See the integer overflow and underflow guide for how this compares across languages.

Quick Reference

ConstantValue
Number.MAX_SAFE_INTEGER9,007,199,254,740,991 (2^53 - 1)
Number.MIN_SAFE_INTEGER-9,007,199,254,740,991
Number.MAX_VALUE~1.79 × 10^308 (imprecise above 2^53)
Number.POSITIVE_INFINITYresult of overflow past MAX_VALUE
Bitwise operand range-2,147,483,648 to 2,147,483,647 (32-bit signed)
>>> 0 output range0 to 4,294,967,295 (32-bit unsigned)
PatternIdiomatic JS
Safe midpoint(lo + hi) >>> 1
Force signed 32-bitx | 0
Force unsigned 32-bitx >>> 0
Large multiplication mod MNumber(BigInt(a) * BigInt(b) % BigInt(M))
Check safe rangeNumber.isSafeInteger(x)

Practice Under Pressure

Reading about overflow is easy. Catching it live, in the middle of a binary search, when an interviewer is watching, is harder. If you want to drill this in a realistic setting, SpaceComplexity runs voice-based DSA mock interviews with rubric feedback on the exact dimensions that catch these bugs: edge-case reasoning, complexity analysis, and self-testing before you declare done.

The difference between a correct solution and a strong hire is catching the overflow before the interviewer does.

For more JavaScript-specific interview patterns, see JavaScript interview gotchas and the JavaScript for coding interviews reference. For the bit manipulation patterns that depend on 32-bit semantics, see the bit manipulation cheat sheet.


Further Reading