TypeScript Integer Overflow: The Interview Guide
- TypeScript has one numeric type (
number), a 64-bit float with only 53 bits of integer precision — integers aboveNumber.MAX_SAFE_INTEGER(9007199254740991) silently lose precision with no error thrown. - Bitwise operators truncate to 32-bit signed integers, so any value above 2^31 − 1 (~2 billion) silently corrupts; use
Math.floor(x / 2)instead ofx >> 1when magnitude is uncertain. Math.truncvsMath.floormatters for negatives:Math.trunc(-7/2)returns -3 (toward zero, matching Java/C++);Math.floor(-7/2)returns -4 — useMath.truncfor most interview division.- Modular arithmetic has a hidden trap: two values each below
1e9+7can multiply to ~10^18, exceeding the safe range — useBigIntfor safemulmodwhen inputs are near MOD. bigintis the escape hatch for arbitrary precision, but mixing it withnumberin arithmetic throws aTypeError— convert at the problem boundary and stay consistent.- Read constraints first:
n > 10^9or "return mod 10^9+7" are signals to check for TypeScript integer overflow before writing any code.
TypeScript has exactly one numeric type: number. It is always a 64-bit IEEE 754 floating-point value. No ints, no longs, no shorts. This sounds like a blessing until your interview solution returns a wrong answer with total confidence and zero explanation. No NaN. No exception. No stack trace. Just a number that looks plausible and is completely incorrect.
If you want the language-agnostic foundation first, the integer overflow and underflow overview is the right starting point.
One Type, One Trap
Every value you write as number is a double: 1 sign bit, 11 exponent bits, and 52 mantissa bits. The 52-bit mantissa plus one implicit leading bit gives you 53 bits of integer precision. Integers are represented exactly up to 2^53 - 1.
Beyond that boundary, consecutive integers collapse into the same value.
const a = 9007199254740992; // 2^53 const b = 9007199254740993; // 2^53 + 1 console.log(a === b); // true
Yes. true. Two numbers that are clearly different, and TypeScript says they are the same. No warning. No compiler error. Just a quiet lie.
The constants that mark this boundary:
Number.MAX_SAFE_INTEGER // 9007199254740991 (2^53 - 1) Number.MIN_SAFE_INTEGER // -9007199254740991
The Bitwise Trap Nobody Expects
Here is where TypeScript gets a second knife. Bitwise operators do not work on 64-bit floats. Before any bitwise operation executes, JavaScript converts both operands to a 32-bit signed integer. The result comes back as a standard 64-bit number.
The safe ceiling for bitwise operations is 2^31 - 1, not 2^53 - 1.
const safe = 2147483647; // 2^31 - 1 console.log(safe | 0); // 2147483647 (correct) const overflow = 2147483648; // 2^31 console.log(overflow | 0); // -2147483648 (sign bit flipped)
The n | 0 trick for truncating a float to an integer works fine for small values. Once your value passes two billion, it silently corrupts. A graph problem with node counts in the millions, or a DP table with large indices, will produce wrong intermediate values the moment you touch them with &, |, or >>.
The >>> (unsigned right shift) makes it worse. It converts to a 32-bit unsigned integer first, so negative numbers become enormous positive ones:
console.log(-5 >>> 1); // 2147483645 console.log(-5 >> 1); // -3
If you reach for x >>> 1 as a fast divide-by-two, make sure x is non-negative and fits in 31 bits. Any doubt: use Math.floor(x / 2). This is the same 32-bit truncation that makes integer overflow in TypeScript interviews so hard to catch at a glance.
Here is what the safe zones look like side by side:

Floor or Trunc? It Depends on Sign
TypeScript has no integer division operator. You divide with / and then round. The difference between Math.floor and Math.trunc only matters for negative numbers, but that difference shows up constantly in binary search and two-pointer problems.
Math.floor(-7 / 2) // -4 (rounds toward -∞) Math.trunc(-7 / 2) // -3 (truncates toward zero) Math.floor(7 / 2) // 3 Math.trunc(7 / 2) // 3 (same for positive)
Most interview problems expect truncation toward zero, matching integer division in Java or C++. Use Math.trunc when the input can be negative. Use Math.floor when you know the input is non-negative or you specifically want floor behavior (computing a tree node's child index, for instance).
The bitwise idiom n >> 1 is equivalent to Math.floor(n / 2) for non-negative integers and is fine for tree traversals where indices are always positive.
The floating point precision guide covers the related hazard of using / and never rounding at all.
When TypeScript Integer Overflow Actually Strikes
Factorial
20! is 2,432,902,008,176,640,000, just under MAX_SAFE_INTEGER. 21! is 51,090,942,171,709,440,000, which overshoots the safe range. Your code happily produces a number. It looks almost right. It is not.
function factorial(n: number): number { let result = 1; for (let i = 2; i <= n; i++) { result *= i; } return result; } console.log(factorial(20)); // 2432902008176640000 (correct) console.log(factorial(21)); // 51090942171709440000 (wrong, precision lost)
No error. No NaN. Just a number that passes your small test cases and fails on the judge's input.
Products in Counting Problems
A combination problem computing n * (n-1) * ... / k! overflows for moderate n. If the problem says "return the answer modulo 1e9+7", that is a signal that intermediate values get large. Take the mod at every multiplication, not just at the end.
Two Nodes in a Grid
A grid with 10^5 rows and 10^5 columns has 10^10 cells. Encoding a cell as row * cols + col gives you a number around 10^10, comfortably within MAX_SAFE_INTEGER (roughly 9 × 10^15). But multiply two encoded values for a hash and you exit the safe range immediately.
The Product That Looks Fine (It's Not)
Many counting and DP problems ask you to return the answer modulo 10^9+7. This prime sits comfortably below MAX_SAFE_INTEGER. The mod 1000000007 explainer covers why that specific prime exists. Here is the TypeScript trap.
Reduce operands before multiplying, not after.
const MOD = 1_000_000_007; // Wrong: product may overflow before mod is taken function badMultiply(a: number, b: number): number { return (a * b) % MOD; // a * b might exceed 2^53 if a, b are close to MOD } // Correct: reduce first, then multiply function multiply(a: number, b: number): number { a = a % MOD; b = b % MOD; return (a * b) % MOD; }
Even the "correct" version above has a subtle problem: MOD is roughly 10^9. The product of two already-modded values can reach (10^9)^2 = 10^18, which exceeds MAX_SAFE_INTEGER (roughly 9 × 10^15). For truly safe modular multiplication, either ensure both inputs are below ~94,906,265, or just use BigInt.
const MOD = 1_000_000_007n; function mulmod(a: bigint, b: bigint): bigint { return (a * b) % MOD; }
BigInt multiplication is exact regardless of size.
BigInt Is the Escape Hatch
TypeScript supports arbitrary-precision integers via bigint. The syntax is a trailing n:
const big: bigint = 9007199254740992n; const bigger = big * big; // no precision loss
You cannot mix number and bigint in any arithmetic expression. TypeScript refuses to compile in strict mode and throws a TypeError at runtime.
const n: number = 5; const b: bigint = 10n; const bad = n + b; // TypeError: cannot mix BigInt and other types const good = BigInt(n) + b; // 15n
Comparison between bigint and number works without conversion. Arithmetic does not.
console.log(5n > 3); // true, comparison is fine console.log(5n === 5); // false, strict equality always false across types console.log(5n == 5); // true, loose equality works
Convert everything to bigint at the problem boundary and stay there. Converting back to number with Number(result) at the end is fine as long as the final answer fits.
One more sharp edge: BigInt has no Math support. Math.sqrt(4n) is a TypeError. For square roots of big integers, implement your own with binary search.
Quick Reference
| Boundary | Value |
|---|---|
| Safe integer max | Number.MAX_SAFE_INTEGER = 9007199254740991 |
| Safe integer min | Number.MIN_SAFE_INTEGER = -9007199254740991 |
| Bitwise op max | 2^31 - 1 = 2147483647 |
| Bitwise op min | -2^31 = -2147483648 |
Largest number | Number.MAX_VALUE ≈ 1.8 × 10^308 |
| Smallest positive | Number.MIN_VALUE ≈ 5 × 10^-324 |
| Pattern | Use |
|---|---|
Math.trunc(a / b) | Integer division, works for negatives |
Math.floor(a / b) | Floor division (not truncation) |
BigInt(n) | Convert number to bigint |
Number(b) | Convert bigint back to number |
Number.isSafeInteger(n) | Runtime check |
a % MOD before multiplying | Safe modular arithmetic |
How to Spot the Risk Before You Code
Read the constraints first. A few signals that overflow is in play:
nup to 10^9 or larger: any product of two inputs exits the safe range.- "Return the answer modulo 10^9+7": intermediate values are large; mod every multiplication.
- Factorial or combinatorics on
n > 20: switch to BigInt or reduce before multiplying. - Bitwise tricks on values that could exceed 2^31: test with a value above 2 billion before trusting the output.
- Encoding a 2D coordinate where rows or cols exceed ~94 million: the product of two encoded values exits the safe range.
When in doubt, add a Number.isSafeInteger() assertion on intermediate results during development. It won't slow your submission, but it will catch silent precision loss during testing.
Why This Will Bite You in an Interview
TypeScript does not help you here. The value does not become NaN. It does not throw. It returns a plausible-looking wrong answer, and your small test case might not exercise the large input that triggers it. You see "accepted" on your examples, fail on the judge's boundary test, and spend precious interview time staring at code that looks completely fine.
The fix is a habit. Any time you see large inputs or a modular arithmetic requirement, pause and ask whether your products stay below MAX_SAFE_INTEGER. Reach for BigInt when there is any doubt. SpaceComplexity runs voice-based DSA interviews with rubric feedback that covers exactly this kind of reasoning, not just whether your code compiled.