bigint

Types arbitrary-precision integers, which require target ES2020 and never mix with number.

Signature
let bar: bigint = 100n;
  • Added in TypeScript 3.2

Errors

Mixing bigint and number in arithmetic is a compile error and a runtime TypeError. Using an `n` literal below target ES2020 raises TS2737: "BigInt literals are not available when targeting lower than ES2020."

Changelog

  • PHP 3.2bigint type and n literals added, initially only for target esnext; no downlevel emit is provided, since BigInt operators behave differently from number.
  • PHP 3.8BigInt literals became available when targeting ES2020; below that, TS2737 is reported and you must use the BigInt() function instead.

bigint is the type of JavaScript's arbitrary-precision integers. Values come from an n-suffixed literal (100n) or the BigInt() function. Use it when integers can exceed Number.MAX_SAFE_INTEGER (253−1): database IDs, snowflake IDs, token amounts, cryptographic values — anywhere a silently rounded digit is a bug.

The two rules that bite

Target. n literals require target of ES2020 or higher. Below that TypeScript reports TS2737 and refuses; there is no downlevel emit, because BigInt operators behave differently from number operators and a polyfill cannot fix that. Fall back to BigInt("100") if you must target lower.

No mixing. bigint and number are separate domains. Neither is assignable to the other, and mixing them in an arithmetic expression is an error at compile time and a TypeError at runtime. Convert explicitly with BigInt(n) or Number(b) — and remember that Number() on a large bigint is exactly the precision loss you adopted bigint to avoid.

Division truncates: 7n / 2n is 3n. There are no bigint floats.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Holding 64-bit database or snowflake IDs without losing the low digits
  • Computing token or currency amounts in base units where rounding is unacceptable
  • Factorials, hashes, or modular arithmetic that overflow the safe integer range

Frequently asked questions

How do I fix "BigInt literals are not available when targeting lower than ES2020"?

Raise `target` in tsconfig.json to `ES2020` or higher. If you must keep a lower target, replace the `n` literals with the `BigInt()` function — `BigInt("100")` instead of `100n` — since TypeScript provides no downlevel emit for BigInt. Add `esnext.bigint` to `lib` if your runtime has BigInt but your `lib` setting does not declare it.

Why can't I add a bigint and a number in TypeScript?

They are separate numeric domains, so mixing them is a compile error and a runtime `TypeError: Cannot mix BigInt and other types`. Convert explicitly with `BigInt(n)` or `Number(b)`. Note that relational comparisons like `1n < 2` are allowed across the two types even though arithmetic is not.

How do I serialise a bigint to JSON?

Convert it to a string first — `JSON.stringify` throws "Do not know how to serialize a BigInt" on any bigint value. Map the field to `id.toString()` before stringifying, or supply a replacer function that does it, and rebuild with `BigInt(value)` when parsing. Parsing the number directly loses precision above 2^53−1.

More examples

Carrying 64-bit IDs through JSON safely

The first log prints the two IDs intact. The second shows the round trip back to strings, which is how a bigint must cross JSON — `JSON.stringify` throws on a raw bigint. The third prints `[ '9223372036854776000', '9007199254740992' ]`: parsing the same IDs as numbers corrupts both, which is the failure mode bigint exists to prevent.

Example · typescript
interface RowWire { id: string; label: string }
interface Row { id: bigint; label: string }

const wire: RowWire[] = [
  { id: "9223372036854775807", label: "max" },
  { id: "9007199254740993", label: "unsafe as number" },
];

const rows: Row[] = wire.map((r) => ({ id: BigInt(r.id), label: r.label }));
const back = rows.map((r) => ({ id: r.id.toString(), label: r.label }));

console.log(rows.map((r) => r.id.toString()));
console.log(JSON.stringify(back));
console.log(wire.map((r) => String(Number(r.id)))); // what naive parsing gives

Why the compiler blocks mixed arithmetic

Prints `333333333333333333`, `1`, `true true`, then `333333333333333340`. Arithmetic is blocked across the two domains but relational comparison is not, which is an easy asymmetry to forget. The final `Number()` conversion visibly rounds the value, showing that converting back out of bigint reintroduces exactly the imprecision you were avoiding.

Example · typescript
const total: bigint = 1_000_000_000_000_000_000n;
const shares = 3; // a plain number

// const bad = total / shares;
// Error: Operator '/' cannot be applied to types 'bigint' and 'number'.
// At runtime this is: TypeError: Cannot mix BigInt and other types.

const perShare = total / BigInt(shares); // explicit conversion
console.log(perShare.toString());
console.log((total % BigInt(shares)).toString());
console.log(total > 10n, total > 5);      // comparisons across types ARE allowed
console.log(Number(perShare));            // back to number: precision now at risk

Discussion

  • Be the first to comment on this lesson.