JavaScript Types and Operators
- Description: Primitive types, the boxed object types, type coercion, the operator zoo, and the subtle equality rules that catch every newcomer.
- My Notion Note ID: K2A-F4-2
- Created: 2018-03-23
- Updated: 2026-05-17
- License: Reuse is very welcome. Please credit Yu Zhang and link back to the original on yuzhang.io
Table of Contents
- 1. The Type System
- 2. Primitives
- 3. typeof, null, and undefined
- 4. Type Conversion
- 5. Arithmetic, Assignment, Logical Operators
- 6. Comparison and Equality
- 7. Bitwise Operators
- 8. Operator Precedence
- 9. References
1. The Type System
JavaScript is dynamically typed — a variable can hold any value, the value carries its type. Eight types in total:
| Category | Types |
|---|---|
| Primitives (immutable) | string, number, bigint, boolean, symbol, undefined, null |
| Object (mutable, reference semantics) | object (includes arrays, functions, dates, regexes, maps, sets, …) |
Functions are objects (callable ones). Arrays are objects (with a length property and integer keys).
2. Primitives
2.1 string
UTF-16 code units. Three quote styles:
"double quotes";
'single quotes';
`template literal — supports ${expression}s and
multi-line`;
Template literals also drive tagged templates:
function html(strings, ...values) { /* ... */ }
const safe = html`<p>${userInput}</p>`;
Common operations:
const s = 'hello';
s.length; // 5
s.toUpperCase(); // 'HELLO'
s.includes('ell'); // true
s.startsWith('he'); // true
s.split(','); // ['hello']
s.replace('e', 'a'); // 'hallo'
s.replaceAll('l', ''); // 'heo'
s.slice(1, 4); // 'ell'
s.at(-1); // 'o' (negative index OK)
[...s]; // ['h','e','l','l','o']
Strings are immutable — every "mutating" method returns a new string.
2.2 number
64-bit IEEE-754 double. One number type for integers and floats — no int vs float distinction.
42; 3.14; 1e6; 0x1f; 0b101; 0o17;
Number.MAX_SAFE_INTEGER; // 2^53 - 1
Number.EPSILON; // smallest representable diff
Infinity; -Infinity;
NaN; // result of bad math
0.1 + 0.2; // 0.30000000000000004 — IEEE-754 gotcha
Detection helpers:
Number.isFinite(x); // not NaN/Infinity
Number.isInteger(x);
Number.isNaN(x); // strict — checks for actual NaN
isNaN(x); // coercing version — beware (isNaN('abc') === true)
2.3 bigint
Arbitrary-precision integers (ES2020):
const big = 1234567890123456789012345678901234567890n;
big + 1n; // works
big + 1; // TypeError — can't mix with number
BigInt(42); // construct from number
Use for cryptography, timestamps in nanoseconds, accounting where precision matters. Otherwise stick with number.
2.4 boolean
true or false. Common ways to produce them:
!!value; // double-bang: coerce to boolean
Boolean(value); // same thing, explicit
value === expected; // comparison
2.5 symbol
Unique, opaque tokens — primary use is as non-colliding object keys.
const s = Symbol('description'); // description is for debugging
const s2 = Symbol('description');
s === s2; // false — every symbol is unique
const obj = { [s]: 'value' };
Well-known symbols customise built-in operations:
| Symbol | What it customises |
|---|---|
Symbol.iterator |
for…of, spread, [...x] |
Symbol.asyncIterator |
for await…of |
Symbol.toPrimitive |
implicit coercion |
Symbol.toStringTag |
Object.prototype.toString output |
2.6 undefined
The "no value" sentinel — what you get from an uninitialised variable, a missing argument, a function with no return.
let x; // undefined
function f() {}; f(); // undefined
({}).foo; // undefined
2.7 null
A different "no value" — intentionally assigned to mark "no object yet".
let user = null;
user = await loadUser();
Two nullish values, two meanings:
undefined— uninitialised, missing, didn't return.null— explicitly empty.
Use ?? and ?. (optional chaining) to handle both together.
3. typeof, null, and undefined
typeof 'a' // 'string'
typeof 1 // 'number'
typeof 1n // 'bigint'
typeof true // 'boolean'
typeof Symbol() // 'symbol'
typeof undefined // 'undefined'
typeof null // 'object' ← historical bug, can't be fixed
typeof {} // 'object'
typeof [] // 'object' ← also object
typeof function(){} // 'function' ← special-cased
typeof null === 'object' is a 30-year-old bug from the very first JavaScript engine. Workarounds:
x === null; // direct
x == null; // true for both null and undefined
typeof x === 'object' && x !== null // "is non-null object"
Array.isArray(x); // arrays specifically
typeof is the one operator that doesn't throw on undeclared variables:
typeof unknownVar === 'undefined'; // safe even if unknownVar isn't declared
4. Type Conversion
JavaScript happily converts between types — implicitly (coercion) or explicitly.
4.1 To String
String(123); // '123'
String(null); // 'null'
String(undefined); // 'undefined'
(123).toString(); // '123'
(255).toString(16); // 'ff'
`${123}`; // '123'
123 + ''; // '123' — bad style but works
4.2 To Number
Number('42'); // 42
Number('42.5'); // 42.5
Number(' 42 '); // 42 — trims
Number('42abc'); // NaN — fails on extra chars
Number(''); // 0 — empty string is 0
Number(true); // 1
Number(false); // 0
Number(null); // 0
Number(undefined); // NaN
+'42'; // 42 — unary plus
parseInt('42abc', 10); // 42 — bails at first non-digit
parseFloat('42.5abc'); // 42.5
Always pass the radix to parseInt: parseInt(s, 10). A leading 0x/0X still infers radix 16 even in current engines; only the pre-ES5 leading-0 → octal inference was removed.
4.3 To Boolean
Boolean(''); // false ← empty string
Boolean(0); // false
Boolean(NaN); // false
Boolean(null); // false
Boolean(undefined); // false
Boolean(0n); // false
// Everything else is true.
Boolean('false'); // true ← non-empty string
Boolean([]); // true ← arrays are objects, not empty
Boolean({}); // true
Falsy values, all seven: false, 0, 0n, '', null, undefined, NaN. Everything else is truthy.
4.4 The + Operator: String vs Number
+ is the only operator that's ambiguous in JavaScript:
1 + 2 // 3
'1' + 2 // '12' — string concatenation
1 + '2' // '12'
1 + 2 + '3' // '33' — left-to-right
'1' + 2 + 3 // '123'
[] + [] // '' — empty arrays → empty strings
[] + {} // '[object Object]'
{} + [] // 0 — interpreted as block + unary [] (in some contexts)
The notorious {}+[] ambiguity is a parser quirk in REPLs; in real code (assigned/parenthesised) it behaves like ([] + {}). Always use parens when relying on + with {}.
5. Arithmetic, Assignment, Logical Operators
5.1 Arithmetic
| Op | Effect |
|---|---|
+ - * / |
Standard. / always returns a float. |
% |
Remainder. Sign follows the dividend. |
** |
Exponent. 2 ** 10 === 1024. |
++ -- |
Increment/decrement. Pre- vs post-fix differs. |
Unary + - |
Coerce to number / negate. |
5.2 Assignment
| Op | Equivalent |
|---|---|
= |
Plain assignment. |
+= -= *= /= %= **= |
Compound arithmetic. |
&&= ` |
|
<<= >>= >>>= &= ` |
= ^=` |
Logical assignment only assigns when the LHS condition holds:
x ||= 5; // x = x || 5 — assign if x is falsy
x &&= 5; // x = x && 5 — assign if x is truthy
x ??= 5; // x = x ?? 5 — assign if x is null/undefined
5.3 Logical
a && b // returns first falsy, else last value
a || b // returns first truthy, else last value
a ?? b // returns a if not null/undefined, else b
!a // logical NOT
These don't return booleans, they return one of the operands. Idiomatic uses:
const name = user.name || 'anonymous'; // default if falsy
const port = config.port ?? 3000; // default if nullish (allows 0)
isReady && doWork(); // run if isReady
Difference between || and ??:
const port = config.port || 3000; // port=0 → 3000 (probably wrong)
const port = config.port ?? 3000; // port=0 → 0 (correct)
?? was added in ES2020 specifically for this case.
6. Comparison and Equality
== // loose equality — coerces types
=== // strict equality — no coercion
!= // loose inequality
!== // strict inequality
< > <= >= // ordering
6.1 Strict Equality (===)
1 === 1 // true
'a' === 'a' // true
NaN === NaN // false ← only value not equal to itself
+0 === -0 // true
null === null // true
{} === {} // false — different references
For NaN detection: Number.isNaN(x) or x !== x.
6.2 Loose Equality (==)
Applies a coercion table. Rules summary:
| LHS | RHS | Behaviour |
|---|---|---|
| same type | same type | Same as === |
null |
undefined |
true |
| number | string | Convert string to number, then compare |
| boolean | anything | Convert boolean to number, then compare |
| object | primitive | Convert object to primitive (valueOf/toString), then compare |
Surprises:
0 == '' // true — both → 0
0 == '0' // true
'' == '0' // false — '' → 0, '0' → 0, but '' !== '0' as strings... wait, after coercion both 0
// Actually true. Let me re-check. '' == '0' compares as strings? no, neither side is number-coerced unless...
// Per spec: both sides are strings → string comparison → false.
null == 0 // false — null only equals undefined
null == undefined // true
[] == false // true — [] → '' → 0; false → 0
[0] == false // true
NaN == NaN // false
Recommendation: always use === and !==. The exception many style guides allow: x == null to test for "null or undefined". Set that with x === null || x === undefined if you want maximum clarity.
6.3 Object.is
Object.is(NaN, NaN); // true
Object.is(+0, -0); // false
Object.is(1, 1); // true
Like === but handles NaN and ±0 differently. Rarely needed in app code; useful when implementing equality on a library.
7. Bitwise Operators
Operate on 32-bit signed integer representations (numbers get truncated and converted).
| Op | Effect |
|---|---|
& |
AND |
| ` | ` |
^ |
XOR |
~ |
NOT |
<< |
Left shift |
>> |
Sign-propagating right shift |
>>> |
Zero-fill right shift |
Use cases are narrow: bit flags, low-level binary protocols, performance hacks. n | 0 is sometimes used as a fast floor-to-int — Math.trunc(n) is clearer.
8. Operator Precedence
Selected, highest to lowest:
| Group | Operators |
|---|---|
| Grouping | (...) |
| Member access / call | . ?. [ ] ( ) |
| Prefix | ! ~ + - ++ -- typeof void delete await |
| Exponent | ** (right-associative) |
| Mul / Mod | * / % |
| Add | + - |
| Bit shift | << >> >>> |
| Relational | < <= > >= instanceof in |
| Equality | == != === !== |
| Bitwise AND, XOR, OR | `& ^ |
| Logical AND, OR, Nullish | `&& |
| Conditional | ? : |
| Assignment | = += -= … |
| Comma | , |
Don't memorise the full table — use parentheses. Reading code: when in doubt, MDN's precedence page is the reference.
Common mixed-operator trap:
a || b && c // parses as a || (b && c)
(a || b) && c // explicit grouping
a + b * c // a + (b * c)
Mixing ?? with &&/|| is a syntax error without parens — the language forces you to disambiguate.
9. References
- MDN Operators — https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators
- MDN Equality comparisons — https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness
- MDN Number — https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number
- "What is the difference between
==and===?" Equality Table — https://dorey.github.io/JavaScript-Equality-Table/ - See also: javascript-control-flow, javascript-functions-scope-and-closures