JavaScriptBigInt

BigInt

Regular JavaScript numbers run out of precision at 2^53 — past Number.MAX_SAFE_INTEGER (9,007,199,254,740,991) you can no longer trust integer arithmetic. BigInt is the language's answer: an arbitrary-precision integer type with its own literal syntax (123n) and its own type tag (typeof 123n === "bigint"). It was added in ES2020 and is now supported in every modern runtime.

Why it exists

JS
// Regular numbers silently break past 2^53
Number.MAX_SAFE_INTEGER;          // 9007199254740991
Number.MAX_SAFE_INTEGER + 1;      // 9007199254740992
Number.MAX_SAFE_INTEGER + 2;      // 9007199254740992   (!)
9007199254740993 === 9007199254740992; // true — collision

// BigInt counts every integer exactly
9007199254740993n + 1n;           // 9007199254740994n
10n ** 40n;                       // 10000000000000000000000000000000000000000n

The trailing n marks a BigInt literal. You can also call BigInt(value).

Creating BigInts

JS
const a = 42n;                // literal
const b = BigInt(42);         // from a number — must be a safe integer
const c = BigInt("12345678901234567890"); // from a string — any size
const d = BigInt(0xff);       // from a hex number
const e = BigInt("0b1010");   // from a binary string

BigInt(1.5);                  // RangeError — not an integer
BigInt("abc");                // SyntaxError
Prefer literals
When the value is known at write-time, use the `n` suffix. Reach for `BigInt(...)` only when the value comes from a string or another Number.
typeof and identity

JS
typeof 42n;            // "bigint"
typeof 42;             // "number"

42n === 42;            // false — different types
42n == 42;             // true  — == coerces

BigInt(42) === 42n;    // true
Arithmetic — same operators, separate type

JS
5n + 3n;       // 8n
5n - 3n;       // 2n
5n * 3n;       // 15n
5n / 3n;       // 1n      — integer division, truncates toward zero
5n % 3n;       // 2n
2n ** 64n;     // 18446744073709551616n
-7n;           // -7n

// Comparisons work as expected, including against Number
5n < 6;        // true
5n === 5;      // false
0n == false;   // true  — both coerce to "zero"
No fractions, ever
`5n / 2n` is `2n`, not `2.5n`. BigInt is integers-only by design. If you need fractions, use a decimal library (`decimal.js`, `big.js`) or stick with Number and accept the precision.
You can't mix BigInt and Number

JS
1n + 1;          // TypeError: Cannot mix BigInt and other types
2n * 3;          // TypeError
Math.sqrt(9n);   // TypeError — Math methods don't accept BigInt

// You must convert explicitly
1n + BigInt(1);       // 2n
Number(1n) + 1;       // 2

// Watch the conversion direction — Number can lose precision!
Number(9007199254740993n);   // 9007199254740992   (lossy)
BigInt(0.5);                  // RangeError
The mix rule keeps you safe
Auto-conversion between BigInt and Number would silently lose information half the time. The explicit cast forces you to make the precision trade-off visible.
Serialisation

JS
JSON.stringify(42n);
// TypeError: Do not know how to serialize a BigInt

JSON.stringify({ id: 42n }, (key, value) =>
  typeof value === "bigint" ? value.toString() : value
);
// '{"id":"42"}'   — encoded as a string

JSON has no BigInt type. The standard pattern is to send BigInts as strings and parse them back on the other side.

When to actually reach for BigInt
  • Large database IDs. Postgres bigint, Twitter snowflake IDs, Discord IDs and YouTube comment IDs all exceed 2^53. Parsing them as Number loses the last few digits.

  • Cryptography and hashing. Modular exponentiation, RSA, elliptic-curve math — anything that touches values bigger than 53 bits.

  • Financial systems where amounts are stored as integers (e.g. satoshis, micro-cents). You're already in integer-land; BigInt removes the safe-integer ceiling.

  • Counting things in scientific or combinatorial code — factorials, large permutations, exact statistics over huge datasets.

A real example: snowflake IDs

JS
// A Discord snowflake from the API — looks like a number, but...
const raw = "175928847299117063";

// Parsing as Number silently corrupts the last few digits
Number(raw);                  // 175928847299117060   ← wrong!

// BigInt preserves it
const id = BigInt(raw);       // 175928847299117063n

// You can do arithmetic on it too — e.g. extract the timestamp
const DISCORD_EPOCH = 1420070400000n;
const timestamp = Number((id >> 22n) + DISCORD_EPOCH);
new Date(timestamp);          // a real Date
When NOT to use BigInt
  • Counts and indexes under a billion. Regular Number is faster and interoperates with Math, Array.length, the DOM, etc.

  • Anything that needs fractions. BigInt is integers-only.

  • Hot loops that interoperate with Number-based code. Conversion costs and the inability to use Math methods can dominate.

  • Floating-point science — BigInt is the wrong tool, use Number or a typed array.

Useful patterns

JS
// Safely turn a Number-or-string ID into a BigInt
const toId = v => typeof v === "bigint" ? v : BigInt(v);

// Sum a list of BigInts
const sum = xs => xs.reduce((a, b) => a + b, 0n);

// Min and max — Math.min doesn't accept BigInt, write your own
const minBig = (a, b) => a < b ? a : b;
const maxBig = (a, b) => a > b ? a : b;

// Modular exponentiation, useful in crypto
function modPow(base, exp, mod) {
  let result = 1n;
  base = base % mod;
  while (exp > 0n) {
    if (exp & 1n) result = (result * base) % mod;
    exp >>= 1n;
    base = (base * base) % mod;
  }
  return result;
}
modPow(2n, 100n, 1_000_000_007n); // 976371285n

BigInt is one of those features you can safely ignore until the day you absolutely need it. The moment you see an ID arrive as a 19-digit string, or your sums of "small" integers start producing duplicates, you'll be glad it's there.