JavaScript Operators by Intent
JavaScript operators are easier to learn when you stop sorting them by symbol and start sorting them by intent. +, ===, &&, ??, ? :, and += are not one big drawer of punctuation. Each one answers a different kind of question: calculate a value, compare two values, choose a fallback, combine conditions, update state, or inspect what something is.
The bug usually starts when an operator is technically valid but says the wrong thing.
Table of contents
- Choose the operator by the job
- Calculate a value
- Join text deliberately
- Compare values
- Combine conditions
- Choose one value
- Provide a fallback
- Update existing state
- Inspect a value
- Access values safely
- Common operator mixups
- Key Takeaways
Choose the operator by the job
When you read an expression, ask what job the operator is doing:
| Intent | Common operators | Result shape |
|---|---|---|
| Calculate a number | +, -, *, /, %, ** | Number |
| Join text | + | String |
| Compare values | ===, !==, <, >, <=, >= | Boolean |
| Combine conditions | &&, ` | |
| Choose between two values | condition ? a : b | One of two values |
| Provide a fallback | ??, ` | |
| Update a variable | =, +=, -=, *=, /=, ++, -- | Assigned or updated value |
| Inspect a value | typeof, in, instanceof | Usually boolean or type string |
| Safely access a path | ?. | Property value or undefined |
That table is more useful than memorizing a symbol list. Operators are tiny, but they carry meaning. Good code makes that meaning obvious.
Calculate a value
Arithmetic operators create numbers from other numbers:
const subtotal = 24 * 3;
const shipping = 6;
const total = subtotal + shipping;
console.log(total); // 78
% gives the remainder after division:
const queuePosition = 18;
const isEvenPosition = queuePosition % 2 === 0;
console.log(isEvenPosition); // true
** raises a number to a power:
const area = 5 ** 2;
console.log(area); // 25
Arithmetic intent is simple: the expression should describe a calculation. If the code is really asking a yes-or-no question, add a comparison and make the boolean explicit.
Join text deliberately
The + operator has two jobs in JavaScript. With numbers, it adds. With strings, it concatenates.
console.log(5 + 3); // 8
console.log("5" + 3); // "53"
That second line is not operator precedence being weird. It is JavaScript choosing string concatenation because one side is already a string.
The naive version looks harmless:
const cartCount = "5";
const bonusItems = 3;
const totalItems = cartCount + bonusItems;
console.log(totalItems); // "53"
If the intent is numeric addition, convert the value first:
const totalItems = Number(cartCount) + bonusItems;
console.log(totalItems); // 8
If the intent is text, template literals usually read better:
const label = `Items: ${totalItems}`;
For string interpolation details, see Template Literals.
Compare values
Comparison operators answer yes-or-no questions.
const age = 16;
const isTeen = age >= 13 && age < 20;
console.log(isTeen); // true
Use strict equality by default:
console.log(5 === 5); // true
console.log("5" === 5); // false
Loose equality can coerce values before comparing them:
console.log("5" == 5); // true
That is legal JavaScript, but it asks the reader to track hidden conversion rules. Most of the time, the clearer fix is to convert deliberately, then compare strictly:
const typedAge = "18";
const canVote = Number(typedAge) >= 18;
console.log(canVote); // true
Comparisons should read like rules. If the rule has a boundary, include it intentionally with >= or <= instead of hoping the next person guesses the edge case.
Combine conditions
Logical operators combine decisions:
const age = 16;
const hasGuardianApproval = true;
const canAccessWorkshop = age >= 13 && (age >= 18 || hasGuardianApproval);
console.log(canAccessWorkshop); // true
&& means every required condition must pass. || means at least one option may pass. ! flips a boolean:
const isBlocked = false;
const canContinue = !isBlocked;
console.log(canContinue); // true
The trap is that && and || return original values, not guaranteed booleans:
console.log("Ada" && "active"); // "active"
console.log("" || "Guest"); // "Guest"
That behavior is useful for fallbacks and guards, but it can surprise you if you expected a literal true or false. The Truthy and Falsy Values guide covers the coercion rules under these operators.
Choose one value
Use the ternary operator when one condition chooses between two values:
const isPublished = false;
const statusLabel = isPublished ? "Published" : "Draft";
console.log(statusLabel); // "Draft"
That is the right intent: one condition, two possible values.
This is the wrong shape:
isPublished ? publishPost() : saveDraft();
That code performs actions. An if statement says that more clearly:
if (isPublished) {
publishPost();
} else {
saveDraft();
}
The JavaScript Ternary Operator article goes deeper on where this operator helps and where it turns smug.
Provide a fallback
Fallback operators answer a different question: "If this value is missing or unusable, what should I use instead?"
|| falls back on any falsy value:
const savedPageSize = 0;
const pageSize = savedPageSize || 20;
console.log(pageSize); // 20
That is correct only if 0 should be treated as unusable.
?? falls back only on null or undefined:
const savedPageSize = 0;
const pageSize = savedPageSize ?? 20;
console.log(pageSize); // 0
Use ?? when 0, "", or false are valid values. Use || when any falsy value should trigger the fallback. That one distinction prevents a surprising number of tiny production bugs.
Update existing state
Assignment operators change a variable:
let balance = 200;
balance += 45;
balance -= 70;
balance -= 5;
console.log(balance); // 170
The long form is equivalent:
balance = balance - 5;
Shorthand assignment is best when the intent is "move this value forward from where it is now." Scores, balances, counters, totals, and loop indexes are common examples.
++ and -- are even narrower: update by exactly one.
let count = 4;
const first = count++;
const second = ++count;
console.log(first); // 4
console.log(second); // 6
console.log(count); // 6
Postfix count++ gives back the old value, then updates. Prefix ++count updates first, then gives back the new value. If that distinction matters to the surrounding expression, consider splitting the update onto its own line. Your future self has enough to do.
Inspect a value
Some operators tell you about a value instead of transforming it:
console.log(typeof "Ada"); // "string"
console.log(typeof 42); // "number"
Use in to check whether a property exists on an object or its prototype chain:
const user = { name: "Ada", role: "admin" };
console.log("role" in user); // true
Use instanceof when you need to check whether an object comes from a constructor:
const createdAt = new Date();
console.log(createdAt instanceof Date); // true
These operators are not about display or calculation. They are about type, shape, and object relationships.
Access values safely
Optional chaining, ?., accesses a property only when the value before it is not null or undefined:
const user = {
profile: {
city: "London",
},
};
console.log(user.profile?.city); // "London"
console.log(user.settings?.theme); // undefined
Use it when a path is genuinely optional. Do not use it as a blanket way to hide broken assumptions.
const button = document.querySelector("#save");
button?.addEventListener("click", save);
That is fine if the save button is optional on this page. If the page cannot work without it, a clear failure during development is better than a silent missing click handler.
Common operator mixups
The operator is often valid. The intent is wrong.
| Mistake | Why it fails | Better shape |
|---|---|---|
| `count | 10when0` is valid | |
value == "5" | Allows hidden coercion | Convert, then use === |
isReady ? start() : stop() | Ternary hides actions | Use if |
items && items.length | Returns items when falsy, not a boolean | items.length > 0 when items must be an array |
score = score + 1 > max | Assignment and comparison are mixed | Name the next score first |
user?.profile?.name everywhere | May hide required missing data | Guard optional paths, fail clearly for required paths |
Here is the last numeric example fixed:
let score = 9;
const max = 10;
const nextScore = score + 1;
const wouldExceedMax = nextScore > max;
if (!wouldExceedMax) {
score = nextScore;
}
console.log(score); // 10
The code is longer than a single expression. It is also much harder to misunderstand, which is the point.
Key Takeaways
- Pick operators by intent: calculate, compare, combine, choose, fall back, update, inspect, or access.
+is both numeric addition and string concatenation, so convert deliberately when data arrives as text.||and??are not interchangeable.??preserves valid falsy values.- Ternaries are for choosing values, not hiding side effects.
- When an expression mixes jobs, split it into named steps before the clever version wins an argument it did not deserve.