Tutorials

JavaScript ES6 Operators

Key Takeaways

  • JavaScript operators act on operands to calculate results, compare values, choose alternatives, and update state.
  • Prefer strict equality, === and !==, unless you intentionally want type conversion.
  • && and || return operand values. Use ?? for defaults that preserve 0, false, and empty strings.
  • Parentheses make grouping explicit; bitwise operators require particular care because Number operands undergo 32-bit integer conversion.

JavaScript operators perform operations on values called operands. An expression combines values and operators to produce a result: in 2 + 2, + adds two operands and produces 4.

console.log(2 + 2); // 4

Despite the ES6 title, this reference covers both long-standing operators and later additions, including exponentiation, optional chaining, nullish coalescing, and logical assignment. Run each code block independently in a modern browser console or a supported Node.js runtime.

Types of JavaScript Operators

The main families are arithmetic, relational and equality, logical, assignment, bitwise, and conditional operators. Unary operators take one operand, binary operators take two, and the conditional operator takes three; these descriptions count operands rather than describe what an operator does.

The MDN operator reference also covers keyword operators such as typeof, delete, and void. Operators aren't limited to punctuation.

JavaScript Arithmetic Operators

Addition, subtraction, multiplication, and division

console.log(2 + 2); // 4: addition
console.log(4 - 2); // 2: subtraction
console.log(5 * 5); // 25: multiplication
console.log(4 / 2); // 2: division
console.log(5 / 2); // 2.5: Number division preserves the fraction

Remainder and exponentiation

% is the remainder operator, not mathematical modulo: a nonzero result has the dividend's sign. The exponentiation operator, **, raises a base to a power; ^ means bitwise XOR, not exponentiation.

console.log(6 % 5);  // 1
console.log(-6 % 5); // -1
console.log(2 ** 3); // 8

Increment and decrement

++ adds one; -- subtracts one. Postfix forms return the old value, while prefix forms return the updated value.

let a = 2;
a++;
console.log(a); // 3

let b = 2;
b--;
console.log(b); // 1

let count = 2;
console.log(count++); // 2; count becomes 3
console.log(++count); // 4; count becomes 4

Numeric addition versus string concatenation

Coercion means automatic type conversion. The addition operator first converts operands to primitive values; if either is a string, it concatenates strings instead of adding numbers.

console.log("5" + 2);      // "52"
console.log("5" - 2);      // 3
console.log(Number("5") + 2); // 7
console.log(+"5");         // 5: unary plus
console.log(-5);           // -5: unary negation

Convert external input deliberately before doing arithmetic. Don't rely on subtraction to fix an unexpected string.

JavaScript Comparison Operators

Relational comparisons

<, >, <=, and >= compare ordering and return booleans. With two strings, ordering is lexicographic by UTF-16 code units; a string compared with a Number undergoes numeric conversion.

console.log(2 > 1);   // true
console.log(2 < 1);   // false
console.log(2 >= 2);  // true
console.log(1 <= 2);  // true
console.log("2" < "10"); // false: string ordering
console.log("2" < 10);   // true: numeric comparison

Equality: == versus ===

Use === and !== by default: they compare without converting operand types. == and != allow coercion under JavaScript's equality rules.

console.log(6 == 5);      // false
console.log(2 != 3);      // true
console.log(0 == false);  // true: false converts to 0
console.log(0 === false); // false: different types
console.log("5" == 5);    // true
console.log("5" === 5);   // false
console.log("5" !== 5);   // true
console.log({} === {});   // false: different objects
console.log(NaN === NaN); // false

Strict equality doesn't compare object contents: objects must be the same reference. One intentional use of coercive equality is value == null to accept both null and undefined; use two explicit strict checks if that better fits your team's conventions.

JavaScript Logical Operators and Short-Circuiting

AND, OR, and NOT

Logical AND, &&, returns its left operand when that value is falsy; otherwise, it returns its right operand. || returns its left operand when truthy, otherwise its right operand. Both skip the right-hand expression when the left determines the result: this is short-circuit evaluation.

Falsy values include false, 0, 0n, "", null, undefined, and NaN. Empty arrays and objects are truthy. Unlike AND and OR, ! always produces a boolean.

console.log(true && false); // false
console.log(true || false); // true
console.log(!true);         // false
console.log("ready" && 42); // 42
console.log("" || "Guest"); // "Guest"

let calls = 0;
function fallback() {
  calls++;
  return "computed";
}
console.log("saved" || fallback()); // "saved"
console.log(0 && fallback());       // 0
console.log(calls);                // 0: neither call ran

Nullish coalescing: ?? versus ||

Nullish coalescing, ??, evaluates and returns the fallback only when its left operand is null or undefined. Use it when zero, false, or an empty string is a valid value.

valuevalue || "fallback"value ?? "fallback"
0"fallback"0
false"fallback"false
"""fallback"""
NaN"fallback"NaN
null"fallback""fallback"
undefined"fallback""fallback"

JavaScript requires parentheses when you mix ?? directly with || or &&.

console.log((null ?? "") || "Guest"); // "Guest"
// null ?? "" || "Guest"; // Invalid syntax without grouping

Optional Chaining: ?.

Optional chaining stops a property access or call when the value before ?. is nullish, returning undefined. Place it at each level that may be absent.

const user = { profile: null };
console.log(user.profile?.name ?? "Guest"); // "Guest"

const handlers = {};
console.log(handlers.onSave?.()); // undefined

const items = null;
console.log(items?.[0]); // undefined

JavaScript Assignment Operators

Simple and compound assignments

= assigns a value; arithmetic and bitwise compound assignments combine an operation with an update. The complete family appears in the MDN expressions and operators guide.

  • Arithmetic: +=, -=, *=, /=, %=, **=.
  • Bitwise: &=, |=, ^=, <<=, >>=, >>>=.
  • Logical: &&=, ||=, ??=.
let x = 2;
console.log(x); // 2

let added = 2;
added += 1;
console.log(added); // 3

let subtracted = 2;
subtracted -= 1;
console.log(subtracted); // 1

let multiplied = 2;
multiplied *= 2;
console.log(multiplied); // 4

let divided = 2;
divided /= 2;
console.log(divided); // 1

let remainder = 6;
remainder %= 5;
console.log(remainder); // 1

let power = 2;
power **= 3;
console.log(power); // 8

For a simple variable, x += y has the effect of x = x + y, including string concatenation. Compound assignments evaluate their left-hand reference only once. For declaration and reassignment rules, see JavaScript variable declarations.

Logical assignment

&&= assigns only when the current value is truthy; ||= assigns when falsy; ??= assigns when nullish. Otherwise, they skip both the right-hand expression and the assignment.

const settings = { retries: 0, label: "", enabled: true };
settings.retries ??= 3;       // Keeps 0
settings.label ||= "Guest";  // Replaces the empty string
settings.enabled &&= false; // Replaces the truthy value
console.log(settings.retries); // 0
console.log(settings.label);   // "Guest"
console.log(settings.enabled); // false

JavaScript Bitwise Operators

Use bitwise operators for explicit bit manipulation, such as packed flags—not as a general speed trick. With Number operands, bitwise operations use 32-bit integers, discarding fractional parts and bits beyond that width.

AND, OR, XOR, and NOT

& sets a result bit when both input bits are 1; | sets it when either is 1; ^ sets it when the bits differ; ~ inverts every bit.

Shown using only the lowest four bits:
  0101  (5)
& 0011  (3)
------
  0001  (1)
const a = 0b0101;
const b = 0b0011;
console.log(a & b); // 1
console.log(a | b); // 7
console.log(a ^ b); // 6
console.log(~a);    // -6: all 32 bits are inverted

Left shift <<, signed right shift >>, and zero-fill right shift >>>

<< shifts left and inserts zeros on the right. >> shifts right while preserving the sign; >>> fills the left with zeros and returns an unsigned 32-bit result.

console.log(5 << 1);   // 10
console.log(-8 >> 1);  // -4
console.log(-8 >>> 1); // 2147483644
console.log((2 ** 32) | 0); // 0: higher bits are lost

The large difference between the two right-shift results is intentional. For more worked examples, see JavaScript bitwise operators.

BigInt Operators

BigInt represents integers without Number's fixed precision. Numeric arithmetic cannot mix BigInt and Number operands without explicit conversion, and BigInt division truncates toward zero.

console.log(2n + 3n);       // 5n
console.log(7n / 2n);       // 3n
console.log(2n + BigInt(1)); // 3n
console.log(2n < 3);       // true: comparison permits mixed types
console.log(2n === 2);     // false: types differ

try {
  console.log(2n + 1);
} catch (error) {
  console.log(error.name); // "TypeError"
}

BigInt supports &, |, ^, ~, <<, and >> without Number's 32-bit truncation. It doesn't support >>> or unary +. Converting a large BigInt to Number can lose precision.

The Ternary Operator

The conditional operator, condition ? whenTrue : whenFalse, is an expression that produces a value. Only the selected branch is evaluated.

console.log(true ? 2 : 3); // 2

const isMember = true;
const price = isMember ? 10 : 15;
console.log(price); // 10

Use a ternary when choosing between two readable alternatives. Prefer if...else control flow for multiple actions or complicated branches; deeply nested ternaries are rarely worth the saved lines.

typeof and Its Surprises

typeof returns a type-label string, not a complete description of a value.

console.log(typeof 3);          // "number"
console.log(typeof (() => {})); // "function"
console.log(typeof null);       // "object": a historical quirk
console.log(typeof []);         // "object"
console.log(typeof 2n);         // "bigint"

// This identifier is intentionally undeclared.
console.log(typeof stackchiefMissingValue); // "undefined"

Reading an undeclared identifier normally throws a ReferenceError, but typeof returns "undefined". That exception doesn't apply to a let or const binding before initialization in its scope—the period called the temporal dead zone. See JavaScript arrays for collection-specific operations rather than relying on typeof to identify arrays.

Other Keyword Operators: in, instanceof, delete, and void

  • in tests whether an object has a property, including inherited properties. Use Object.hasOwn(object, key) when you need only its own properties.
  • instanceof, by default, checks whether a constructor's prototype occurs in an object's prototype chain; it isn't a structural comparison of object fields.
  • delete removes configurable own properties, not lexical variable bindings. Deleting an array element leaves a hole rather than shrinking its length.
  • void evaluates an expression and produces undefined.
const record = { name: "Ada" };
console.log("name" in record);       // true
console.log("toString" in record);   // true: inherited
console.log(record instanceof Object); // true
console.log(delete record.name);     // true
console.log("name" in record);       // false
console.log(void (2 + 2));           // undefined

typeof, delete, and void are unary operators written as keywords. By contrast, ... is spread or rest syntax, not a general-purpose unary operator.

JavaScript Operator Precedence and Associativity

Precedence determines how operators group; associativity resolves grouping among operators at the same precedence level. Parentheses let you make that grouping explicit.

console.log(2 + 3 * 4);     // 14: 2 + (3 * 4)
console.log((2 + 3) * 4);   // 20
console.log(10 - 3 - 2);    // 5: (10 - 3) - 2
console.log(2 ** 3 ** 2);   // 512: 2 ** (3 ** 2)
console.log(-(2 ** 2));     // -4
// -2 ** 2; // Invalid syntax: parenthesize the intended grouping

This abbreviated table runs from higher to lower precedence. The conditional and assignment rows need a caveat: the conditional operator grammar accepts an AssignmentExpression in either branch, so assignments there don't require parentheses.

OperatorsGrouping
(expression)Explicit grouping
**Right-to-left
* / %Left-to-right
+ -Left-to-right
<< >> >>>Left-to-right
< <= > >= in instanceofLeft-to-right
== != === !==Left-to-right
&Left-to-right
^Left-to-right
|Left-to-right
&&Left-to-right
||, ??Left-to-right; mixing requires parentheses
condition ? a : bRight-to-left
Assignment operatorsRight-to-left

Grouping isn't the same as operand evaluation order: operands are evaluated left-to-right, subject to short-circuiting. Don't use associativity to predict which function call runs first.

Common Mistakes

  • Using || for every default. Choose ?? when falsy values are meaningful.
  • Confusing logical and bitwise operators. & and | perform bit operations and don't provide logical short-circuiting.
  • Using bitwise conversion as a general optimization. value | 0 can discard information. Reach for it only when you actually need signed 32-bit conversion.
  • Compressing too much into an expression. Name intermediate values and use parentheses or statements when that makes the decision easier to review.

FAQ

What is the difference between == and === in JavaScript?

== allows type conversion before comparison; === does not. Prefer === by default: 0 == false is true, while 0 === false is false.

What is the difference between || and ?? in JavaScript?

|| uses its fallback for any falsy left operand, while ?? uses it only for null or undefined. Use ?? when 0, false, or an empty string should remain valid values.

How does operator precedence work in JavaScript?

Higher-precedence operators group more tightly, so multiplication groups before addition. Associativity resolves grouping at equal precedence, and parentheses make your intended grouping explicit.

What does the >>> operator do in JavaScript?

The zero-fill right-shift operator shifts a Number's 32-bit representation right, inserts zeros on the left, and returns an unsigned result. Unlike signed right shift, it doesn't preserve a negative sign, and it doesn't support BigInt.

When should I use the ternary operator instead of if...else?

Use a ternary to choose one value from two simple alternatives, such as a label or price. Use if...else when branches contain multiple actions or nesting makes the expression difficult to read.

Further Reading

Written by Sam Erickson

Software engineer. I write every article on StackChief myself. Author of Mastering the Tech Interview. More about me