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

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