The Magic of Operator Precedence in JavaScript
Alright, so you've written a line of code, but when you run it, the output isn’t what you expected. You double-check your logic, and everything seems right. What’s going on? Chances are, you might be overlooking something known as operator precedence.
Say there is 1 VIP seat and two VIPs appear to the event, who gets to sit on the chair? This is decided depending on who is more "important", right?
Let’s dive into the concept of operator precedence and how it influences the way your code is interpreted by JavaScript.
What is Operator Precedence?
In JavaScript, operator precedence determines the order in which different operations are performed in a single expression. Simply put, when an expression has multiple operators, some operations are performed before others. Operators with higher precedence are executed first.
Quick note: We have discussed operators in detail in our previous article. I would highly suggest reading this before proceeding to read the below article.
Imagine a simple math problem like 5 + 3 * 2
. If you calculate it left to right, you might first add 5 and 3 to get 8, then multiply by 2 to get 16. But hold up—that’s not quite right, is it? The multiplication happens first, giving you 3 * 2 = 6
, and then you add 5, resulting in 11. That’s operator precedence in action.
Remember BODMAS?
console.log(5 + 3 * 2); // Output: 11
In this case, multiplication has higher precedence than addition, so it happens first.
Precedence vs. Associativity: Batman and Robin
Operator precedence answers the “who goes first” question, but when two operators with the same precedence appear together, JavaScript relies on associativity to break the tie. Associativity determines whether the operation is performed left-to-right or right-to-left.
For example, take a look at this:
let x = 10 - 4 - 2;
Both subtraction operations have the same precedence, so JavaScript evaluates them based on their associativity. Subtraction is left-associative, so it’s processed as (10 - 4) - 2, resulting in 4.
But what happens when you deal with something like exponentiation (**
)? Check this out:
console.log(2 ** 3 ** 2); // Output is 512
Exponentiation is right-associative, which means it’s evaluated as 2 ** (3 ** 2)
, or 2 ** 9
, resulting in 512. Had it been left-associative, you’d calculate (2 ** 3) ** 2
which equals 64.
Left and right-associative just means, start with left or start with right, it's that simple!
We all love brackets and parentheses, don't we?
Grouping with Parentheses
Whenever in doubt—or when you simply want to be explicit about the order of operations—you can use parentheses to group expressions and ensure they’re evaluated in the order you intend.
let result = (5 + 3) * 2;
console.log(result); // Output: 16
Here, the parentheses force the addition to happen before the multiplication, giving you a result of 16 instead of the 11 you’d get without them.
Dealing with the Unary Operators
Unary operators, like ++
, --
, and typeof
, also have their own precedence. They usually have higher precedence than most binary operators (those that operate on two operands).
let a = 10;
console.log(typeof a + ' is a number'); // Output: "number is a number"
Here, the typeof
operator has higher precedence than string concatenation (+
), so typeof a
is evaluated first, giving you "number"
, which is then concatenated with the string " is a number"
.
Short-Circuiting
Logical operators like &&
, ||
, and ??
introduce a cool trick called short-circuiting. (Yes, just like the electrical one at home ). These operators can sometimes skip evaluating the right-hand side of an expression if the left-hand side already determines the result.
let name = null;
console.log(name || "Guest"); // Output: "Guest"
In this example, since name
is null
(a falsy value), the ||
operator short-circuits and directly returns "Guest"
without bothering to evaluate name
.
Refer to the below for the order of precedence - Bookmark this to refer later before your interview
- Grouping (
()
) - Member Access (
.
), Function Call (()
), Optional Chaining (?.
) - New without arguments (
new
) - Postfix Increment/Decrement (
x++
,x--
) - Unary Operations (
!
,~
,+
,-
,typeof
,void
,delete
) - Exponentiation (
**
) - Multiplication/Division/Modulus (
*
,/
,%
) - Addition/Subtraction (
+
,-
) - Bitwise Shifts (
<<
,>>
,>>>
) - Relational (
<
,<=
,>
,>=
,in
,instanceof
) - Equality (
==
,!=
,===
,!==
) - Bitwise AND (
&
) - Bitwise XOR (
^
) - Bitwise OR (
|
) - Logical AND (
&&
) - Logical OR (
||
) - Nullish Coalescing (
??
) - Conditional (Ternary) Operator (
?:
) - Assignment (
=
,+=
,-=
etc.) - Comma (
,
)
Check the table of precendence here.
Understanding operator precedence and associativity is crucial to writing bug-free JavaScript code, allowing you to predict and control the behavior of your code. When in doubt, remember to use parentheses to clarify and enforce the order of operations. Keep practising, and soon enough, I assure you that this will become a piece of cake for you!
Now, go forth and write some code and share it with us, we love reading what our students build!