Operators, Comparison and Type Coercion
Arithmetic, comparison and logical operators, why === is safer than ==, truthy and falsy values, and the modern operators ?? and ?. that handle missing values.
Arithmetic operators
An operator combines values into a new value. The arithmetic operators work on numbers:
| Operator | Meaning | Example | Result |
|---|---|---|---|
+ |
addition | 7 + 2 |
9 |
- |
subtraction | 7 - 2 |
5 |
* |
multiplication | 7 * 2 |
14 |
/ |
division | 7 / 2 |
3.5 |
% |
remainder | 7 % 2 |
1 |
** |
exponentiation (ES2016) | 2 ** 10 |
1024 |
console.log(7 / 2, 6 / 2);
console.log(7 % 2, -7 % 2);
console.log(Math.floor(7 / 2), Math.trunc(-7 / 2), Math.floor(-7 / 2));
console.log(1 / 0, -1 / 0, 0 / 0);
console.log(0.1 + 0.2);
3.5 3 1 -1 3 -3 -4 Infinity -Infinity NaN 0.30000000000000004
What this shows:
-
There is one number type, so
/simply divides:7 / 2is3.5and6 / 2is3. For whole-number division, round the result yourself withMath.floor()(always down) orMath.trunc()(toward zero); they differ for negative numbers. -
%is the remainder, and its sign follows the left operand:-7 % 2is-1. -
Dividing by zero is not an error: it gives
Infinity,-Infinity, orNaN("not a number") for0 / 0. -
Decimals are binary floating-point numbers, so
0.1 + 0.2is not exactly0.3(see why 0.1 + 0.2 is not 0.3 and Numbers, Math and Dates).
Increment and decrement
++ adds 1 to a variable and -- subtracts
1. Written after the variable (postfix), the expression
gives the old value; written before it (prefix), it gives
the new value. The variable changes either way:
let tickets = 5;
console.log(tickets++); // gives 5, then tickets becomes 6
console.log(tickets);
console.log(++tickets); // tickets becomes 7, then gives 7
console.log(tickets--, tickets);
5 6 7 7 6
On a line of its own, count++ and ++count
do the same thing. The difference only matters when the expression
is used inside a larger one — which is a good reason not to do that.
Assignment operators
= assigns a value. The compound operators combine an
operation with assignment: total += kwh means
total = total + kwh. They exist for every arithmetic
operator (-=, *=, /=,
%=, **=), and since ES2021 also for the
logical operators (&&=, ||=,
??=; see below). An
assignment is itself an expression whose value is the assigned
value, which is why a = b = 0 works.
Type coercion
Type coercion is the automatic
conversion of a value to another type when an operator needs it.
Where Python raises a TypeError for
"5" + 3, JavaScript converts one side and carries on.
That is what makes JavaScript
weakly typed. The rules for the arithmetic operators are short:
-
+with a string on either side joins text. The other side is converted to a string. -
All other arithmetic operators convert to numbers.
-,*,/,%and**turn both sides into numbers, and a string that is not a valid number becomesNaN. -
Operators work left to right. So
1 + 2 + " kWh"first adds the numbers.
console.log("5" + 3, 3 + "5");
console.log("5" - 3, "5" * "2", "10" / "4");
console.log(1 + 2 + " kWh", "kWh " + 1 + 2);
console.log("12 kWh" * 2);
console.log(true + 1, null + 1, undefined + 1);
console.log([1, 2] + [3], [] + []);
53 35 2 10 2.5 3 kWh kWh 12 NaN 2 1 NaN 1,23
The last lines show the stranger conversions:
true becomes 1, null becomes
0, undefined becomes NaN, and
arrays are first turned into strings ([1, 2] becomes
"1,2", an empty array becomes an empty string). You do
not need to memorize all of this. You need to recognize it when a
bug shows "312.510" where you expected
322.5.
Comparison operators
The relational operators <, >,
<= and >= return a boolean. When
both sides are strings, they compare text character by character by
character code (UTF-16 code units), not alphabetically as a person
would. Otherwise both sides are converted to numbers:
console.log(118.2 < 121.4);
console.log("10" < "9"); // two strings: compared as text
console.log("10" < 9); // one number: compared as numbers
console.log("Zwolle" < "apeldoorn", "Zwolle" < "Apeldoorn");
console.log("12 kWh" < 20, "12 kWh" >= 20);
true true false true false false false
"10" < "9" is true because the
character "1" comes before "9". Uppercase
letters all come before lowercase ones, so
"Zwolle" sorts before "apeldoorn". For
human-friendly ordering, use localeCompare() (see
Strings and Template Literals). The last line shows that any comparison with NaN is
false, so a value can be neither smaller nor greater
than or equal to another.
Strict equality: === and !==
Strict equality,
===, is true only when both values have
the same type and the same value. It never converts anything. Its
opposite is !==. This is the comparison to use by
default:
console.log(5 === 5, 5 === "5");
console.log("North" === "North", "North" === "north");
console.log(null === undefined);
console.log(5 !== "5");
true false true false false true
Loose equality: == and !=
Loose equality,
==, first converts the two values to a common type when
their types differ, and then compares them. Its opposite is
!=. The conversion rules are long and produce results
that are hard to predict:
console.log(5 == "5");
console.log("" == 0, "0" == 0, "" == "0");
console.log(false == "0", false == "");
console.log(null == undefined, null == 0);
console.log([] == false, [] == "");
true true true false true true true false true true
Look at the second line: "" equals 0, and
"0" equals 0, but "" does not
equal "0". Equality that is not transitive is a
reliable source of bugs. Use ===, and convert values
explicitly when their types may differ.
One use of == is common and deliberate:
value == null is true for both
null and undefined and for nothing else,
so it checks "is there no value?" in one comparison. Many style
guides allow exactly this exception.
NaN and comparing objects
Two cases trip up both == and ===.
NaN is not equal to anything, not even to itself; test
for it with Number.isNaN(). And objects (including
arrays) are equal only when they are the same object, not
when they have the same contents (see
values and references):
const reading = Number("n/a");
console.log(reading === NaN, Number.isNaN(reading));
const a = [118.2, 119.0];
const b = [118.2, 119.0];
console.log(a === b, a === a);
console.log(JSON.stringify(a) === JSON.stringify(b));
false true false true true
Comparing the JSON text of two simple arrays or objects is a quick way to compare contents, as the last line does, but it depends on the order of keys. Testing libraries offer a proper "deep equal".
Logical operators
The logical operators are && (and),
|| (or) and ! (not). With booleans they
behave as in
Boolean logic, and && and || use
short-circuit evaluation: they stop as soon as the result is known. Two details make them
more powerful than plain true/false logic.
Truthy and falsy values
Every value counts as either true or false when it is used as a condition (see truthy and falsy). In JavaScript exactly these values are falsy:
false-
0,-0and0n(BigInt zero) ""(the empty string)nullandundefinedNaN
Everything else is truthy, including
"0", "false", " ", empty
arrays [] and empty objects {}.
!!value (not-not) or Boolean(value) shows
how a value counts:
const values = [0, "", null, undefined, NaN, "0", "false", [], {}];
for (const value of values) {
console.log(value, "is", Boolean(value) ? "truthy" : "falsy");
}
0 is falsy
is falsy
null is falsy
undefined is falsy
NaN is falsy
0 is truthy
false is truthy
[] is truthy
{} is truthy
&& and || return an operand
a || b returns a if it is truthy, and
otherwise b. a && b returns
a if it is falsy, and otherwise b. The
result is one of the two values, not a converted boolean. Only
! always returns a boolean:
console.log("North" || "Unknown");
console.log("" || "Unknown");
console.log("INC-20417" && "has an id");
console.log(0 && "never reached");
console.log(!"North", !0);
North Unknown has an id 0 false true
Older code uses || to supply a default value:
const region = input || "Unknown". That works for
strings, but it also replaces values that are valid yet falsy, such
as 0 or an empty string. The next operator fixes that.
The nullish coalescing operator ??
The
nullish coalescing operator, ?? (ES2020), returns its right-hand side only when
the left-hand side is null or
undefined ("nullish"). Every other value, including
0, "" and false, is kept:
const settings = { retries: 0, label: "", timeoutMs: undefined };
console.log(settings.retries || 3, settings.retries ?? 3);
console.log(settings.label || "(none)", settings.label ?? "(none)");
console.log(settings.timeoutMs || 5000, settings.timeoutMs ?? 5000);
settings.timeoutMs ??= 5000; // assign only if null or undefined
settings.retries ??= 3; // stays 0
console.log(settings);
3 0
(none)
5000 5000
{ retries: 0, label: '', timeoutMs: 5000 }
A configured retry count of 0 means "do not retry";
|| silently turns it into 3, while
?? respects it. Use ?? for defaults, and
|| only when you really want to replace every falsy
value.
Optional chaining ?.
Reading a property of undefined or
null throws a TypeError, and data from an
API often has gaps.
Optional chaining,
?. (ES2020), stops and returns
undefined as soon as the value on its left is
null or undefined, instead of failing:
const tickets = [
{ id: "INC-20417", assignee: { name: "Amara Okafor", team: "Service desk" } },
{ id: "INC-20418", assignee: null },
];
for (const ticket of tickets) {
const name = ticket.assignee?.name ?? "unassigned";
console.log(ticket.id, name);
}
console.log(tickets[1].assignee?.team.toUpperCase());
console.log(tickets[5]?.id);
console.log(tickets[0].notify?.("Ticket updated"));
INC-20417 Amara Okafor INC-20418 unassigned undefined undefined undefined
The three forms:
obj?.prop— read a property-
obj?.[expression]— read a property or array element by key or index -
func?.(args)— call a function only if it exists
Once ?. stops, the rest of the chain is skipped too,
which is why assignee?.team.toUpperCase() does not fail
when assignee is null. Combined with
?? it gives a default in one line, as in the loop
above.
Do not sprinkle ?. everywhere. If a value should always
be present, a missing value is a bug, and an error that points at it
is more useful than an undefined that surfaces three
functions later. Use ?. where a value is genuinely
optional.
Other operators
| Operator | What it does |
|---|---|
c ? a : b |
The conditional (ternary) operator: a if
c is truthy, otherwise b (see
conditional expression)
|
typeof x |
The type of a value as a string (see typeof) |
x instanceof C |
true if the object x was created
by the class or constructor C (or one that
inherits from it)
|
"key" in obj |
true if the object has a property with that
name
|
delete obj.key |
Removes a property from an object; returns
true when the property is gone afterward
|
const kwh = 412;
console.log(kwh > 400 ? "high usage" : "normal usage");
const readings = [118.2, 119.0];
const issuedOn = new Date(Date.UTC(2026, 9, 1));
console.log(readings instanceof Array, issuedOn instanceof Date);
console.log(readings instanceof Object, "text" instanceof String);
const ticket = { id: "INC-20417", status: "open", draftNote: "check meter" };
console.log("draftNote" in ticket);
console.log(delete ticket.draftNote);
console.log("draftNote" in ticket, ticket);
high usage
true true
true false
true
true
false { id: 'INC-20417', status: 'open' }
instanceof works for objects only:
"text" is a primitive string, not a
String object, so the result is false. Use
typeof for primitives and instanceof for
objects. delete removes properties, not variables; to
empty a variable, assign null or
undefined. Classes and constructors are explained in
Classes, Prototypes and this.
Precedence and associativity
When an expression has several operators, operator precedence decides which one is applied first, and associativity decides the order among operators of equal precedence. From highest to lowest, for the operators on this page:
| Operators | Associativity |
|---|---|
( … ) grouping |
— |
. ?. [ ]
( ) member access and calls
|
left to right |
x++ x-- (postfix) |
— |
! -x +x
++x --x typeof
delete (prefix)
|
— |
** |
right to left |
* / % |
left to right |
+ - |
left to right |
< <= >
>= in instanceof
|
left to right |
== != ===
!==
|
left to right |
&& |
left to right |
|| ?? |
left to right |
? : = +=
-= …
??=
|
right to left |
console.log(2 + 3 * 4, (2 + 3) * 4);
console.log(2 ** 3 ** 2); // right to left: 2 ** 9
console.log(20 - 5 - 3); // left to right: (20 - 5) - 3
console.log(!0 === true); // (!0) === true
let a, b;
a = b = 7; // right to left: b = 7, then a = 7
console.log(a, b);
14 20 512 12 true 7 7
Two combinations are so ambiguous that JavaScript refuses them with
a SyntaxError: a unary minus directly before
** (write (-2) ** 2 or
-(2 ** 2)), and mixing ?? with
&& or || without parentheses:
console.log((-2) ** 2, -(2 ** 2));
console.log(-2 ** 2);
SyntaxError: Unary operator used immediately before exponentiation expression. Parenthesis must be used to disambiguate operator precedence
Notice that not even the first line ran: a syntax error is found when the engine parses the file, before any of it executes.
Browser dialogs: alert, confirm and prompt
Browsers offer three built-in dialog boxes for simple interaction. They are host APIs of the browser, not part of the language, so they do not exist in Node.js. This code runs in a browser:
alert("Planned maintenance tonight from 22:00 to 23:00.");
const ok = confirm("Close ticket INC-20417?"); // true (OK) or false (Cancel)
console.log("Close confirmed:", ok);
const answer = prompt("Meter reading in kWh:", "0"); // a string, or null on Cancel
if (answer === null) {
console.log("Cancelled");
} else {
const kwh = Number(answer);
console.log(Number.isNaN(kwh) ? "Not a number" : `Reading: ${kwh} kWh`);
}
If the user clicks OK in the confirm dialog and then types 312.5, the console shows:
Close confirmed: true Reading: 312.5 kWh
-
alert(message)shows a message with an OK button and returnsundefined. -
confirm(message)shows OK and Cancel and returnstrueorfalse. -
prompt(message, default)shows a text field and returns what the user typed as a string, ornullwhen they press Cancel. Convert the string before calculating with it.
All three are modal and blocking: the page cannot be used and your script pauses until the user closes the dialog. Their look cannot be styled, and browsers may suppress them, for example in some embedded frames. Real applications therefore use their own dialogs and form fields built with HTML (see The DOM and Events). The three functions remain useful for quick experiments and appear on the JSE exam.