Number.prototype.toLocaleString()

A full Intl.NumberFormat behind a method call. If a number is going to be read by a human, this or Intl is the answer — toFixed is not.

Number methodES3 (options since ES2012)Live demo
Common call
n.toLocaleString('en-US')
Returns
a formatted string
Replaces
manual comma insertion and toFixed
Watch out
defaults to max 3 decimal places — it ROUNDS
number.toLocaleString([locales[, options]])
→ string

Demo

Live evaluation
Try:
Inputs
nnumbera number
localestringe.g. en-US, de-DE
Output
(1234.5).toLocaleString('en-US')
'1,234.5'

The first two cases are the reason this method exists: en-US writes 1,234.5 while de-DE writes 1.234,5 — the separators are exactly swapped. Hard-coding either one is wrong for most of the world. The en-IN case shows grouping is not always in threes; Indian numbering groups the leading digits in pairs. The last case is the default worth knowing: without options the maximum fraction digits is THREE, so a value with more decimals is silently rounded for display.

Parameters

NameTypeRequiredDescription
localesstring | string[]no (host default)A BCP 47 tag such as "en-US" or "de-DE". Omitted, the runtime locale is used — which differs between your machine and your users.
optionsobjectno ({})Intl.NumberFormat options — style (decimal, currency, percent, unit), currency, minimumFractionDigits, maximumFractionDigits, notation, useGrouping.

Return value

string — The number formatted for the given locale — digit grouping, the right decimal separator, and optionally a currency symbol or percent sign.

Common patterns

Currency
Handles the symbol, its position and the decimals.
price.toLocaleString('en-US', {style: 'currency', currency: 'USD'});
Percent
Multiplies by 100 for you — pass the fraction.
(0.25).toLocaleString('en-US', {style: 'percent'});
Reuse a formatter in a loop
Intl.NumberFormat is much faster repeated.
const f = new Intl.NumberFormat('en-US');
rows.map(r => f.format(r.value));

Examples

1. US grouping
(1234.5).toLocaleString('en-US')
Returns
'1,234.5'
2. German swaps
(1234.5).toLocaleString('de-DE')
Returns
'1.234,5'
3. Currency
(5).toLocaleString('en-US', {style: 'currency', currency: 'USD'})
Returns
'$5.00'
4. Percent
(0.25).toLocaleString('en-US', {style: 'percent'})
Returns
'25%'
5. Rounds to 3
(1.23456789).toLocaleString('en-US')
Returns
'1.235'
6. toFixed ignores locale
(1234.5).toFixed(2)
Returns
'1234.50'

Pitfalls

1. It rounds to three decimals by default
Not a display-only truncation you can ignore — the extra digits are gone from the output. Any value needing more precision must say so with maximumFractionDigits, which is easy to miss because short numbers look fine.
Digits lost
(1.23456789).toLocaleString('en-US')
'1.235'
Ask for more
(1.23456789).toLocaleString('en-US', {maximumFractionDigits: 8})
'1.23456789'
2. The default locale is the runtime locale
Omitting the argument uses whatever the machine is set to — your development machine, your CI server, or your user browser. Output then differs between environments, and snapshot tests fail in ways that look random.
Environment-dependent
(1234.5).toLocaleString()
depends on the host
Be explicit
(1234.5).toLocaleString('en-US')
'1,234.5'
3. The output is not machine-parseable
A formatted string contains group separators, non-breaking spaces and currency symbols, and parseFloat will stop at the first one. Format only at the very edge of your system, and never round-trip through it.
Parses wrongly
parseFloat((1234.5).toLocaleString('en-US'))
1
Keep the number
const n = 1234.5;   // format only for display
1234.5
4. It is slow in a loop
Each call may construct a formatter from scratch. Rendering a table of thousands of numbers this way is noticeably slower than building one Intl.NumberFormat and reusing its format method — the same algorithm without the repeated setup.
Rebuilds each time
rows.map(r => r.n.toLocaleString("en-US"))
fine for small lists
Reuse a formatter
const f = new Intl.NumberFormat("en-US");
rows.map(r => f.format(r.n));
much faster

When to use

Use it
  • Any number a person will read
  • Currency, percentages and units
  • Digit grouping that follows the reader conventions
  • Compact notation for large numbers, via the notation option
Reach for something else
  • Machine-readable output → toFixed or toString
  • Formatting many values → Intl.NumberFormat, reused
  • Values you will parse back → never format first
  • Exact decimal arithmetic → format at the end, compute in minor units

Notes

Complexity
O(1) per call, with substantial constant cost from locale setup
Return
A new string; the number is unchanged
CPython impl
V8: Builtins-number-tolocalestring / ICU
Memory
May allocate a formatter per call unless one is reused
Thread-safe
Single-threaded

FAQ

They do the same work and take the same options. The method is convenient for a one-off; the constructor is much faster when formatting many values, because the locale data is prepared once instead of per call.

const f = new Intl.NumberFormat('en-US', {style: 'currency', currency: 'USD'});
rows.map(r => f.format(r.price));

History

ES3
toLocaleString added with implementation-defined behaviour.
ES2012
ECMA-402 gave it the locales and options arguments, tying it to Intl.NumberFormat.
ES2020
The notation option added, enabling compact forms such as 1.2K.