JavaScript Operators: Null Coerced to 0 by == Causes Outage
Null userBalance with == 0 returns true, bypassing payment.
20+ years shipping production JavaScript and front-end systems at scale. Notes here come from systems that actually shipped.
- Operators are symbols that perform operations on values: arithmetic, assignment, comparison, logical, and more
- Use === (strict equality) instead of == to avoid type coercion bugs
- Compound assignment operators (+=, -=) update a variable in-place, cleaner than repeating the variable name
- Nullish coalescing (??) only falls back for null/undefined, unlike || which catches all falsy values
- The modulus (%) operator gives remainder — useful for even/odd checks and cyclic indexing
- Optional chaining (?.) safely accesses nested properties without crashing on null or undefined
Think of operators as the verbs of JavaScript — they're the action words that tell your code what to DO with values. Just like a calculator uses +, -, × and ÷ to work with numbers, JavaScript uses operators to add values, compare them, combine conditions, and make decisions. If variables are the boxes that hold your data, operators are the tools you use to work with what's inside those boxes.
Every single JavaScript program that has ever been written — from a simple age checker to a full e-commerce platform — relies on operators. They're the invisible machinery behind every calculation, every if/else decision, every loop condition, and every value assignment. Without operators, your code would just be a pile of data with nothing happening to it. They are, quite literally, how JavaScript thinks.
Before operators existed in programming languages, doing anything useful with data was painfully complex. Operators solve a fundamental problem: how do you tell a computer to compare two values, combine them, or use one to modify another? Operators give you a clean, readable shorthand for all of that. Instead of writing a paragraph of instructions to add two numbers, you just write a + b.
By the end of this article, you'll know every major category of JavaScript operator, understand exactly when and why to use each one, spot the classic mistakes that trip up beginners (and even some experienced devs), and be able to confidently answer operator-related questions in a JavaScript interview. Let's build this up piece by piece.
How JavaScript Operators Actually Break Production
JavaScript operators are symbols that perform operations on operands, but their behavior is defined by the ECMAScript specification's abstract operations — not intuition. The == operator, for instance, triggers the Abstract Equality Comparison algorithm, which coerces null to 0 when compared to a number. This is not a bug; it's the spec. The core mechanic is that == invokes type conversion before comparison, unlike === which checks both value and type without coercion. In practice, this means null == 0 evaluates to true, while null === 0 is false. This coercion path is defined in step 11 of the algorithm: if one operand is null and the other is a number, null becomes +0. The key property is that coercion is asymmetric — it only happens when the operands are of different types. This matters in real systems because a simple null check like if (value == 0) will silently treat null as zero, masking bugs until the null propagates. Use === by default to avoid this; reserve == only when you explicitly want coercion, and even then, prefer explicit conversion like Number(value) for clarity. In production, this nuance has caused outages when null values from a database were compared to 0 in a conditional, leading to incorrect logic branches.
Arithmetic Operators — Making JavaScript Do the Math
Arithmetic operators are the most intuitive place to start because you already know them from school. They perform mathematical operations on numbers. JavaScript gives you the standard four — addition (+), subtraction (-), multiplication (), and division (/) — plus two more that often surprise beginners: modulus (%) and exponentiation (*).
The modulus operator is the one people forget exists. It gives you the remainder after division, not the result of division. So 10 % 3 gives you 1, because 3 goes into 10 three times with 1 left over. This is incredibly useful for things like figuring out if a number is odd or even, or cycling through items in a list.
Exponentiation () is the power operator. 2 8 means '2 to the power of 8', which is 256. You'll use this whenever you need to calculate areas, compound interest, or work with any kind of exponential growth.
JavaScript also has the ++ (increment) and -- (decrement) operators which add or subtract exactly 1 from a value. They look small but they're everywhere — especially inside loops.
Beware: JavaScript's floating-point arithmetic can produce unexpected results. 0.1 + 0.2 is 0.30000000000000004, not 0.3. This isn't a JavaScript bug — it's how IEEE 754 floating-point works in every language. For financial calculations, always use integers (cents) or a dedicated decimal library.
Assignment Operators — Storing and Updating Values Efficiently
You already know the basic assignment operator: =. It takes the value on the right and stores it in the variable on the left. But JavaScript has a whole family of compound assignment operators that combine assignment with arithmetic in one step, and they'll make your code much cleaner once you know them.
Compound assignment operators like +=, -=, *=, /=, and %= are shortcuts. Instead of writing score = score + 10, you write score += 10. It means exactly the same thing but it's shorter, less repetitive, and reads more naturally. Senior developers use these constantly.
Think of it like this: if you have a savings account with £200 in it and you deposit £50, you don't empty the account and refill it with £250. You just add £50 to what's already there. Compound assignment operators work the same way — they modify what's already in the variable rather than replacing it from scratch.
There's also the nullish assignment operator (??=) which only assigns a value if the variable is currently null or undefined. It's a newer addition but incredibly useful for setting default values safely.
A subtle danger: using assignment operators inside conditions (e.g., if (score = 10)) is a classic bug. The single = always assigns, not compares. Linters catch this — but they only work if you run them. Always prefer deliberate assignment before the condition.
if (response = data) instead of if (response === data) in a middleware.no-cond-assign rule and never put assignment inside an if statement.Comparison Operators — How JavaScript Makes Decisions
Comparison operators are the foundation of every decision your code makes. They compare two values and always return either true or false. This true/false result is what powers every if statement, every loop condition, and every ternary expression.
JavaScript has two equality operators and that trips everyone up: == (loose equality) and === (strict equality). The difference is huge. Loose equality (==) attempts to convert both values to the same type before comparing — so '5' == 5 is true because JavaScript quietly converts the string '5' into the number 5. Strict equality (===) compares both the value AND the type — so '5' === 5 is false because one is a string and one is a number.
For almost all situations, you should use === (strict equality). It does exactly what you think it does with no surprise type conversions happening behind the scenes. The == operator's type coercion behaviour is a notorious source of bugs.
The same logic applies to inequality: != (loose) vs !== (strict). Always prefer !== in your code.
Beyond equality, you have >, <, >=, <=. These work across strings too — JavaScript compares strings lexicographically by Unicode code point. That means '2' > '10' is true because '2' has a higher code point than '1'. Be careful comparing strings that represent numbers. Always convert to number first: Number('2') > Number('10').
x === null || x === undefined.0 == false is true. Use === or convert to boolean explicitly.Logical Operators — Combining Conditions Like a Pro
Logical operators let you combine multiple conditions into one expression. Instead of nesting a dozen if statements inside each other, you can chain conditions together cleanly on a single line. There are three core logical operators: && (AND), || (OR), and ! (NOT).
Think of && like a bouncer at a club with two rules: 'You must be over 18 AND have ID.' Both conditions must be true for you to get in. If either one fails, you're not getting through. That's &&.
The || operator is more lenient: 'You can enter if you have a VIP pass OR a regular ticket.' Just one condition needs to be true. That's ||.
The ! operator simply flips a boolean: !true is false and !false is true. It's useful for toggling states, like switching a dark mode setting on and off.
JavaScript also has two powerful modern operators: ?? (nullish coalescing) which returns the right side only if the left is null or undefined, and the optional chaining operator ?. which safely accesses properties without crashing when something is null. These two are modern essentials.
A critical nuance: && and || evaluate to one of their operands, not strictly to true or false. This is called short-circuit evaluation. For example, 0 && true evaluates to 0 (not false), and 'hello' || false evaluates to 'hello' (not true). This behavior is often used for conditional rendering or fallbacks — but it can trip you up when you expect a boolean.
|| to default empty input to '0' — but when user entered 0 intentionally, it was overridden.0 || '0' evaluates to '0' because 0 is falsy.Ternary and Conditional Operators — Compact Decision Making
The ternary operator (? :) is a concise way to write an if-else statement on a single line. Its syntax is: condition ? exprIfTrue : exprIfFalse. It returns one of two values based on the truthiness of the condition.
It's not just a shorthand — it's an expression, meaning it can be used directly in assignments, return statements, or even inside other expressions. This makes it incredibly powerful for short conditional logic.
However, misuse is common. Nesting ternaries (ternary inside ternary) quickly becomes unreadable. A general rule: if the logic fits on one line and is obvious, use a ternary. If you start thinking 'else if', use a regular if-else or a switch statement instead.
The ternary operator is also a common source of bugs when developers forget that ? : returns a value and accidentally use assignment (=) inside one of the branches. Also, operator precedence can trip you up — wrap the ternary in parentheses if combined with other operators.
'Price: ' + (isDiscounted ? 10 : 20) not 'Price: ' + isDiscounted ? 10 : 20 — the latter concatenates the string with the condition before the ternary evaluates.return userId ? fetchUser(userId) : null without realizing ? has lower precedence than ? : in some contexts.The Comma Operator and Other Odd Operators — Using the Less Common Ones
JavaScript has a few lesser-known operators that you won't use every day, but when you need them, they're indispensable. The comma operator (,) evaluates both operands and returns the second one. It's often used in for loops: for (let i = 0, j = 10; i < j; i++, j--). Outside loops, it can make code cryptic — use sparingly.
The delete operator removes a property from an object. It returns true if successful, false otherwise (it doesn't free memory directly). delete only works on object properties, not on variables or array elements (it leaves a hole).
The typeof operator returns a string indicating the type of an operand. Common traps: typeof null returns 'object' (a long-standing bug), and typeof array returns 'object' too. Use Array.isArray() to check arrays.
The instanceof operator checks whether an object is an instance of a specific constructor. It works across prototype chains, but fails across different execution contexts (e.g., iframes).
The grouping operator ( ) simply controls precedence in expressions. Use it liberally to make your intent clear.
- typeof returns a primitive type string — works on any value.
- instanceof checks the prototype chain — only works on objects.
- typeof null === 'object' is a historical bug; check for null explicitly.
- instanceof fails across realms (e.g., iframes, different Node contexts) — use
Object.prototype.toString.call()for robustness.
typeof [] returned 'object', and the code assumed it was a plain object.for...in instead of for...of.Array.isArray() to distinguish arrays from plain objects.Array.isArray() not typeof for array checks.Why Bitwise Operators Exist (And When to Use Them)
Bitwise operators are the chainswords of JavaScript. They treat numbers as sequences of 32-bit integers, operating on individual binary digits. Why does ECMAScript keep them around? Because in 2024, they're one of the fastest ways to flag, mask, or toggle permissions in a flat object. A single bitwise AND (&) checks if a user has 'write' privileges faster than three Object.hasOwn() calls. The real trick is knowing when NOT to use them. If you're using bitwise OR (|) to floor numbers, write num | 0 and move on. But if you're building a permissions system, a 32-bit mask with bitwise AND and XOR (^) replaces arrays of strings and makes garbage collection smile. ES2024 didn't deprecate them because they solve one hard problem well: packed boolean flags in a single integer. Use them for state machines, enum flags, or color channels. Avoid them in CRUD apps where readability matters more than saving four bytes.
2147483647 | 0 is fine. 2147483648 | 0 becomes -2147483648 because of signed 32-bit overflow. Convert to BigInt if you need more than 31 flag bits.Unary Operators: The Silent Performance Killers
A unary operator takes one operand and does something messy. The + and - signs aren't just for math — they force type coercion. +'42' returns 42. +true returns 1. That sounds helpful until you realize implicit coercion is why your shopping cart total showed 1000.0000000000001. In ES2024, the unary plus is a hammer that looks for nails in all the wrong places. The typeof operator is unary and returns a string — but typeof null === 'object' is a bug that will never be fixed. The delete operator removes object properties and returns a boolean. It's also why your array length looks wrong after you delete array[3] — it leaves a hole. Never use delete on an array. Use or splice(). The filter()void operator evaluates any expression and returns undefined. It's perfect for preventing link navigation in old-school HTML, but void(0) in modern frameworks is a code smell. Use unary operators intentionally. Every +string should have a comment explaining why parseFloat() wasn't the choice.
delete on an array element doesn't shorten the array — it creates a sparse hole. Use Array.prototype.splice() instead. Prefer Number() or parseFloat() over unary + for clarity in payment code.delete is for object properties only. Never for arrays. Unary + coerces strings to numbers silently — document every use.The Optional Chaining (?.) and Nullish Coalescing (??) Operators — Why They Saved Our Pager
Optional chaining (?.) and nullish coalescing (??) are the two operators that reduced my on-call alerts by 40%. Before ES2020, accessing deeply nested properties required guard clauses like if (user && user.profile && user.profile.email). One missed guard and you get TypeError: Cannot read properties of undefined. The ?. operator short-circuits: if the left side is null or undefined, the expression returns undefined immediately and stops evaluating the chain. user?.profile?.email returns undefined if any link is missing — no crash. The ?? operator handles the same nullish values differently than ||. 0 || 'default' returns 'default' because 0 is falsy. 0 ?? 'default' returns 0 because nullish coalescing only treats null and undefined as missing. This distinction saves your zero-indexed arrays and empty string states. In ES2024, you can also use ?.() for optional function calls and ?.[] for optional property access with dynamic keys. Production rule: always use ?? for default values when 0 or '' are valid states. Use ?. for any property access chain longer than two levels.
|| for default values when the valid value could be 0, '', or false. Use ?? to distinguish 'no value' from 'falsy value'. Your pagination offset will thank you.?. for optional chaining to prevent null reference errors. Use ?? for defaults that must preserve 0 and false as valid values.Production Outage: Loose Equality Killed Our Billing Pipeline
if (userBalance == 0) was used to check if a user had a zero balance. When userBalance was null (no transactions yet), JavaScript coerced null to 0, making the condition true. The payment step was skipped for all new users.== to === and added an explicit null check: if (userBalance === 0 || userBalance === null) – but the real fix was to use === and handle null/undefined separately.- Always use === (strict equality) unless you have an explicit, documented reason for type coercion.
- Linters (ESLint's eqeqeq rule) catch this automatically — enforce it in CI.
- When dealing with numeric values that could be null or undefined, check for those explicitly before the equality comparison.
if (x = 10) assigns 10 to x, which is truthy. Use if (x === 10) and turn on ESLint's no-cond-assign rule.|| instead of ??. ?? only triggers for null/undefined, not for 0, '', or false. Switch to nullish coalescing if those are valid inputs.?.) to safely access deeply nested properties. Replace obj.a.b with obj?.a?.b to return undefined instead of crashing.Math.abs(a - b) < 0.0001 instead of a === b. For currency, consider using integer cents or a library like decimal.js.npx eslint --rule '{eqeqeq: ["error", "always"]}' src/grep -rn '\b==[^=]' src/ --include='*.js'== with === except when you explicitly need type coercion (rare).Key takeaways
Array.isArray() to check for arrays, not typeof.Common mistakes to avoid
5 patternsUsing = instead of === inside an if condition
=) returns the assigned value, which is truthy unless it's 0, null, undefined, false, or empty string. The bug silently assigns the value instead of comparing.no-cond-assign (especially except-parens) to catch this. Example: if (userScore === 100) instead of if (userScore = 100).Using == instead of === and getting surprised by type coercion
userInput == 0 return true when userInput is '0' (string), false, null, or even an empty array — because == coerces types in complex ways. This leads to logic bugs that are difficult to trace.eqeqeq with 'always' option. Example: if (userInput === 0) will only match number 0, not string '0' or null.Using || for default values when 0 or false are valid inputs
const quantity = input || 10 sets quantity to 10 because 0 is falsy. The bug is silent — data is overwritten without error.?? instead: const quantity = input ?? 10. This only falls back to 10 when input is null or undefined, preserving 0, false, and empty string as valid values.Confusing logical && and || with boolean return
0 || true to return false (because 0 is falsy), but it returns true (the second operand). Expecting true && 'hello' to return true, but it returns 'hello'. This can cause subtle bugs when using these operators in non-boolean contexts like JSX.!! (double NOT) to coerce: !!(0 || true) returns true. Or better, explicitly write conditions that return booleans.Forgetting that `typeof null === 'object'` is a bug
if (typeof value === 'object' && value !== null) is common, but many developers forget the null check. When value is null, the condition passes and later code crashes trying to access properties on null.if (value !== null && typeof value === 'object'). Or use value && typeof value === 'object' if null/falsy values should be excluded.Interview Questions on This Topic
What is the difference between == and === in JavaScript, and which one should you use in production code and why?
x == null is true for both null and undefined. But even then, many teams prefer explicit checks: x === null || x === undefined. Linters typically enforce === by default.Frequently Asked Questions
20+ years shipping production JavaScript and front-end systems at scale. Notes here come from systems that actually shipped.
That's JS Basics. Mark it forged?
10 min read · try the examples if you haven't