Numeric Separators
Use underscore separators in numeric literals for readability — rules, bases, BigInt, and what they do not change at runtime.
- javascript
- numeric-separators
Long numeric literals are hard to scan. Numeric separators let you put _ inside number literals. They are syntax sugar only — the value is identical to the unadorned number.
const budget = 1_000_000;
const budget2 = 1000000;
budget === budget2; // true
const mask = 0xFF_FF_FF_FF;
const bits = 0b1111_0000_1111_0000;
const dec = 1_234.56_78;
const big = 9_007_199_254_740_991n;
Rules (what parsers reject)
// Invalid:
// 100_ trailing
// _100 leading
// 1__000 double
// 1_e2 / 1e_2 next to e in scientific in illegal positions
// 0x_1 right after base prefix sometimes restricted — avoid edge forms
Safe habit: group like humans do — thousands for decimal (1_000_000), nibbles/bytes for hex/binary (0xFF_EC).
Not for strings or runtime formatting
Number('1_000'); // NaN — separators are NOT in string-to-number
parseInt('1_000', 10); // 1 — stops at _
Intl / toLocaleString format output for display; separators are for source code.
(1_000_000).toLocaleString('en-US'); // "1,000,000"
Where they shine
| Use | Example |
|---|---|
| Timeouts | 30_000 ms |
| Limits | maxSize = 5_242_880 (5 MiB) |
| Bit patterns | 0b1100_1010 |
| BigInt IDs in tests | 123_456_789_012_345n |
Engines and tooling
Supported in modern browsers and Node. If you target ancient environments without a transpile step, avoid them — but any current frontend toolchain is fine.
They don’t appear in JSON (JSON has no underscores in numbers). Don’t expect wire formats to accept them.
Interview angle
There’s little “algorithm” here — interviewers may ask whether 1_000 === 1000 (yes) or whether parseInt('1_000') works (no). Knowing the compile-time only nature separates people who skimmed the feature from people who used it.
Interview answer (out loud)
“Numeric separators let underscores appear in number literals for readability. They’re erased at parse time, so the runtime value matches the same digits without underscores. They don’t work inside numeric strings for
Number/parseInt, and JSON doesn’t support them.”
Bases and separators together
const permissions = 0b1100_0001;
const color = 0xff_cc_00;
const size = 0o755; // octal still valid; separators: 0o7_5_5
// Exponent form
const approx = 1.2e6;
const approxSep = 1_200_000; // often clearer than e-notation for configs
Team style guides sometimes require separators only above 4 digits (1000 vs 1_000) so files don’t look noisy.
Linters and formatters
Prettier leaves numeric separators alone (they’re author intent). ESLint plugins can require them for large literals — optional. What matters: don’t reformat away meaning in code review bikesheds; separators never change runtime.
Interview micro-drills
Number('1_000'); // NaN
eval('1_000'); // 1000 — parses as source
JSON.parse('{"n":1_000}'); // throws — JSON grammar has no _
Knowing the boundary between language syntax and data formats is the actual skill.
Readability conventions
Group decimal literals in thousands (1_000_000) and binary/hex in nibbles or bytes (0b1111_0000). Stay consistent within a file so reviewers scan numbers quickly during production incidents.
Further reading
Related
Related guides
- AbortControllerCancel fetch and other async work with AbortController and AbortSignal — timeouts, race conditions, and cleanup when components unmount.
- Array find, some, every, includesShort-circuiting array predicates: find, findIndex, some, every, and includes — when to use each and common interview traps.
- Array map, filter, reducemap, filter, and reduce as the core transform toolkit — immutability, chaining costs, reduce patterns, and when a plain loop is clearer.
- Arrow Functions Deep DiveLexical this, no arguments object, no construct, concise bodies — when arrows help and when methods and generators need classic functions.
- Async Iteration and for await...ofAsync iterables, for await...of, and streaming data — how async iterators differ from Promise.all and when to use each.