Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

JavaScript (ES2026)  Module 2 – Values, types and operators

Operators, short-circuiting and precedence

How the arithmetic, assignment, comparison, logical and bitwise operators work, which group first, in which order operands run, and where to add parentheses.

  • Beginner
  • 20 minutes
  • Examples run with Node.js 24.21.0 and quickjs 0.32.0
  • By MySmartCoPilot
Free lesson, Pro answers: 20 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Use the arithmetic, assignment, comparison, logical and conditional operators correctly
  • Predict how precedence and associativity group an expression, and in which order its operands run
  • Combine and test bit flags with |, & and ~, and explain why the bitwise operators wrap at 32 bits
  • Add parentheses wherever the grouping is not obvious to a reader

Before you start

What you’ll learn · Before you start

What you will learn

  • Use the arithmetic, assignment, comparison, logical and conditional operators correctly
  • Predict how precedence and associativity group an expression, and in which order its operands run
  • Combine and test bit flags with |, & and ~, and explain why the bitwise operators wrap at 32 bits
  • Add parentheses wherever the grouping is not obvious to a reader

Before you start

On this page

An operator combines values into a new value: + adds, < compares, && chooses. When an expression holds several operators, three separate rules decide what happens. Precedence decides which operators group first: * binds more tightly than +, so 2 + 3 * 4 is 14. Associativity decides how operators of the same precedence group: most go from left to right, so 10 - 4 - 3 is 3, while ** and assignment go from right to left. Evaluation order decides when each operand runs, and in JavaScript that is always from left to right, whatever the grouping, except that &&, ||, ?? and ? : may skip an operand altogether. This lesson walks through each family of operators, shows the precedence traps that cause real bugs, and ends with bit flags, the main everyday use of the bitwise operators.

Arithmetic: + - * / % and **

Division, remainder, powers and zero JavaScript · arithmetic.mjs
// The arithmetic operators on a few awkward inputs.
const seven = 7;
const two = 2;
console.log(seven / two, Math.trunc(seven / two)); // no integer division: truncate it yourself
console.log(-seven % 3, seven % -3, 5.5 % two); // % keeps the sign of the left operand, and works on fractions
console.log(two ** 3 ** two, (two ** 3) ** two); // ** groups from the right
console.log(1 / 0, -1 / 0, 0 / 0);

// A true modulo, for wrapping round: three days before Tuesday (day 2) is Saturday (day 6).
const daysInWeek = 7;
const tuesday = 2;
console.log((tuesday - 3) % daysInWeek, (((tuesday - 3) % daysInWeek) + daysInWeek) % daysInWeek);

Output

3.5 3
-1 1 1.5
512 64
Infinity -Infinity NaN
-1 6

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node arithmetic.mjs

  • / gives the exact quotient, fraction included. 7 / 2 is 3.5; cut off the fraction with Math.trunc when you want integer division.
  • % is a remainder, not a modulo. Its result takes the sign of the left operand, so -7 % 3 is -1 and 7 % -3 is 1, and it works on fractions too: 5.5 % 2 is 1.5. To wrap round a cycle, such as the days of a week, use ((n % m) + m) % m: counting Sunday as day 0, three days before Tuesday is then Saturday, day 6, instead of day -1.
  • ** raises to a power and groups from the right, as in mathematics: 2 ** 3 ** 2 is 2 ** 9, 512, while (2 ** 3) ** 2 is 64.
  • Dividing by zero does not throw. 1 / 0 is Infinity, -1 / 0 is -Infinity and 0 / 0 is NaN.

+ also joins strings, by the rules of Type conversion and coercion, so it is the one arithmetic operator whose meaning depends on its operands. Two groupings are refused outright:

Two groupings the grammar refuses JavaScript · syntax-errors.mjs
// Two groupings the grammar refuses, so the reader never has to guess. Each source is compiled on its own here.
const sources = ['-2 ** 2', '(-2) ** 2', '-(2 ** 2)', 'a ?? b || c', '(a ?? b) || c'];
for (const source of sources) {
  try {
    // eslint-disable-next-line no-new-func -- compiling each snippet is the point of this example
    const run = new Function('a', 'b', 'c', `return ${source};`);
    console.log(`${source.padEnd(14)} gives ${run(null, 0, 5)}`);
  } catch (error) {
    console.log(`${source.padEnd(14)} ${error.name}`);
  }
}

Output

-2 ** 2        SyntaxError
(-2) ** 2      gives 4
-(2 ** 2)      gives -4
a ?? b || c    SyntaxError
(a ?? b) || c  gives 5

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node syntax-errors.mjs

-2 ** 2 could mean (-2) ** 2, which is 4, or -(2 ** 2), which is -4, and readers who know other languages expect different answers, so JavaScript refuses it with a SyntaxError and you write the parentheses you mean. For the same reason, ?? cannot sit next to || or && without parentheses.

Assignment, increment and decrement

Compound assignment, ++ and -- JavaScript · assignment.mjs
// Compound assignment: x op= y means x = x op y.
let balance = 100;
balance -= 30;
balance *= 2;
balance **= 1;
console.log(balance);

// Assignment is an expression whose value is the value assigned, and it groups from the right.
let low;
let high;
low = high = 0;
low -= 1;
high += 1;
console.log(low, high);

// x++ gives the old value, ++x the new one.
let count = 5;
console.log(count++, count, ++count, count);

// Inside a larger expression they are hard to follow: what does this array hold?
let i = 0;
const picked = [i++, i++, ++i];
console.log(picked, i);

Output

140
-1 1
5 6 7 7
[ 0, 1, 3 ] 3

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node assignment.mjs

  • Compound assignment writes x op= y for x = x op y, for every arithmetic and bitwise operator: -=, *=, **=, |= and the rest, plus the logical forms &&=, ||= and ??=.
  • Assignment is an expression: its value is the value assigned, and it groups from the right, so low = high = 0 sets high first and then low. The same fact explains a classic bug, if (status = 'done'), which assigns instead of comparing and is always true; ESLint’s no-cond-assign rule reports it.
  • x++ gives the old value and ++x the new one, and both change x. [i++, i++, ++i] holds 0, 1 and 3, which few readers work out at a glance. Give increments a line of their own, or write i += 1.

Comparing and choosing

The comparison operators always give a boolean, and the earlier lessons of this module cover their rules: Equality: ==, === and Object.is for === and ==, and Type conversion and coercion for < and >. Two more facts belong here.

  • Comparisons do not chain. big > middle > small reads like mathematics, but it groups as (big > middle) > small: the first comparison gives true, which converts to 1, and 1 > 1 is false. Write big > middle && middle > small.
  • &&, || and ?? return one of their operands and may skip the right one, as Booleans, truthy and falsy values showed. The conditional operator condition ? a : b evaluates only one of its branches, and it groups from the right, so a ? b : c ? d : e means a ? b : (c ? d : e). One level of it reads well; for more, use if or a lookup table.

Precedence and associativity

The ECMAScript standard has no precedence table: its grammar defines one expression inside another, and the nesting decides the grouping. Read from the top, this is the order that results, the most tightly binding first:

Group Operators Same-level operators group
1 parentheses ( … )
2 a.b, a[b], a?.b, calls f(), new X() from the left
3 postfix x++, x--
4 prefix !, ~, +x, -x, ++x, --x, typeof, void, delete, await
5 ** from the right
6 *, /, % from the left
7 +, - from the left
8 <<, >>, >>> from the left
9 <, <=, >, >=, in, instanceof from the left
10 ==, !=, ===, !== from the left
11 & from the left
12 ^ from the left
13 | from the left
14 && from the left
15 || and ??, which cannot be mixed without parentheses from the left
16 ? : from the right
17 = and every compound assignment, =>, yield from the right
18 the comma , from the left

You do not need to memorise the table. You need to recognise the five places where it surprises people, and add parentheses there:

Five traps, each with the parentheses it needed JavaScript · precedence-traps.mjs
// Five expressions that group differently from how they read, each with the parentheses it needed.
const SHARE = 4;
const withShare = 5; // bits 4 and 1
const withoutBit1 = 6; // bits 4 and 2
const a = 2;
const b = 3;
const big = 3;
const middle = 2;
const small = 1;
const user = null;

// eslint-disable-next-line no-self-compare -- the bug: === groups first and compares SHARE with itself
console.log('1.', withShare & SHARE === SHARE, withoutBit1 & SHARE === SHARE);
console.log('   ', (withShare & SHARE) === SHARE, (withoutBit1 & SHARE) === SHARE);

console.log('2.', 'Total: ' + a + b);
console.log('   ', 'Total: ' + (a + b));

console.log('3.', typeof a + 1);
console.log('   ', typeof (a + 1));

console.log('4.', big > middle > small);
console.log('   ', big > middle && middle > small);

// eslint-disable-next-line no-unsafe-negation -- the mistake this line shows
console.log('5.', !user instanceof Object);
console.log('   ', !(user instanceof Object));

Output

1. 1 0
    true true
2. Total: 23
    Total: 5
3. number1
    number
4. false
    true
5. false
    true

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node precedence-traps.mjs

  1. The bitwise operators bind less tightly than the comparisons. withShare & SHARE === SHARE groups as withShare & (SHARE === SHARE), which is withShare & true, which is withShare & 1. It gives 1 for 5 (looks right, by accident) and 0 for 6, although 6 has the SHARE bit. ESLint’s no-self-compare rule, when it is switched on, reports the hidden SHARE === SHARE; the comment above that line in the example silences it on purpose.
  2. + joins from the left. 'Total: ' + a + b joins 'Total: 2' and then 3. Group the sum, or write a template literal, `Total: ${a + b}`.
  3. typeof applies to the operand right after it. typeof a + 1 is 'number' + 1, which is 'number1'.
  4. Comparisons do not chain, as the previous section showed.
  5. ! binds more tightly than instanceof and in. !user instanceof Object is false instanceof Object or true instanceof Object, which is always false. Write !(user instanceof Object); ESLint’s no-unsafe-negation rule, in its recommended set, reports the unparenthesised form.
Two expression trees: without parentheses === groups first under &; with them, & groups first under ===.perms & SHARE === SHARE(how JavaScript groups it)(perms & SHARE) === SHARE(what the author meant)&perms===SHARESHARE===&permsSHARESHARE

How perms & SHARE === SHARE is grouped, and how it was meant

Text description of the diagram

Two small expression trees, one above the other. In each tree, an operator sits above the operands it applies to.

  • Without parentheses, perms & SHARE === SHARE is grouped as perms & (SHARE === SHARE). The top of the tree is &. Its operands are perms and a === node, which compares SHARE with SHARE. That comparison is always true, so the whole expression becomes perms & true, which is perms & 1.
  • With parentheses, (perms & SHARE) === SHARE has === at the top. Its operands are an & node, which keeps the SHARE bit of perms, and SHARE itself. This is the test the author meant: it is true exactly when the SHARE bit is on.

Grouping is not evaluation order

Precedence decides which operator applies to which operands. It does not decide what runs first: JavaScript evaluates operands from left to right, then applies the operators as the grouping says.

Operands run from left to right JavaScript · evaluation-order.mjs
// Grouping decides which operator applies to what; the operands still run from left to right.
const calls = [];
function value(n) {
  calls.push(n);
  return n;
}

console.log(value(1) + value(2) * value(3), calls.join(' then '));

calls.length = 0;
console.log(value(2) ** value(3) ** value(2), calls.join(' then '));

// The skipping operators are the exception: an operand that cannot change the result never runs.
calls.length = 0;
const result = value(0) && value(4);
const chosen = value(5) > 1 ? value(6) : value(7);
console.log(result, chosen, calls.join(' then '));

Output

7 1 then 2 then 3
512 2 then 3 then 2
0 6 0 then 5 then 6

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node evaluation-order.mjs

value(1) + value(2) * value(3) multiplies before it adds, yet the calls happen in the order 1, 2, 3. ** groups from the right, and its operands still run from the left: 2, then 3, then 2. The last line shows the exception: && skips its right operand when the left one is falsy, and ? : runs only one branch, so value(4) and value(7) never run. Code that depends on the order of side effects inside one expression is hard to read even when it is correct, so give each side effect a statement of its own.

Bitwise operators and flags

&, |, ^, ~, <<, >> and >>> work on 32-bit integers. Their operands are converted to 32-bit integers (by the conversions the standard calls ToInt32 and, for the unsigned shift >>>, ToUint32), the bits are combined, and the result is a number again:

What the 32-bit conversion does JavaScript · bitwise-32.mjs
// The bitwise operators turn their operands into 32-bit integers first.
const values = [5.7, -5.7, 2 ** 31 - 1, 2 ** 31, 3_000_000_000];
for (const value of values) {
  console.log(String(value).padEnd(12), 'x | 0 =', String(value | 0).padEnd(12), 'Math.trunc =', Math.trunc(value));
}

// >>> 0 reads the same 32 bits as an unsigned number, ~ flips every bit, << shifts left.
const minusOne = -1;
console.log(minusOne >>> 0, ~5, 1 << 31, 1 << 32);

Output

5.7          x | 0 = 5            Math.trunc = 5
-5.7         x | 0 = -5           Math.trunc = -5
2147483647   x | 0 = 2147483647   Math.trunc = 2147483647
2147483648   x | 0 = -2147483648  Math.trunc = 2147483648
3000000000   x | 0 = -1294967296  Math.trunc = 3000000000
4294967295 -6 -2147483648 1

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node bitwise-32.mjs

5.7 | 0 drops the fraction, which is why x | 0 and ~~x appear in old code as a fast way to truncate. But numbers beyond 32 bits wrap round: 2 ** 31 | 0 is -2,147,483,648 and 3_000_000_000 | 0 is -1,294,967,296. Use Math.trunc to drop a fraction; it works for every number.

Their everyday job is holding flags: a group of on-or-off options stored together in a single number, with one bit for each option.

Permissions as bit flags JavaScript · flags.mjs
// Four permissions, one bit each.
const READ = 1; // 0b0001
const WRITE = 2; // 0b0010
const SHARE = 4; // 0b0100
const DELETE = 8; // 0b1000

const bits = (n) => n.toString(2).padStart(4, '0');

let editor = READ | WRITE; // combine with |
console.log('editor        ', bits(editor), editor);

editor |= SHARE; // add a flag
console.log('add SHARE     ', bits(editor), editor);

editor &= ~WRITE; // remove a flag: & with every other bit
console.log('remove WRITE  ', bits(editor), editor);

console.log('can share?    ', (editor & SHARE) !== 0);
console.log('can delete?   ', (editor & DELETE) !== 0);

const needed = READ | SHARE;
console.log('has READ and SHARE?', (editor & needed) === needed);

Output

editor         0011 3
add SHARE      0111 7
remove WRITE   0101 5
can share?     true
can delete?    false
has READ and SHARE? true

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node flags.mjs

To Write Why it works
combine flags READ | WRITE | switches on every bit that is on in either operand
add a flag perms |= SHARE switching on a bit that is already on changes nothing
remove a flag perms &= ~WRITE ~WRITE has every bit on except WRITE’s
test one flag (perms & SHARE) !== 0 & keeps only the bits on in both
test several flags (perms & needed) === needed every bit of needed must survive the &
flip a flag perms ^= SHARE ^ switches a bit on if it was off, and off if it was on

Flags keep a set of options small and fast to test, which is why browsers report the mouse buttons held down during a mouse event this way, in its buttons property: 1 for the primary button, 2 for the secondary one, 4 for the middle one, added together. For a handful of settings in application code, an object with named booleans is easier to read; reach for flags when you store or send many of them.

Programmer Calculator (Bitwise) See any number as 32 bits and try &, |, ^ and the shifts on it. JavaScript & TypeScript Online Compiler Change the operands in these examples and predict the result before you run them.

The comma operator and other rarities

  • The comma operator evaluates both operands and gives the right one: (1, 2, 3) is 3. Its one common home is the last part of a for loop, as in i++, j--; elsewhere it hides what code does, and linters warn about it. The commas between function arguments, array items and declarations are separators, not this operator.
  • void expression evaluates the expression and gives undefined. You will see void 0 in old or generated code as a way to write undefined.
  • typeof, delete, in and instanceof are operators too, with the precedence shown above. Their meaning belongs to the lessons on types, objects and classes.

Exercise: permission flags

Exercise · Easy · JavaScript

Grant, revoke and test permission flags

A document app stores each user's permissions as one number, with one bit per permission:

  • READ is 1, bits 0001;
  • WRITE is 2, bits 0010;
  • SHARE is 4, bits 0100;
  • DELETE is 8, bits 1000.

Write four functions, each a single expression with the bitwise operators:

  • grant(perms, flags) returns perms with every bit of flags switched on;
  • revoke(perms, flags) returns perms with every bit of flags switched off, and leaves bits that were already off alone;
  • hasAll(perms, flags) returns true when every bit of flags is on in perms (and true when flags is 0);
  • hasAny(perms, flags) returns true when at least one bit of flags is on in perms.
grant(1, 4)       // 5: READ, then SHARE added
revoke(7, 2)      // 5: WRITE taken away from READ | WRITE | SHARE
hasAll(6, 4)      // true: 6 is WRITE | SHARE
hasAny(1, 2 | 4)  // false

The starter code uses +, - and a comparison without parentheses. Run the sample tests to see where each one goes wrong.

Starter code · flags.mjs

// READ = 1, WRITE = 2, SHARE = 4, DELETE = 8: one bit per permission.

/** perms with every bit of flags switched on. */
export function grant(perms, flags) {
  return perms + flags;
}

/** perms with every bit of flags switched off. */
export function revoke(perms, flags) {
  return perms - flags;
}

/** True when every bit of flags is on in perms. */
export function hasAll(perms, flags) {
  // eslint-disable-next-line no-self-compare -- the starter's mistake: === groups before &
  return Boolean(perms & flags === flags);
}

/** True when at least one bit of flags is on in perms. */
export function hasAny(perms, flags) {
  return perms > 0 && flags > 0;
}
The sample tests · flags.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { grant, revoke, hasAll, hasAny } from './flags.mjs';

const READ = 1;
const WRITE = 2;
const SHARE = 4;
const DELETE = 8;

test('grant switches bits on', () => {
  assert.equal(grant(READ, SHARE), 5);
  assert.equal(grant(READ | WRITE, SHARE | DELETE), 15);
  assert.equal(grant(0, WRITE), 2);
});

test('granting a flag that is already on changes nothing', () => {
  assert.equal(grant(READ, READ), 1);
  assert.equal(grant(READ | WRITE, WRITE | SHARE), 7);
});

test('revoke switches bits off', () => {
  assert.equal(revoke(READ | WRITE | SHARE, WRITE), 5);
  assert.equal(revoke(15, READ | DELETE), 6);
});

test('revoking a flag that is already off changes nothing', () => {
  assert.equal(revoke(READ, WRITE), 1);
  assert.equal(revoke(READ | SHARE, WRITE | SHARE), 1);
});

test('hasAll needs every bit of flags', () => {
  assert.equal(hasAll(READ | WRITE, READ), true);
  assert.equal(hasAll(READ | WRITE, READ | SHARE), false);
  assert.equal(hasAll(WRITE | SHARE, SHARE), true);
  assert.equal(hasAll(READ | SHARE, SHARE), true);
  assert.equal(hasAll(READ, SHARE), false);
  assert.equal(hasAll(READ, 0), true);
});

test('hasAny needs at least one bit of flags', () => {
  assert.equal(hasAny(READ | WRITE, WRITE | SHARE), true);
  assert.equal(hasAny(READ, WRITE | SHARE), false);
  assert.equal(hasAny(DELETE, DELETE), true);
  assert.equal(hasAny(READ | WRITE, 0), false);
});

test('a short history of one user', () => {
  let perms = 0;
  perms = grant(perms, READ | SHARE);
  perms = revoke(perms, SHARE);
  perms = grant(perms, WRITE);
  assert.equal(perms, 3);
  assert.equal(hasAll(perms, READ | WRITE), true);
  assert.equal(hasAny(perms, SHARE | DELETE), false);
});
A hint

| switches bits on, and switching on a bit that is already on changes nothing, which + cannot promise. & ~flags keeps every bit except those of flags. For the tests, put parentheses around the &: === and !== bind more tightly than &, so perms & flags === flags compares flags with itself first.

The sample tests run on this device, in your browser (QuickJS): nothing is sent to mysmartcopilot.com. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Self-check

Check yourself

6 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 6 What is the value of 2 + 3 * 4 ** 2?

    Type a number.

    Show the answer to question 1

    Answer: 50

    ** binds most tightly, so 4 ** 2 is 16; then 3 * 16 is 48; then 2 + 48 is 50.

  2. Question 2 of 6 What does 5 > 4 > 3 give?

    Choose one answer.

    Show the answer to question 2

    Answer: false

    Comparisons group from the left: 5 > 4 is true, and true > 3 converts true to 1, so it asks whether 1 > 3, which is false. Write 5 > 4 && 4 > 3 to test a range.

  3. Question 3 of 6 What does this code print?

    Read the code, then choose one answer.

    let i = 0;
    const picked = [i++, i++, ++i];
    console.log(picked.join(','), i);
    Show the answer to question 3

    Answer: 0,1,3 3

    i++ gives the old value and then adds 1: the first two items are 0 and 1, and i is then 2. ++i adds 1 first and gives the new value, 3.

  4. Question 4 of 6 evaluation-order.mjs records which calls run, and in which order. What does it print?

    What does this program print? Choose one answer.

    // Grouping decides which operator applies to what; the operands still run from left to right.
    const calls = [];
    function value(n) {
      calls.push(n);
      return n;
    }
    
    console.log(value(1) + value(2) * value(3), calls.join(' then '));
    
    calls.length = 0;
    console.log(value(2) ** value(3) ** value(2), calls.join(' then '));
    
    // The skipping operators are the exception: an operand that cannot change the result never runs.
    calls.length = 0;
    const result = value(0) && value(4);
    const chosen = value(5) > 1 ? value(6) : value(7);
    console.log(result, chosen, calls.join(' then '));
    Show the answer to question 4

    Answer: it prints

    7 1 then 2 then 3
    512 2 then 3 then 2
    0 6 0 then 5 then 6

    Operands run from left to right whatever the grouping, so the calls go 1, 2, 3 and then 2, 3, 2. value(0) && value(4) stops at the falsy 0, and the conditional operator runs only its chosen branch, so 4 and 7 never run.

  5. Question 5 of 6 DELETE is 8. Which condition is true exactly when the DELETE bit is on in perms?

    Choose one answer.

    Show the answer to question 5

    Answer: (perms & DELETE) !== 0

    & keeps only the bits that are on in both operands, so the result is 8 when the bit is on and 0 when it is off. Without parentheses, DELETE !== 0 groups first and gives true, so the test becomes perms & 1. | switches the bit on instead of testing it, and && only checks that both values are truthy.

  6. Question 6 of 6 Why is 3_000_000_000 | 0 equal to -1,294,967,296?

    Choose one answer.

    Show the answer to question 6

    Answer: The bitwise operators convert their operands to 32-bit integers, and 3 billion does not fit, so it wraps

    A 32-bit signed integer holds values from -2,147,483,648 to 2,147,483,647. Three billion is 3,000,000,000 − 4,294,967,296 = -1,294,967,296 once it is wrapped into that range. Ordinary numbers hold integers exactly up to 2⁵³ − 1, so use Math.trunc, not | 0, to drop a fraction.

Interview questions

What is the difference between operator precedence and evaluation order in JavaScript? Precedence and associativity decide how an expression is grouped: in a + b * c the multiplication groups first, and in a - b - c the left subtraction does. Evaluation order decides when each operand runs, and in JavaScript operands always run from left to right, whatever the grouping: in f() + g() * h(), f is called first, then g, then h, and only then are the multiplication and the addition done. The exceptions are the operators that can skip an operand, &&, ||, ?? and ? :. Parentheses change the grouping, never the left-to-right order of the operands. That is why two side effects in one expression, such as i++ + i++, are legal but hard to read.

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Fresher to mid

    An admin page is guarded by if (user.perms & ADMIN === ADMIN), with ADMIN = 8. Users with odd permission values, such as 5, get in, while some real admins, such as 12, are refused. Explain exactly why, fix the check, and say how you would stop this class of bug.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Fresher to mid

    A billing service turns amounts into whole cents with Math.round(amount * 100) | 0. It ran for years, until one invoice for $25,000,000.00 was stored as a negative number. Work out the exact value it produced, explain why, and say what you would change.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • Precedence groups operators, associativity groups operators of equal precedence, and operands always run from left to right, except where &&, ||, ?? and ? : skip one.
  • / keeps the fraction (use Math.trunc for integer division), % keeps the sign of the left operand, and ** groups from the right; -2 ** 2 is a SyntaxError.
  • Assignment is an expression that groups from the right; x++ gives the old value; keep increments out of larger expressions.
  • Comparisons do not chain: a > b > c means (a > b) > c.
  • The bitwise operators bind less tightly than ===, so write (perms & FLAG) === FLAG; they also convert to 32 bits, so use Math.trunc rather than | 0 to drop a fraction.
  • Flags combine with |, are removed with & ~, tested with & and flipped with ^.
  • When the grouping is not obvious to a reader, add parentheses.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.