Types, Values & Conversions
|
This section documents modern ECMAScript and core browser JavaScript APIs — it is not tied to any specific framework or library (React, Vue, Angular, etc.). This content was generated with the assistance of AI and should be verified against the current ECMAScript specification and MDN documentation before relying on it in production, since JavaScript language features and browser API support continue to evolve. This section’s bibliography lists the reference material consulted while preparing these pages. |
JavaScript’s values fall into two categories: primitive values (numbers, strings, booleans, null,
undefined, and symbols) and object values (plain objects, arrays, functions, and everything else — including Arrays & Typed Arrays, covered on their own page rather than here).
Primitives are immutable and compared by value; objects are mutable and compared by reference. This page covers
the primitive types themselves and the rules JavaScript uses to convert a value of one type into another. The
authoritative, exhaustive version of these rules lives in the specification itself, at
ECMA-262’s data types section; this page covers
the parts of it that matter day to day.
Numbers
JavaScript has a single numeric type, Number, used for both integers and floating-point values. Internally,
every number is a 64-bit IEEE-754 double, the same "double" format used by Java, C+, and most modern
languages. That format can represent magnitudes as large as roughly `/-1.7976931348623157e308` and as small as
roughly +/-5e-324.
Numeric Literals
A base-10 integer is just a sequence of digits. JavaScript also recognizes hexadecimal (0x), and, since ES6,
binary (0b) and octal (0o) integer literals, plus floating-point literals with a decimal point and/or
exponential notation:
0
3
10000000
0xff // => 255
0xBADCAFE // => 195939070
0b10101 // => 21
0o377 // => 255
3.14
2345.6789
6.02e23 // 6.02 x 10^23
1.4738223E-32 // 1.4738223 x 10^-32
Underscores may be used inside a numeric literal purely as a visual separator — they carry no meaning and do not affect the value:
let billion = 1_000_000_000; // a thousands separator
let bytes = 0x89_AB_CD_EF; // a bytes separator
Precision, Safe Integers, and Rounding
Because the IEEE-754 double format exactly represents all integers between -(253 - 1) and 253 - 1, the
Number object exposes that boundary directly:
| Property/Method | Meaning |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
The largest/smallest (closest to zero) representable finite magnitudes |
|
Same as the global |
|
Same as the global |
|
Same as the global |
|
|
|
|
Integers larger than Number.MAX_SAFE_INTEGER can silently lose precision in their trailing digits. Because the
underlying format is binary, decimal fractions like 0.1 cannot be represented exactly either, which is the
classic source of floating-point surprises:
let x = .3 - .2; // thirty cents minus twenty cents
let y = .2 - .1; // twenty cents minus ten cents
x === y // => false: the two values are NOT the same!
x === .1 // => false
y === .1 // => true
Arithmetic never throws on overflow, underflow, or division by zero — overflow produces Infinity/-Infinity,
underflow produces 0/-0, and 0/0 produces NaN. If floating-point approximation is a problem (financial
calculations are the classic case), work with scaled integers — for example, manipulate money as integer cents
rather than fractional dollars — or reach for BigInt, below.
BigInt
BigInt (ES2020) is a separate numeric type for arbitrary-precision integers — values that can have
thousands or even millions of digits, not just the ones that fit safely in a double. A BigInt literal is a
string of digits with a trailing lowercase n:
1234n // a BigInt literal
0b111111n // binary
0o7777n // octal
0x8000000000000000n // hexadecimal -- a 64-bit integer
BigInt(Number.MAX_SAFE_INTEGER) // => 9007199254740991n -- convert a regular number
BigInt("1" + "0".repeat(100)) // => 10n**100n: one googol
The standard arithmetic operators (+ - * / % **) all work on BigInt values, except that division truncates
toward zero rather than producing a fraction:
3000n / 997n // => 3n: the quotient
3000n % 997n // => 9n: and the remainder
BigInt and regular Number operands cannot be mixed in arithmetic (1n + 1 throws a TypeError) — neither type is strictly more general than the other, since BigInt covers arbitrarily large magnitudes but
only integers, while Number covers non-integers but is capped at 64 bits of precision. Comparison operators,
however, do accept mixed operands: 1 < 2n and 0 == 0n are both true, though 0 === 0n is false because
=== also checks the type. BigInt is not suitable for cryptography (implementations make no attempt to
prevent timing attacks), and none of the Math object’s functions accept BigInt operands.
Strings
A string is an immutable, ordered sequence of 16-bit values, most of which represent a single Unicode character. Strings use zero-based indexing, and there is no separate "character" type — a single character is just a string of length 1.
String Literals
A string literal is delimited by single quotes, double quotes, or (since ES6) backticks. Double quotes may appear unescaped inside a single-quoted string and vice versa:
"" // the empty string
'testing'
"3.14"
"Wouldn't you prefer O'Reilly's book?"
`"She said 'hi'", he said.`
Backtick-delimited strings are template literals, covered below; beyond allowing embedded expressions, they
also let a string literal span multiple lines with the line breaks included verbatim, which single- and
double-quoted strings do not (those need an escaped \n for a literal newline, or a trailing backslash to
merely continue the literal onto the next source line without inserting one).
Immutability and UTF-16 Internals
Strings are immutable: there is no way to alter the character at a given index in place. Every string method
that looks like it modifies a string (toUpperCase(), replace(), trim(), …) actually returns a new
string, leaving the original untouched:
let s = "hello";
s.toUpperCase(); // => "HELLO", but does not alter s
s // => "hello": unchanged
Under the hood, JavaScript strings use the UTF-16 encoding: each element of a string is an unsigned 16-bit
value, and most common characters (the Unicode "basic multilingual plane") occupy exactly one such element. A
character outside that plane — most emoji, for instance — is represented as a surrogate pair of two 16-bit
elements, so "😀".length is 2 even though it displays as a single character. Most string methods operate on
these raw 16-bit elements and do not treat surrogate pairs specially; since ES6, however, strings are iterable,
and iterating with for…of or the spread operator (…) walks actual Unicode characters rather than raw
16-bit values.
Template Literals and Tagged Templates
A template literal can embed arbitrary JavaScript expressions between ${ and }; each expression is
evaluated, converted to a string, and spliced into the result:
let name = "Bill";
let greeting = `Hello ${name}.`; // => "Hello Bill."
A template literal may contain any number of expressions and span any number of lines with no special escaping. When a function name immediately precedes the opening backtick, the literal becomes a tagged template literal: instead of producing a string directly, the literal’s pieces are passed to that function (the "tag"), and the tagged template literal’s value is whatever the tag function returns. This is how libraries implement things like automatic HTML/SQL escaping or CSS-in-JS — the tag intercepts every interpolated value before it reaches the final string:
// A tag function receives the literal text as an array of strings, followed by one
// argument per interpolated expression.
function upper(strings, ...values) {
let result = strings[0];
for (let i = 0; i < values.length; i++) {
result += String(values[i]).toUpperCase() + strings[i + 1];
}
return result;
}
let name = "world";
upper`Hello, ${name}!`; // => "Hello, WORLD!"
JavaScript ships one built-in tag, String.raw(), which returns the literal text without processing backslash
escapes:
`\n`.length // => 1: an actual newline character
String.raw`\n`.length // => 2: a backslash character and the letter "n"
Boolean Values
A boolean is one of exactly two values, true or false, most often produced by a comparison (a === 4) and
consumed by a control structure (if, while, the conditional operator). Because JavaScript converts values
liberally, any value can be used where a boolean is expected — it is first converted according to whether it
is truthy or falsy.
Only the following values are falsy; every other value — including every object and every non-empty array, even
new Boolean(false) — is truthy:
| Falsy value | Note |
|---|---|
|
the boolean itself |
|
both numeric zeros |
|
|
|
the empty string |
|
"no value", see below |
|
"no value", see below |
|
the not-a-number value |
This lets code test a value’s presence implicitly rather than with an explicit comparison:
if (o !== null) { /* ... */ } // explicit: only excludes null
if (o) { /* ... */ } // implicit: excludes null, undefined, 0, "", NaN, etc. too
Which form is correct depends on what values o can legitimately hold — prefer the explicit comparison
whenever 0, "", or NaN are valid, meaningful values that should not be treated as absent.
null and undefined
null and undefined are both primitive values that represent the absence of a value, but they carry a
different intent:
-
nullis a language keyword. It represents a program-level, expected absence — code assignsnulldeliberately to say "there is intentionally no value here."typeof nullreturns"object", a well-known historical quirk rather than a meaningful classification. -
undefinedrepresents a deeper, often unintended absence: it is the value of a variable that has been declared but never assigned, the value read from an object property or array index that does not exist, the return value of a function with no explicitreturn, and the value of a parameter for which no argument was passed.typeof undefinedreturns"undefined".
Both are falsy, neither has any properties or methods (accessing one throws a TypeError), and the loose
equality operator treats them as equal to each other (null == undefined is true), while === distinguishes
them. A common convention — used throughout this documentation set — is to treat undefined as a signal of a
system-level or error-like absence, and to use null explicitly whenever code needs to represent "no value" on
purpose.
Symbols
A Symbol (ES6) is a primitive value that is guaranteed unique and immutable, used chiefly as a
non-colliding property key. Unlike strings, no two symbols are ever equal, even when created with the same
description:
let s1 = Symbol("propname");
let s2 = Symbol("propname");
s1 === s2 // => false: Symbol() never returns the same value twice
let o = {};
o[s1] = 1; // safe to add without risk of colliding with any other property
Because a symbol can never collide with a string-named property (or with another symbol), the language uses
symbols as an extension mechanism for adding new behavior to objects without risking backward compatibility — for example, Symbol.iterator is the well-known symbol an object implements to become iterable (see
Iterators & Generators). Symbol.for(key) offers an alternative,
shared registry: unlike Symbol(), calling Symbol.for() twice with the same string returns the same
symbol, which is useful when independent parts of a program need to agree on one symbol value.
Type Conversion and Coercion
JavaScript converts values between types far more liberally than most languages: whenever an operator or
built-in function expects a value of one type, a value of a different type is silently converted. This
implicit coercion is most visible in + and ==:
10 + " objects" // => "10 objects": the number 10 is coerced to a string
"7" * "4" // => 28: both strings are coerced to numbers
1 - "x" // => NaN: "x" cannot be coerced to a number
null == undefined // => true: these are treated as equal
"0" == 0 // => true: the string is coerced to a number first
0 == false // => true: the boolean is coerced to a number first
"0" == false // => true: both operands are coerced to 0
== versus ===
The loose equality operator == performs these implicit conversions before comparing; the strict equality
operator === never converts and requires both the type and the value to match. Because ==’s conversion
rules are a frequent source of surprising results, `=== (and its counterpart !==) is almost always the right
operator to reach for:
| Expression | == result |
=== result |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
Note that convertibility does not imply equality: undefined converts to false in a boolean context, but
undefined == false is itself false, because == never coerces its operands to booleans — only if and
similar constructs do that conversion.
Explicit Conversions
To convert deliberately rather than relying on an operator’s implicit behavior, call the Boolean(), Number(),
or String() functions directly (without new — calling them as constructors produces rarely-useful wrapper
objects instead of primitives):
Boolean([]) // => true: any object, including an empty array, is truthy
Number("3") // => 3
Number("") // => 0
Number("one") // => NaN: not a valid numeric literal
String(false) // => "false" -- equivalent to false.toString()
A few operator-based idioms achieve the same conversions implicitly and show up often in existing code:
x + "" // same as String(x)
+x // same as Number(x): unary plus
!!x // same as Boolean(x): double negation
For parsing a numeric value out of a larger string, the global parseInt() and parseFloat() functions are
more forgiving than Number(): they skip leading whitespace, parse as many valid numeric characters as they
can, and ignore any trailing text, rather than producing NaN for the whole string:
Number("3 blind mice") // => NaN: the trailing text makes the whole string invalid
parseInt("3 blind mice") // => 3: stops at the first non-numeric character
parseFloat(" 3.14 meters") // => 3.14
parseInt("ff", 16) // => 255: an explicit radix argument
See Also
-
Arrays & Typed Arrays — the
Arrayobject type, and the binary-data typed array constructors built on the same numeric formats described above. -
Objects & Destructuring — how object property values (as opposed to the primitive types on this page) are read, written, and destructured.
-
Iterators & Generators — the well-known symbols (
Symbol.iteratorand others) that make an object work withfor…ofand the spread operator. -
Lexical Structure — how numeric and string literals are tokenized at the source level.