Free lesson · JavaScript Basic to Advance
Numbers, BigInt, and NaN
How JavaScript Thinks About Numbers
JavaScript has one main numeric type: number. It handles integers, decimals, negatives, very small values, very large values — all with the same type.
const integer = 42;
const decimal = 3.14;
const negative = -100;
const verySmall = 0.000001;
const veryLarge = 1_000_000;
console.log(typeof integer);
console.log(typeof decimal);
console.log(typeof negative);
console.log(typeof verySmall);
console.log(typeof veryLarge);
All five of those have the type number. JavaScript does not distinguish between integer types and floating-point types the way many other languages do. There is no separate int, float, double, or long. There is just number.
You also see something in that last line: 1_000_000. The underscores are visual separators that JavaScript ignores at runtime. 1_000_000 and 1000000 are exactly the same value. This feature, added in ES2021, exists purely to make long numbers easier to read.
Why There Is Only One Number Type
The decision to have one numeric type goes back to JavaScript's ten-day origin. Brendan Eich didn't have time to design a full numeric tower — integers, floats, doubles, fractions, the way languages like Java or Python distinguish them. So he made one type that could handle the common case: IEEE 754 double-precision floating-point numbers.
This is the same numeric format used by spreadsheets, calculators, and most scientific software. It can represent integers up to about 9 quadrillion accurately, decimals to about 15-17 significant digits, and an enormous range of magnitudes.
The trade-off: it's not perfectly accurate for all decimals. Floating-point can't represent some common values exactly. This produces JavaScript's most famous arithmetic surprise:
console.log(0.1 + 0.2);
console.log(0.1 + 0.2 === 0.3);
The first line prints 0.30000000000000004. The second line prints false. Adding 0.1 and 0.2 does not give exactly 0.3 in JavaScript — and this is not a JavaScript bug. It's a property of binary floating-point representation that exists in nearly every programming language. JavaScript just makes it more visible because there's no separate decimal type that handles it better.
For most everyday math — counting things, computing percentages, basic arithmetic — this doesn't matter. For financial calculations, currency arithmetic, anything where exact decimal precision is required, you need to use specialized libraries or work in integer cents instead of decimal dollars.
Interactive animation: The same three prices added as decimals and as integers in the smallest unit - decimals drift, integers do not.
Number Methods and Operations
The standard arithmetic operators work the way you'd expect:
console.log(10 + 3); // 13
console.log(10 - 3); // 7
console.log(10 * 3); // 30
console.log(10 / 3); // 3.3333333333333335
console.log(10 % 3); // 1 (remainder)
console.log(10 ** 3); // 1000 (exponentiation)
Note that 10 / 3 produces a decimal result, not a truncated integer. JavaScript doesn't have integer division by default — division always returns a number. If you want an integer result, you have to truncate explicitly:
console.log(Math.floor(10 / 3)); // 3 (rounds down)
console.log(Math.ceil(10 / 3)); // 4 (rounds up)
console.log(Math.round(10 / 3)); // 3 (rounds to nearest)
console.log(Math.trunc(10 / 3)); // 3 (drops the decimal)
The Math object holds dozens of mathematical functions and constants. The ones you'll use most often:
console.log(Math.abs(-7)); // 7 (absolute value)
console.log(Math.max(3, 1, 4, 1, 5, 9)); // 9
console.log(Math.min(3, 1, 4, 1, 5, 9)); // 1
console.log(Math.sqrt(16)); // 4
console.log(Math.random()); // a random number between 0 and 1
console.log(Math.PI); // 3.141592653589793
Math.random() is worth a special note — it returns a value between 0 (inclusive) and 1 (exclusive). To get a random integer between 1 and 10, you'd write Math.floor(Math.random() * 10) + 1. This pattern shows up constantly in code.
The NaN Problem
JavaScript has a special number value called NaN, which stands for "Not a Number." It represents the result of an arithmetic operation that doesn't have a valid numeric answer.
console.log(0 / 0); // NaN
console.log(Math.sqrt(-1)); // NaN
console.log(parseInt("not a number")); // NaN
console.log("abc" * 2); // NaN
Each of those operations would mathematically have no valid number answer. Instead of crashing, JavaScript returns NaN and keeps running. This is convenient — your program doesn't stop because of one bad calculation — but it creates a problem you need to know about.
NaN is not equal to itself.
console.log(NaN === NaN); // false
console.log(NaN == NaN); // false
This is the strangest thing about NaN, and it's deliberately designed this way. The reasoning: if two calculations both fail and produce NaN, there's no guarantee they failed for the same reason. So NaN is defined as never equal to anything, including itself.
This makes checking for NaN tricky. You can't use value === NaN. You have to use the special function Number.isNaN():
const result = 0 / 0;
console.log(result === NaN); // false (doesn't work)
console.log(Number.isNaN(result)); // true (the right way)
graph TD
A[Numeric operation] --> B{Valid result?}
B -->|Yes| C[Return the number]
B -->|No| D[Return NaN]
D --> E[NaN is contagious:<br/>NaN + anything = NaN]
D --> F[NaN is never equal to anything,<br/>including itself]
style D fill:#312e81,color:#fff
This diagram shows how NaN propagates through JavaScript. When an arithmetic operation produces an invalid result, JavaScript returns NaN instead of crashing. NaN is contagious: any further arithmetic involving NaN also produces NaN. And NaN has the strange property of never being equal to anything, even itself, which is why you need a special function called Number isNaN to detect it.
BigInt for Huge Integers
JavaScript's number type can only safely represent integers up to 2^53 - 1, which is 9007199254740991. Beyond that, precision starts to break down — you can get nonsense answers from operations that should be exact.
console.log(9007199254740991 + 1); // 9007199254740992 (correct)
console.log(9007199254740991 + 2); // 9007199254740992 (wrong! should be ...993)
console.log(9007199254740993); // 9007199254740992 (silently rounded)
That third line is the scariest. You wrote a specific number, and JavaScript silently rounded it because it couldn't represent it accurately. For most code, this never matters — you rarely deal with numbers that large. But for cryptography, scientific computing, or working with large database IDs, you need bigint.
const big = 9007199254740993n; // The 'n' suffix makes it a bigint
const bigger = 12345678901234567890n;
console.log(big);
console.log(bigger);
console.log(big + 1n);
console.log(typeof big);
The n suffix is mandatory for bigint literals. And bigint does not mix with regular number arithmetic — 10n + 5 will throw an error. You have to convert: 10n + BigInt(5) or Number(10n) + 5.
You won't use bigint often as a beginner. But knowing it exists prevents the surprise of "wait, why is my number wrong" when you eventually encounter a value too large for the number type.
Interactive animation: Past Number.MAX_SAFE_INTEGER, adding 1 and adding 2 print the same number - whole numbers quietly stop working.
Common Pitfalls
Warning: Three numeric gotchas that bite JavaScript developers.
0.1 + 0.2 !== 0.3because of floating-point representation. For money or precise decimal math, work in integer cents or use a decimal library.
NaN !== NaNso you can't check forNaNwith===. UseNumber.isNaN(value)instead.Integer math breaks above
Number.MAX_SAFE_INTEGER(about 9 quadrillion). Usebigintfor larger integers.
Interactive animation: Four ways to ask about NaN - NaN === NaN is false, and Number.isNaN is the check that works.
Why This Matters in Interviews
Interview: Numeric questions in JavaScript interviews often probe whether you understand floating-point representation and the
NaNbehavior. A common question is "what does0.1 + 0.2 === 0.3return, and why?" The expected answer isfalse, because of IEEE 754 floating-point representation.Topic 2.8 — Interview Lens: Type Coercion Questions — covers the trickier number-related interview puzzles, including questions about
NaN,Infinity, and the difference betweenparseIntandNumber().
Try It
Exercise: Predict the output for each of these, then run them to check:
0.1 + 0.2 1 / 0 -1 / 0 0 / 0 Number.MAX_SAFE_INTEGER Number.MAX_SAFE_INTEGER + 1 Number.MAX_SAFE_INTEGER + 2 parseInt("42 is a number") parseInt("not a number") Number("42") Number("42 is a number")Several of these produce surprising results. Note which ones surprised you. Each surprising answer has a specific reason rooted in JavaScript's numeric design.