Mert Özen Further With Every Line
Frontend JavaScript Basics — #05

JavaScript Operators: Arithmetic, Equality, Logical Operators and Truthy/Falsy

Mert Özen Nov 3, 2026 9 min 4 views
JavaScript Operators: Arithmetic, Equality, Logical Operators and Truthy/Falsy

We cover arithmetic and assignment operators, comparisons, == versus === and coercion, truthy/falsy values, short-circuiting with && and ||, the ?? operator and precedence pitfalls.

Version note: The code in this post targets ES2026 and current browsers (Chrome, Edge, Firefox, Safari).

Predict First

Four short lines, four questions:

console.log(0 || "default");
console.log(0 ?? "default");
console.log("5" == 5);
console.log("5" === 5);

The first two lines look almost identical, and so do the last two. But are their outputs identical too? Make a guess for each of the four lines before you keep reading. We'll explain the answers one by one as we get to know the operators involved.

Arithmetic Operators

The starting point is familiar: the four basic operations. Paste this into your browser's console and try it:

console.log(7 + 3);
console.log(7 - 3);
console.log(7 * 3);
console.log(7 / 2);
console.log(7 % 3);
console.log(2 ** 10);

The outputs are 10, 4, 21, 3.5, 1 and 1024. Division doesn't do integer division; 7 / 2 gives 3.5 straight away. % is the remainder operator: divide 7 by 3 and the remainder is 1. It's also the shortest way to tell whether a number is even or odd; if num % 2 is zero, the number is even. The exponentiation operator ** arrived with ES2016; 2 ** 10 means 2 to the power of 10.

As we saw earlier, the + operator has a second job. If either side is a string, it doesn't add; it concatenates. The other arithmetic operators don't do this; they try to turn the string into a number:

console.log("10" + 2);
console.log("10" - 2);
console.log("3" * "4");
console.log(+"42");
console.log(+"hello");

In order: "102", 8, 12, 42 and NaN. A lone + in front of a value is a short way to convert it to a number; a string that doesn't look like a number gives NaN, "not a number." Dividing a positive number by zero doesn't throw an error either; it returns Infinity. We'll cover that in detail in the post on numbers.

Assignment, Increment and Decrement

You know the = operator from the post on variables. Updating a variable based on its own value is so common that there are shorthand forms for it:

let points = 10;
points += 5;
points -= 3;
points *= 2;
points /= 4;
console.log(points);

The result is 6. points += 5 is short for points = points + 5, and the others follow the same logic. It works with strings too: message += "!" adds an exclamation mark to the end of the message.

To add or subtract one, there's ++ and --. A small but important detail hides here: whether the operator sits before or after the variable.

let n = 5;
const a = n++;
const b = ++n;
console.log(a, b, n);

The output is 5 7 7. n++ hands over the old value first and then increments; that's why a became 5 and n went up to 6. ++n increments first and then hands over the new value; n became 7 and b got 7 as well. To avoid dealing with this difference, using ++ on its own line is usually the most readable choice.

Comparison: Greater, Less and Equal

Comparison operators always return a boolean: true or false.

console.log(5 > 3);
console.log(5 <= 5);
console.log("pear" < "apple");
console.log("10" < "9");
console.log("10" < 9);

The first two are true, as you'd expect. The third is false, because strings are compared in dictionary order, character by character: "p" comes after "a." The fourth, surprisingly, is true. Since both sides are strings, the engine doesn't think in numbers; it compares the first characters, and since "1" comes before "9," "10" comes out smaller. In the fifth, one side is a number, so the string is converted to a number and a real numeric comparison happens: 10 is not less than 9, so the result is false.

The lesson: convert strings that look like numbers into actual numbers before comparing them. Also know that uppercase letters sort before lowercase ones; the answer to "B" < "a" is true.

== vs ===: A First Look at Coercion

Let's get to the last two lines of the opening question. "5" == 5 gives true, "5" === 5 gives false.

=== is strict equality: it returns true if both the type and the value of the two sides match. A string and a number have different types, so the answer is false from the start. == is loose equality: if the types differ, it first converts one or both of them, then compares. This automatic conversion is called coercion. In "5" == 5, the string was converted to a number and 5 was compared with 5.

You can picture two doormen. === checks your ID; both the photo and the name have to match. == lets in anyone who "looks about right." The problem is that the rules for "looking about right" are hard to predict:

console.log(0 == "");
console.log(0 == "0");
console.log("" == "0");
console.log(null == undefined);
console.log(null == 0);
console.log(NaN == NaN);

Output: true, true, false, true, false, false. Look closely at the first three. 0 equals both "" and "0", yet "" and "0" aren't equal to each other. That can't happen in math; with ==, it does. The last line is interesting too: NaN isn't equal to anything, not even itself, and === gives the same result. We'll see the right way to check whether a value is NaN in the post on numbers.

The rule is simple: always use === and !==. If you need a type conversion, do it explicitly, yourself. That way, whoever reads the code doesn't have to guess what the engine is doing behind the scenes.

Truthy and Falsy

Before moving on to the logical operators, we need one more concept. When JavaScript uses a value somewhere a boolean is expected, it interprets it as either true or false. Values that behave like false are called falsy; everything else is truthy. The falsy list is short and worth memorizing:

console.log(Boolean(false));
console.log(Boolean(0), Boolean(-0), Boolean(0n));
console.log(Boolean(""));
console.log(Boolean(null), Boolean(undefined));
console.log(Boolean(NaN));

They all give false. That's false, 0, -0, 0n, the empty string "", null, undefined and NaN. Everything outside these eight values is truthy. Here come the surprises:

console.log(Boolean("0"), Boolean("false"), Boolean(" "));
console.log(Boolean([]), Boolean({}));

All true. A string with "0" in it isn't empty, so it's truthy. An empty array and an empty object are truthy too, because they're objects, and objects always count as truthy. The !!value pattern you'll often see in code does the same job as Boolean(value); it says "not" twice and turns the value into a plain boolean.

&&, || and !: Short-Circuiting

! means "not" and always returns a boolean: !true gives false, and !"" gives true. && means "and," || means "or." But in JavaScript these two work differently from what you might think: they don't return a boolean, they return one of the operands itself.

console.log("" || "Guest");
console.log("Ada" || "Guest");
console.log("Ada" && "Welcome");
console.log(null && "Welcome");

Output: Guest, Ada, Welcome, null. || looks for the first truthy value from left to right and returns it the moment it finds it; if none is truthy, it gives the last value. && looks for the first falsy value; if they're all truthy, it gives the last one.

The "the moment it finds it" part matters. The engine stops as soon as it knows the answer and never looks at the right side. This is called short-circuiting. Think of a fuse in an electrical circuit: once the current is cut, nothing reaches the rest of the circuit.

const isLoggedIn = true;
isLoggedIn && console.log("Loading profile...");

const userName = "";
const displayName = userName || "Guest";
console.log(displayName);

If isLoggedIn were false, the console.log would never run. The second part is a default value pattern: if the user name is empty, "Guest" is used. Now we can explain the first line of the opening question: 0 || "default" gives "default", because 0 is falsy and || moved on to the next value.

??: Only When It's Really Empty

Using || for default values has a flaw. Since 0 and "" are falsy too, they get replaced by the default even when they're valid values:

const quantity = 0;
console.log(quantity || 1);
console.log(quantity ?? 1);

The user may have deliberately chosen 0 items, but || turns that into 1. The nullish coalescing operator ??, which arrived with ES2020, solves this: it uses the right side only if the left side is null or undefined. 0, "" and false pass through as they are. That's why the second line gives 0, and it's also the answer to the second opening question: 0 ?? "default" gives 0.

The practical distinction: if you mean "use the default when it's empty, zero or false," use ||; if you mean "use the default when there's no value at all," use ??. Most of the time, the second one is what you're after.

There's also a syntax rule: you can't use ?? in the same expression as || or && without parentheses. Write null || undefined ?? "x" and you get a SyntaxError. You have to state explicitly with parentheses which part runs first: (null || undefined) ?? "x".

Operator Precedence and Common Pitfalls

When an expression contains several operators, precedence rules decide which one runs first. As in math, multiplication and division come before addition: 2 + 3 * 4 gives 14, while (2 + 3) * 4 gives 20. On the logical side, && runs before ||. ** works from right to left: 2 ** 3 ** 2 gives 512, because 3 ** 2 is calculated first. You don't need to memorize the whole table. Use parentheses wherever you're unsure; the code works correctly and tells the reader what you meant.

Chaining comparisons. 1 < 2 < 3 gives true, but 3 > 2 > 1 gives false. The engine reads from left to right: 3 > 2 becomes true, then it asks true > 1. Converted to a number, true is 1, and 1 isn't greater than 1. Join the two comparisons with &&: 3 > 2 && 2 > 1.

Raising a negative number to a power without parentheses. Write -2 ** 2 and the result isn't 4 or -4; it's a SyntaxError. The language makes you resolve the ambiguity: write (-2) ** 2 or -(2 ** 2).

Adding numbers that come from a form. Values typed by the user usually arrive as strings, and "10" + "5" gives you "105". Convert them to numbers before adding.

Trusting comparisons with null. null == 0 gives false, but null >= 0 gives true, because >= converts the value to a number, while == applies a special rule for null. Before comparing a value that might be empty with a number, check separately whether it's empty.

A Small Experiment

★☆☆ Use % to find out whether a number is even or odd and print the result to the console. Try the same for 0, 7 and -4.

★★☆ Predict the output of each line before running it:

console.log("5" + 1 - 1);
console.log(null ?? 0 || 10);
console.log("" || 0 || "last");
console.log(false === 0, false == 0);

Hint: if you paste these four lines in one go, none of them runs. Why? Which line do you need to fix, and how, so that the rest runs too?

★★★ A variable named stock can be 0, 5, null or undefined. If there's no value at all (null or undefined), print "Unknown" to the console; if it's 0, print 0 as it is. Explain why writing this with || goes wrong, then write the correct version with ?? and try it with all four values.

Each operator asks a question and produces an answer. In the next post we'll make decisions with those answers: with if/else, the ternary operator and switch, we'll choose which path the code takes.