What Is BigInt in JavaScript? Big Integers Explained
BigInt is JavaScript's built-in type for whole numbers beyond Number's safe limit. How the syntax works, what it can't do, and when you actually need it.
BigInt is a built-in JavaScript primitive type for representing whole numbers of arbitrary size — integers that can exceed the range a regular Number can safely hold. Every other numeric value in JavaScript, from 1 to 1.5 to Infinity, is a 64-bit floating-point Number. BigInt is a second, separate numeric type that trades floating-point convenience for exact, unbounded integer precision.
Why Number isn’t enough
JavaScript’s Number type stores every value as an IEEE 754 double-precision float, which gives it exactly 53 bits of usable integer precision. That means Number can represent whole numbers exactly only up to Number.MAX_SAFE_INTEGER (2^53 − 1, or 9,007,199,254,740,991). Past that point, some integers simply can’t be represented — the float format has to round to the nearest representable value, and adjacent integers start colliding.
This is easy to demonstrate:
console.log(9007199254740992 === 9007199254740993); // true
Two clearly different integers compare as equal because both round to the same underlying float. For everyday arithmetic this rarely matters. For cryptographic key material, 64-bit database IDs, high-precision timestamps, or any math where losing a single integer’s worth of precision is a correctness bug, it matters a great deal.
Creating and using BigInt values
A BigInt literal is a normal integer literal with an n suffix:
const big = 9007199254740993n;
const alsoOK = BigInt(9007199254740993); // constructor form, though this loses precision if the source is already an imprecise Number
const fromString = BigInt("9007199254740993"); // safest way to construct one
Arithmetic operators (+, -, *, /, %, **) all work on BigInt values, and division truncates toward zero rather than producing a fraction — 7n / 2n is 3n, not 3.5n, because BigInt has no concept of a decimal point.
What you can and can’t do with BigInt
BigInt deliberately does not mix with Number in arithmetic. This throws a TypeError:
1n + 1; // TypeError: Cannot mix BigInt and other types
You have to convert explicitly in one direction — 1n + BigInt(1) or Number(1n) + 1 — which is a deliberate design choice, not an oversight. Silently coercing between the two would reintroduce exactly the precision loss BigInt exists to prevent. Comparison operators (<, >, ==) are the one exception: 1n == 1 is true, because loose equality across types was already part of the language before BigInt arrived.
A few other constraints worth knowing:
- BigInt values can’t be used with
Mathobject methods (Math.sqrt(4n)throws). - They can’t be serialized by
JSON.stringify()by default — you’ll need a customreplacerortoJSONmethod. - Comparing a BigInt to
NaNor using it wherenull/undefinedcoercion happens can produce surprising results, since BigInt has its own rules fortypeof("bigint") and coercion.
BigInt vs Number: a quick comparison
| Number | BigInt | |
|---|---|---|
| Underlying format | IEEE 754 double | Arbitrary-precision integer |
| Safe integer range | ±(2^53 − 1) | Unbounded |
| Decimals supported | Yes | No — integers only |
| Typical use | General arithmetic, measurements | Large IDs, crypto, exact integer math |
| Mixing in expressions | — | Not allowed without explicit conversion |
JSON.stringify support | Native | Requires custom handling |
Where BigInt actually matters
The clearest use cases are places where an integer naturally exceeds 2^53: snowflake-style distributed IDs, 64-bit database primary keys pulled in from a backend, cryptographic operations that need exact modular arithmetic, and any protocol (like some binary formats read through typed arrays and ArrayBuffer) that specifies 64-bit integer fields. If you’re hashing or signing data, exact integer math is often a hard requirement rather than a nice-to-have — see how digital signatures work for the kind of arithmetic where a rounding error would silently break a signature.
BigInt also shows up when JavaScript is used as a host for lower-level numeric work — reading binary buffers in Node.js, or interoperating with WebAssembly modules that pass 64-bit integers across the boundary. Before BigInt existed, developers reached for string-based big-number libraries to fake this; now the capability is native, at the cost of not composing with regular numeric code for free.
Gotchas to watch for
The biggest practical trap is JSON. APIs that return large integer IDs as raw JSON numbers (not strings) have often already lost precision by the time JSON.parse() hands them to you — BigInt can’t retroactively fix a value that was already rounded during parsing. The safe pattern is for the API to send those IDs as strings and for the client to convert with BigInt(idString) explicitly.
The second trap is performance: BigInt arithmetic is meaningfully slower than Number arithmetic for small values, because the engine can’t use the fast native integer paths it uses for regular numbers. Reach for BigInt when correctness genuinely requires it, not as a default replacement for Number in ordinary loops or counters.
Type-checking code should also account for the new primitive. typeof 10n returns "bigint", distinct from "number", so any code that branches on typeof x === "number" needs an explicit BigInt branch if it’s meant to handle both.
The takeaway
BigInt gives JavaScript exact, arbitrary-precision integers where Number’s 53-bit limit would otherwise silently round large values. Reach for it when you’re handling IDs, cryptographic values, or binary data that specifies 64-bit integers — and reach for plain Number everywhere else, since BigInt doesn’t mix with it automatically and carries a real performance cost. The two types are meant to coexist deliberately, not to replace one another.
Tagged
Keep reading
Takina · · 4 min read Web Locks API: Coordinating Work Across Browser Tabs
The Web Locks API lets JavaScript acquire named locks shared across tabs, so only one tab does work like a token refresh or a write at a time.
Takina · · 4 min read Svelte 5 Runes Explained
Svelte 5 runes like $state and $derived replace the old reactive-assignment magic with explicit function calls that work anywhere in a file.
Takina · · 4 min read Shallow Copy vs Deep Copy in JavaScript
A shallow copy duplicates an object's top-level properties but shares nested references; a deep copy duplicates everything recursively. How to do each.