jsguides

Number.MAX_SAFE_INTEGER

Number.MAX_SAFE_INTEGER

Number.MAX_SAFE_INTEGER is a static property of the Number object that holds the largest integer JavaScript can represent exactly: 9007199254740991 (about 9 quadrillion). The “safe” part is the key — integers larger than this can still be stored as Number values, but they may not round, compare, or increment the way you expect. The constant is read-only; assigning to it does nothing in sloppy mode and throws a TypeError in strict mode.

Syntax

Number.MAX_SAFE_INTEGER

Number.MAX_SAFE_INTEGER is a static property on the Number constructor. You read it as a plain value — no parentheses, no new, no module to import. Every realm, worker, and iframe loaded from the same engine sees the same number, because the constant is defined once at parser start rather than recomputed per execution context.

Value

Number.MAX_SAFE_INTEGER;                              // 9007199254740991
Number.MAX_SAFE_INTEGER === Math.pow(2, 53) - 1;      // true

The value is 2^53 - 1. It comes from the 53-bit mantissa of an IEEE-754 double-precision float: 52 stored fraction bits plus 1 implicit leading bit. Any integer up to and including 2^53 - 1 can be stored exactly. Past that point, the mantissa runs out of bits and consecutive integers can collapse to the same float.

The companion Number.MIN_SAFE_INTEGER mirrors this on the negative side:

Number.MIN_SAFE_INTEGER;                       // -9007199254740991
Number.MIN_SAFE_INTEGER === -(2 ** 53 - 1);    // true

Description

Why 2^53 - 1

The number type in JavaScript is a double-precision IEEE-754 float. Doubles allocate 52 bits for the fraction, plus 1 implicit leading bit, giving 53 bits of mantissa precision. That is enough to encode every integer from 0 up to 2^53 - 1 without rounding. One step past that, two distinct integers share a single float, and integer arithmetic stops being associative:

const max = Number.MAX_SAFE_INTEGER;
max + 1;        // 9007199254740992
max + 2;        // 9007199254740992
(max + 1) === (max + 2);  // true — mathematically wrong

Number.isSafeInteger() encodes the definition: an integer is “safe” if (a) it can be represented exactly, and (b) no other integer rounds to that representation. Past MAX_SAFE_INTEGER, the second condition is the one that fails.

MAX_SAFE_INTEGER vs MAX_VALUE

A common confusion: MAX_SAFE_INTEGER is an integer limit, not a magnitude limit. The largest finite Number overall is Number.MAX_VALUE, which is roughly 1.79e308. That number is astronomically larger, but once you step past MAX_SAFE_INTEGER, integer-level increments are no longer exact — you just get a much larger float.

Number.MAX_SAFE_INTEGER;  // 9_007_199_254_740_991  (largest safe integer)
Number.MAX_VALUE;         // 1.7976931348623157e+308 (largest finite number)

Both are useful, but they answer different questions. Reach for MAX_SAFE_INTEGER when the next step is a comparison, a counter increment, or a key in a map. Reach for MAX_VALUE when you want to know “is this still a finite number at all?”

Examples

Basic value

console.log(Number.MAX_SAFE_INTEGER);
// 9007199254740991

The +1 / +2 collision

This is the demo worth memorizing — it shows exactly what “safe” means and why it matters. Run it in any modern browser console and the result is identical on every engine, because the equality is baked into the IEEE-754 representation rather than a quirk of one runtime.

const max = Number.MAX_SAFE_INTEGER;
console.log(max + 1);            // 9007199254740992
console.log(max + 2);            // 9007199254740992
console.log((max + 1) === (max + 2));  // true

If your domain involves IDs, microsecond timestamps, high-resolution counters, or anything that can grow past ~9 quadrillion, this collision is what will quietly break equality checks. Two different IDs become the same number. Two different timestamps become indistinguishable. That is the failure mode the constant is warning you about.

Pairing with Number.isSafeInteger

Number.isSafeInteger() lets you guard arithmetic against overflow before it happens:

function addSafe(a, b) {
  if (!Number.isSafeInteger(a) || !Number.isSafeInteger(b)) {
    throw new RangeError("Inputs must be safe integers");
  }
  const result = a + b;
  if (!Number.isSafeInteger(result)) {
    throw new RangeError("Result overflows the safe-integer range");
  }
  return result;
}

addSafe(1, 2);                                  // 3
addSafe(Number.MAX_SAFE_INTEGER, 1);            // RangeError: Result overflows

This pattern is the right shape for financial calculations, vote counts, sequence generators, and anywhere you treat integers as exact.

Pairing with Number.EPSILON

Number.EPSILON is 2^-52, the smallest difference between two representable floats near 1. Both it and MAX_SAFE_INTEGER derive from the 53-bit mantissa, which is why their product is close to — but not exactly — 2:

Number.EPSILON;                                // 2.220446049250313e-16
Number.MAX_SAFE_INTEGER;                       // 9007199254740991
Number.MAX_SAFE_INTEGER * Number.EPSILON;      // 1.9999999999999998

That near-2 result is a useful sanity check: it confirms both constants come from the same underlying precision budget. If MAX_SAFE_INTEGER were defined as 2^52 instead, the product would be exactly 1; the fact that you get 1.9999999999999998 is a fingerprint of the off-by-one, because the mantissa can hold 2^53 distinct values but the largest integer in that range is 2^53 - 1.

MIN_SAFE_INTEGER symmetry

Number.MIN_SAFE_INTEGER;                  // -9007199254740991
Number.MAX_SAFE_INTEGER + Number.MIN_SAFE_INTEGER;  // 0

The safe range is symmetric around zero: roughly ±9 quadrillion, give or take a single integer on the negative side.

BigInt for the unsafe range

When you need exact integers past the safe range, the answer is BigInt. Coercing Number to BigInt first avoids the +1 / +2 collision entirely:

const huge = BigInt(Number.MAX_SAFE_INTEGER) + 2n;  // 9007199254740993n
huge.toString();                                    // "9007199254740993"

BigInt is a separate type and cannot be mixed with Number in arithmetic without explicit conversion. The trade-off: you give up direct compatibility with Math.* and Number.isFinite()-style checks, and you gain a practically unlimited integer range.

Common pitfalls

It is a safe-integer limit, not a number limit. Number.MAX_VALUE is the magnitude boundary, around 1.79e308. You can still do meaningful arithmetic past MAX_SAFE_INTEGER — you just lose integer-level precision.

Don’t use it as an array-index ceiling. Arrays are limited to 2^32 - 2 indices, far below 2^53 - 1. Array length fails long before the safe-integer ceiling ever matters.

JSON serialization collapses at the boundary. JSON.stringify(Number.MAX_SAFE_INTEGER + 1) and JSON.stringify(Number.MAX_SAFE_INTEGER + 2) both round-trip to the same value through JSON.parse. For lossless transport of large integers, serialize as strings or use BigInt with a custom replacer/reviver.

const a = Number.MAX_SAFE_INTEGER + 1;        // 9007199254740992
const b = Number.MAX_SAFE_INTEGER + 2;        // 9007199254740992
const wire = JSON.stringify({ a, b });        // '{"a":9007199254740992,"b":9007199254740992}'
const { a: ra, b: rb } = JSON.parse(wire);
ra === rb;                                    // true — the two values are now indistinguishable

Bitwise operators ignore it. Bitwise operations coerce to 32-bit signed integers. Number.MAX_SAFE_INTEGER | 0 is 0; Number.MAX_SAFE_INTEGER & 1 is 1. The safe-integer range does not apply to bitwise results — they have their own, much smaller precision limit.

String comparisons are safer at the edge. If you must store IDs that may exceed MAX_SAFE_INTEGER (Twitter snowflakes, UUIDs as integers, large database keys), store and compare them as strings. Lexicographic order on equal-length digit strings is also numeric order, so you do not lose anything.

The constant is read-only. Number.MAX_SAFE_INTEGER = 0; is a no-op in sloppy mode and throws TypeError: Cannot redefine property: MAX_SAFE_INTEGER in strict mode. Its property attributes are { writable: false, enumerable: false, configurable: false }.

Specifications

SpecStatus
ECMAScript 2015 (ES6)Added. Stable.
ECMAScript 2024+Inherited, no changes.

The constant is defined in ECMA-262 under Number.MAX_SAFE_INTEGER and has not changed since introduction.

Browser compatibility

Number.MAX_SAFE_INTEGER is available in every JavaScript engine shipped since 2015: V8 (Chrome, Node.js, Edge), SpiderMonkey (Firefox), JavaScriptCore (Safari), and Chakra (legacy Edge). Internet Explorer does not expose it; a one-line polyfill is enough if you need to support legacy runtimes:

if (!Number.MAX_SAFE_INTEGER) {
  Object.defineProperty(Number, "MAX_SAFE_INTEGER", {
    value: 9007199254740991,
    writable: false,
    enumerable: false,
    configurable: false
  });
}

core-js ships the same polyfill under its es6.number.statics module.

See also