JavaScript Operators Explained: Arithmetic, Comparison, Logical, and the Modern Ones
An operator is a symbol that takes one or more operands and returns a value. + is an operator. So are ===, &&, ??, and ?.. JavaScript has a lot of them — and a few are surprisingly subtle, even for developers who have been writing JavaScript for years.
This lesson tours every operator category you'll encounter in modern JavaScript, with special attention to the four newer ones (nullish coalescing, optional chaining, logical assignment, exponentiation) that changed how everyday code reads in the last few language editions.
Arithmetic operators #
The usual suspects, plus one less-known one.
| Operator | Meaning | Example | Result |
|---|---|---|---|
+ |
Addition | 2 + 3 |
5 |
- |
Subtraction | 5 - 2 |
3 |
* |
Multiplication | 4 * 3 |
12 |
/ |
Division | 10 / 4 |
2.5 |
% |
Remainder ("modulo") | 10 % 3 |
1 |
** |
Exponentiation | 2 ** 10 |
1024 |
++ / -- |
Increment / decrement | let i = 0; i++; i |
1 |
The one that surprises people is %. It's remainder, not strictly mathematical modulo. The sign follows the dividend, not the divisor: -7 % 3 is -1, not 2. If you need true mathematical modulo, write ((n % m) + m) % m.
** (exponentiation) is the modern way to do powers. It replaced Math.pow(a, b) in ES2016. Both still work; ** is more readable.
The + operator overloading #
+ is the one arithmetic operator that does two completely different things depending on its operands:
2 + 3; // 5 — numeric addition
'a' + 'b'; // 'ab' — string concatenation
'2' + 3; // '23' — string concatenation wins when either side is a string
2 + '3'; // '23' — same
The rule: if either side is a string, + concatenates. Otherwise it adds. This is why 1 + 2 + '3' is '33' (numeric add, then string concat) and '1' + 2 + 3 is '123' (string concat all the way).
For predictable behavior, convert explicitly: Number(x) + Number(y) or \${x}${y}“.
Comparison operators #
There are six. The first four are uncontroversial. The last two have a notorious complication.
| Operator | Meaning | Example | Result |
|---|---|---|---|
< |
Less than | 2 < 3 |
true |
> |
Greater than | 2 > 3 |
false |
<= |
Less than or equal | 3 <= 3 |
true |
>= |
Greater than or equal | 3 >= 3 |
true |
=== |
Strict equality | 2 === 2 |
true |
!== |
Strict inequality | 2 !== 3 |
true |
== |
Loose equality (avoid) | '2' == 2 |
true |
!= |
Loose inequality (avoid) | '2' != 2 |
false |
Rule of thumb: use === and !==. Never use == and !=.
The loose-equality operators perform implicit type coercion before comparing — '2' == 2 is true because '2' gets coerced to the number 2. The rules for coercion are complex and surprising ([] == ![] is true, for instance). The strict operators do not coerce; they just compare like-for-like.
Lesson coverage of the coercion rules lives in Module 6. For now: strict equality is the safe default.
Comparing objects #
All comparison operators on objects compare by reference identity, not by content.
const a = { x: 1 };
const b = { x: 1 };
a === b; // false — different objects
a === a; // true — same object
If you need structural equality, use a deep-equal helper (JSON.stringify for simple cases, lodash.isEqual for production).
Logical operators #
Three primary operators, all of which short-circuit: they evaluate the second operand only if needed.
| Operator | Meaning | Returns |
|---|---|---|
&& |
Logical AND | The first falsy operand, or the last operand |
|| |
Logical OR | The first truthy operand, or the last operand |
! |
Logical NOT | The boolean opposite of its operand |
The surprising part: && and \|\| don't necessarily return true or false. They return one of their operands.
0 && 'hi'; // 0 — first operand is falsy, returns it
1 && 'hi'; // 'hi' — first is truthy, returns the second
0 || 'hi'; // 'hi' — first is falsy, returns the second
'a' || 'b'; // 'a' — first is truthy, returns it
This enables a common idiom: default values with ||.
const name = inputName || 'Anonymous'; // use 'Anonymous' if inputName is falsy
It also enables a footgun, which is why ES2020 introduced ?? — see below.
Truthy and falsy #
Logical operators care about the operand's truthiness, not its strict boolean value. The falsy values in JavaScript are exactly six:
false0(and-0,0n)''(empty string)nullundefinedNaN
Everything else is truthy. Including:
'0'(a non-empty string)'false'(also a non-empty string)[](an empty array is still an object){}(an empty object is still an object)
The array and object cases catch people out. if ([]) always runs the body. If you want "is this array non-empty," check arr.length > 0.
Nullish coalescing: ?? #
The || operator's defaulting behavior is broken for any value where 0, '', or false is a legitimate input.
const port = userPort || 3000; // BUG: if userPort is 0, you get 3000
const label = title || 'Untitled'; // BUG: if title is '', you get 'Untitled'
ES2020 added ?? (nullish coalescing) to fix this. It only falls back when the left side is null or undefined — not for other falsy values.
const port = userPort ?? 3000; // 0 stays as 0
const label = title ?? 'Untitled'; // '' stays as ''
Modern rule: use ?? for defaulting values. Use || only when you specifically want falsy-coalescing behavior.
Optional chaining: ?. #
Reading nested properties used to require defensive checks at every level.
// Old
const street = user && user.address && user.address.street;
ES2020's ?. does the same thing in one symbol:
const street = user?.address?.street;
At each ?., if the value to its left is null or undefined, the entire expression short-circuits to undefined. Otherwise it proceeds.
Three forms:
obj?.prop // property access
obj?.[expr] // computed access
fn?.(args) // call (no-op if fn is null/undefined)
A worked example:
const street = user?.address?.street ?? 'No street on file';
const handler = config?.onError?.(err); // only calls if onError exists
const first = items?.[0]; // safe array access
The combination of ?. and ?? is the modern idiom for "reach into a possibly-missing structure with a default."
Logical assignment: &&=, ||=, ??= #
ES2021 added three shorthand operators that combine logical operators with assignment.
let a = null;
a ??= 5; // a is 5 (was nullish, so assign)
let b = 10;
b ??= 5; // b stays 10 (was not nullish)
let c = 'hi';
c ||= 'fallback'; // c stays 'hi'
let d = '';
d ||= 'fallback'; // d becomes 'fallback'
let flag = true;
flag &&= someCheck(); // only runs someCheck if flag is truthy
The one you'll use most is ??=, for "set this if it doesn't already have a value" patterns.
Assignment operators #
Besides =, there are arithmetic and bitwise shorthands.
| Operator | Same as |
|---|---|
x += y |
x = x + y |
x -= y |
x = x - y |
x *= y |
x = x * y |
x /= y |
x = x / y |
x %= y |
x = x % y |
x **= y |
x = x ** y |
x ??= y |
x = x ?? y |
| `x | |
x &&= y |
x = x && y |
They're conveniences. Use them when they read naturally.
Ternary: ?: #
The one and only conditional expression operator.
const label = isAdmin ? 'Admin' : 'User';
Read as: condition ? value-if-truthy : value-if-falsy.
Ternaries can chain, but readability falls off a cliff fast:
// Fine
const x = a ? 1 : b ? 2 : 3;
// Going too far
const y = a ? (b ? (c ? 1 : 2) : 3) : (d ? 4 : 5);
When a ternary chain gets nested, switch to if/else or a switch. We cover those in Lesson 1.5.
Bitwise operators (briefly) #
For most developers these are a footnote. They operate on the 32-bit integer representation of numbers.
5 & 3; // 1 — bitwise AND
5 | 3; // 7 — bitwise OR
5 ^ 3; // 6 — bitwise XOR
~5; // -6 — bitwise NOT
5 << 1; // 10 — left shift
5 >> 1; // 2 — sign-propagating right shift
5 >>> 1; // 2 — zero-fill right shift
Useful for low-level work (color manipulation, flags, encoding). Most application code never touches them.
The typeof, instanceof, in, and delete operators #
Four single-keyword operators worth knowing:
typeof 'hi'; // 'string' — value type as a string
user instanceof User; // true if user was created via `new User(...)`
'name' in user; // true if 'name' is a property of user (own or inherited)
delete user.name; // removes the 'name' property; returns true
typeof we covered in Lesson 1.3. instanceof and in come up in Module 4 (Classes & OOP). delete is rare — usually you'd assign undefined or restructure instead.
Operator precedence: don't memorize, parenthesize #
JavaScript has 17 levels of operator precedence. Memorizing the full table is a waste of time. Two practical rules:
- Arithmetic binds tighter than comparison binds tighter than logical.
a + b > c && dis((a + b) > c) && d. - When in doubt, parenthesize. Code-review-time clarity beats clever-precedence-knowledge any day.
A summary of the modern toolkit #
The operators you should reach for first in 2026:
===/!==for equality. Never==/!=.??for defaulting. Use||only when you specifically want falsy-coalescing.?.for safe property access into possibly-missing structures.??=,||=,&&=for shorthand assignment.**for exponentiation instead ofMath.pow.- Ternary
?:for short conditional expressions.if/elsefor anything longer.
These six categories cover roughly 95% of operator use in modern code.
What's next #
Lesson 1.5 covers conditional statements — if/else, switch, the ternary as a statement-of-conditions, and the short-circuit patterns that lean on the logical operators we just covered. Combined with this lesson, you'll have everything you need to express any decision a program needs to make.
Try it yourself #
The ?? vs || distinction is one of the easiest modern-vs-old differences to feel in practice. Try this:
const port = 0;
console.log(port || 3000);
console.log(port ?? 3000);js_sandboxIt logs 3000 then 0.•
port || 3000 → 0 is falsy, so || falls through to the default. Bug — the user said port 0 explicitly.•
port ?? 3000 → 0 is not nullish, so ?? returns it. Correct — port 0 is preserved.This is the case that motivated
?? being added to the language.One tiny code change — || to ?? — fixes a real-world bug that's lived in countless config-loading functions. That's the value of knowing the modern operator set.
Up next in JavaScript
More from this topic
Enjoyed this article?
Get new JavaScript tutorials delivered. No spam — just code-first articles when they ship.


