In JavaScript, why does 1n + 1 throw a TypeError while 1n == 1 and 1n < 2 are both true?
answer
- two numeric types, one operator set
- neither implicit conversion would be safe
- comparison compares mathematical values
- strict equality also checks the type tag
- Math functions convert, so they throw
basics
~20 sArithmetic operators refuse mixed BigInt and number operands, because implicitly converting either way could silently lose precision — the exact problem BigInt exists to prevent. Comparison operators are exempt: they compare mathematical values, so mixed comparisons are safe and allowed.
solid answer
~40 s`BigInt`, added in ES2020, is a separate primitive type — `typeof 1n` is `'bigint'`. Binary arithmetic operators require both operands to be the same numeric type, so `1n + 1` throws `TypeError: Cannot mix BigInt and other types, use explicit conversions`. The reason is that no implicit rule is safe: converting the BigInt to a number can drop digits above 2^53, and converting the number to a BigInt would have to invent behaviour for fractional values. Comparisons are different — `<`, `>`, `<=`, `>=` and `==` compare mathematical values across the two types, so `1n == 1` and `1n < 2` are true. `===` still returns false, because it also requires matching types. To do mixed arithmetic you convert explicitly with `BigInt(x)` or `Number(x)` and accept the consequence you chose.
code
javascript · 11 linestry {
console.log(1n + 1);
} catch (e) {
console.log(e.constructor.name); // TypeError
}
console.log(1n == 1); // true
console.log(1n === 1); // false
console.log(1n < 2); // true
console.log(1n + BigInt(1)); // 2n
console.log(7n / 2n); // 3n — division truncatesgo deeper
Recall that BigInt literals end in n, that typeof gives 'bigint', and that you cannot add a BigInt to a plain number without converting one of them first.
Explain why the language throws rather than picking a conversion: one direction loses digits, the other is undefined for fractions. Then draw the line between arithmetic operators, which refuse mixing, and comparisons, which allow it.
Demonstrate judgment about where conversions live — a single explicit boundary rather than scattered casts — and flag the downstream costs early: no Math support, truncating division, and JSON.stringify throwing.
Own the decision of whether BigInt belongs in a shared data model at all, given that every consumer, library and serializer on the path has to cope with a type that will not mix, and what the migration looks like if it does.
## A separate primitive, on purpose `BigInt` (ES2020) is the seventh primitive type and holds an integer of arbitrary size. Its literals carry an `n` suffix, or you build one from a value with the `BigInt()` function: ```js typeof 1n; // "bigint" BigInt('9007199254740993'); // 9007199254740993n BigInt(10); // 10n BigInt(1.5); // RangeError: The number 1.5 cannot be converted to a BigInt BigInt('abc'); // SyntaxError ``` Note `BigInt` is called as a plain function — `new BigInt(1)` throws, because there is no BigInt constructor in the `new`-able sense. ## Why arithmetic refuses to mix ```js 1n + 1; // TypeError: Cannot mix BigInt and other types, use explicit conversions 2n * 3; // TypeError ``` The designers had three options for `bigint OP number` and rejected the first two: 1. **Convert the BigInt to a number.** Any value above 2^53 would silently lose digits — reintroducing exactly the bug BigInt was created to fix. 2. **Convert the number to a BigInt.** Fine for `1`, undefined for `1.5`, `NaN` and `Infinity`. A rule that throws for half its inputs is worse than one that always throws. 3. **Throw.** Predictable, and it forces the author to state which precision loss they accept. So the error is not an oversight; it is the feature. The fix is an explicit conversion at the point where you decided which semantics you want: ```js 1n + BigInt(1); // 2n Number(1n) + 1; // 2 ``` Two operators sit outside this rule. `+` with a string is still string concatenation, so `1n + '1'` gives `'11'`. And unary `+` on a BigInt throws (`+1n` is a `TypeError`) even though unary `-` works — a deliberate carve-out so that unary plus keeps its "always produces a number" meaning for tooling that relies on it. ## Why comparison is allowed to mix Relational operators and loose equality compare *mathematical values*, and that comparison is always well defined between an integer of any size and a double. Nothing is lost, so nothing needs to be forbidden: ```js 1n == 1; // true 1n < 2; // true 2n > 1.5; // true 1n === 1; // false — strict equality also requires the same type ``` That `===` result is the one candidates trip over. Loose equality compares values across types; strict equality first requires `typeof` to match, and `'bigint'` is not `'number'`. The same type distinction means `1n` and `1` are different keys in a `Set` or `Map`. ## No Math, and truncating division Every `Math.*` function converts its arguments with `ToNumber`, which throws for a BigInt: ```js Math.max(3n, 5n); // TypeError: Cannot convert a BigInt value to a number Math.sqrt(16n); // TypeError ``` So there is no `Math.abs`, `Math.floor` or `Math.sqrt` for BigInt — you write the arithmetic yourself, or convert (losing exactness) first. Division is defined but truncates toward zero, because the result must be an integer: ```js 7n / 2n; // 3n -7n / 2n; // -3n 7n % 2n; // 1n 2n ** 64n; // 18446744073709551616n — arbitrary precision, no wraparound ``` Bitwise operators, `<<` and `>>` also work on BigInt at full width, unlike the number versions which truncate to 32 bits. ## Serialization does not come for free `JSON.stringify` has no representation for BigInt and throws `TypeError: Do not know how to serialize a BigInt`. You supply a replacer, or give your own objects a `toJSON` method that emits a string. This is worth remembering before adopting BigInt in a data model that crosses a JSON boundary. ## The compact interview answer "BigInt is its own primitive type. Arithmetic refuses mixed operands because neither implicit conversion is safe, so you convert explicitly. Comparisons compare mathematical values and are allowed to mix, but `===` still fails because the types differ. `Math.*` throws on BigInt, and division truncates."
- Which conversion would you reach for at a boundary where BigInt data meets number-only code, and what do you give up?`Number(bigValue)` when the value is provably inside the safe range — guard it with `Number.isSafeInteger(Number(bigValue))` or a magnitude check, because the conversion rounds silently otherwise. `BigInt(numValue)` in the other direction, remembering it throws a RangeError for fractional values, `NaN` or `Infinity`. Whichever way you go, do it once at the edge rather than sprinkling conversions through the arithmetic.
- What does JSON.stringify do with a BigInt, and how do you work around it?It throws `TypeError: Do not know how to serialize a BigInt`, because JSON has no such type and the committee refused to pick a lossy default. You pass a replacer that converts BigInt values to strings, or give your own classes a `toJSON` returning a string. On the way back you call `BigInt(str)` in a reviver — the value survives only because it travelled as text.
- What does 7n / 2n return, and why is there no fractional result?`3n`. BigInt is an integer type, so division truncates toward zero rather than producing a fraction; `-7n / 2n` is `-3n`, not `-4n`. If you need a fractional result you must convert to numbers and accept double precision, or scale the operands first — for example multiply by a power of ten before dividing and track the scale yourself.
saying these in an interview costs you the question
- Expects 1n + 1 to coerce and return 2n
- Thinks 1n === 1 is true because the values match
- Believes Math.max works on BigInt values
- Assumes JSON.stringify emits 1n as 1
- Expects 7n / 2n to give 3.5