Skip to main content

โž• Arithmetic and Assignment Operators

Every calculation your app performs โ€” a running total, a progress bar, a page counter โ€” comes down to a handful of operators. This lesson turns those symbols into tools you reach for confidently, and it shows you the two or three traps that quietly break real code.

๐ŸŽฏ Learning Objectives

By the end of this lesson, you will be able to:

  • Use all six arithmetic operators โ€” including modulo (%) and exponentiation (**) โ€” and explain what each returns
  • Predict the result of prefix vs. postfix increment and decrement in an expression
  • Apply unary plus/minus for number conversion and know when it fails
  • Refactor verbose reassignments using compound assignment (+=, *=, โ€ฆ) and destructuring
  • Avoid the floating-point precision trap in money math

Estimated Time: 30โ€“40 minutes  โ€ข  Difficulty: Beginner

Hands-on: Build a small tip-splitting calculator that stays penny-accurate.

In This Lesson

Operators: The Verbs of Code

If variables are the nouns of a program โ€” the things you store โ€” then operators are the verbs. An operator is a symbol that takes one or more values (its operands) and produces a new value. JavaScript groups them into several families:

graph TD A[JavaScript Operators] A --> B["Arithmetic<br/>+ - * / % **"] A --> C["Assignment<br/>= += -= *= ..."] A --> D["Comparison<br/>=== !== > <"] A --> E["Logical<br/>&& || ! ??"] A --> F["Other<br/>ternary, typeof, ..."]

This lesson covers the first two families โ€” arithmetic and assignment โ€” because they are where nearly every program begins. Comparison and logical operators get their own lesson next.

๐Ÿ’ก Analogy โ€” operators as kitchen tools. A mixer combines, a knife divides, an oven transforms. You wouldn't blend pasta, and you wouldn't use string concatenation to add two prices. Each operator is shaped for a specific job; picking the right one is half of writing clear code.

The Arithmetic Operators

Arithmetic operators perform mathematical operations on numbers and always return a number (or NaN when the math is impossible).

OperatorNameExampleResult
+Addition5 + 38
-Subtraction5 - 32
*Multiplication5 * 315
/Division6 / 41.5
%Remainder (modulo)5 % 21
**Exponentiation2 ** 38
const a = 10;
const b = 3;

console.log(a + b);   // 13
console.log(a - b);   // 7
console.log(a * b);   // 30
console.log(a / b);   // 3.3333333333333335
console.log(a % b);   // 1   (remainder of 10 รท 3)
console.log(a ** b);  // 1000 (10 to the power of 3)

Two of these deserve a closer look because beginners rarely meet them in school math.

The remainder operator %

% returns what is left over after integer division. It is one of the most useful operators in day-to-day programming:

  • Even or odd? A number is even when n % 2 === 0.
  • Wrapping around a range โ€” clocks, carousels, paginated grids.
  • Every Nth item โ€” striping table rows, batching work.
const isEven = (n) => n % 2 === 0;
console.log(isEven(4)); // true
console.log(isEven(7)); // false

// Wrap a 24-hour value onto a 12-hour clock face
const clockPosition = (hour) => hour % 12;
console.log(clockPosition(15)); // 3
console.log(clockPosition(25)); // 1

// Highlight every 3rd item in a list
for (let i = 0; i < 9; i++) {
  if (i % 3 === 0) console.log(`Item ${i} starts a new row`);
}

โš ๏ธ Modulo and negative numbers

In JavaScript, % takes the sign of the left operand: -7 % 3 is -1, not 2. If you need a result that is always non-negative (common in wrap-around math), use ((n % m) + m) % m.

The exponentiation operator **

Added in ES2016, ** raises the left operand to the power of the right. It replaces the older Math.pow() for most cases:

console.log(2 ** 10);   // 1024
console.log(10 ** -2);  // 0.01  (same as 1 / 100)
console.log(2 ** 0.5);  // 1.4142135623730951  (the square root of 2)

// The old way still works and is equivalent
console.log(Math.pow(2, 10)); // 1024

๐Ÿ“– Definition โ€” expression vs. statement

An expression is any code that produces a value (a + b, x ** 2). A statement is a complete instruction that performs an action (let total = a + b;). Operators build expressions; statements do something with them.

๐Ÿ’ฐ Worked example โ€” compound interest

Arithmetic operators shine in finance. Here is the compound-interest formula A = P(1 + r/n)nt written directly with **:

function compoundInterest(principal, annualRate, years, timesPerYear) {
  const amount = principal * (1 + annualRate / timesPerYear) ** (timesPerYear * years);
  return amount;
}

// $1,000 at 5% compounded quarterly for 10 years
const balance = compoundInterest(1000, 0.05, 10, 4);
console.log(`Final balance: $${balance.toFixed(2)}`); // $1643.62

Increment & Decrement

The increment (++) and decrement (--) operators change a variable by exactly 1. Each comes in two flavours, and the difference between them trips up almost everyone at first.

  • Prefix (++x): change the value first, then hand back the new value.
  • Postfix (x++): hand back the current value first, then change it.
sequenceDiagram participant X as x participant Y as y Note over X,Y: Prefix โ€” y = ++x X->>X: increment x by 1 X->>Y: assign the new x to y Note over X,Y: Postfix โ€” y = x++ X->>Y: assign the current x to y X->>X: increment x by 1
let a = 5;
let b = ++a;   // a becomes 6, THEN b gets 6
console.log(a, b); // 6 6

let c = 5;
let d = c++;   // d gets 5, THEN c becomes 6
console.log(c, d); // 6 5
๐Ÿ’ก Analogy โ€” the deli ticket. Prefix (++x) is taking a ticket and stepping to the next number before anyone reads yours. Postfix (x++) is showing your current number to the clerk and then tearing off a fresh one. Either way you end up one ahead โ€” the question is which number the clerk saw.

โš ๏ธ Keep them simple

Expressions like x++ + ++x are legal but hostile to readers. The classic loop counter for (let i = 0; i < n; i++) is where ++ belongs. When in doubt, write x += 1; on its own line โ€” it is unambiguous.

Unary Plus and Minus

A unary operator works on a single operand. Unary + quietly converts its operand to a number; unary - converts and negates it.

console.log(+"42");    // 42     (string โ†’ number)
console.log(+true);    // 1       (boolean โ†’ number)
console.log(+"");      // 0       (empty string โ†’ 0)
console.log(+"hello"); // NaN     (not convertible)

console.log(-"42");    // -42
console.log(-true);    // -1

Unary + is a compact shorthand for Number(). It is handy when a value arrives as text โ€” for example from a form field, where everything is a string:

function parseQuantity(input) {
  const quantity = +input; // convert once

  if (Number.isNaN(quantity)) {
    return { ok: false, error: 'Please enter a number' };
  }
  if (quantity <= 0 || !Number.isInteger(quantity)) {
    return { ok: false, error: 'Quantity must be a positive whole number' };
  }
  return { ok: true, value: quantity };
}

console.log(parseQuantity('5'));    // { ok: true, value: 5 }
console.log(parseQuantity('3.14')); // { ok: false, error: 'Quantity must be a positive whole number' }
console.log(parseQuantity('five')); // { ok: false, error: 'Please enter a number' }

โœ… Prefer Number.isNaN

The global isNaN("hello") returns true because it converts first. Number.isNaN("hello") returns false โ€” it only reports the genuine NaN value. When checking the result of a conversion, reach for Number.isNaN.

Assignment & Compound Assignment

The plain assignment operator = stores a value in a variable. It is worth remembering that assignment is itself an expression โ€” it evaluates to the value assigned โ€” which is why chaining works:

let x = 10;
let name = 'Alice';

// Assignment is right-associative, so this runs right-to-left:
let a, b, c;
a = b = c = 5; // c = 5, then b = 5, then a = 5
console.log(a, b, c); // 5 5 5

Compound assignment

Compound operators fuse an arithmetic step with the assignment, turning total = total + price into the tighter total += price:

OperatorShorthand for
x += yx = x + y
x -= yx = x - y
x *= yx = x * y
x /= yx = x / y
x %= yx = x % y
x **= yx = x ** y
let x = 10;
x += 5;   // 15
x -= 3;   // 12
x *= 2;   // 24
x /= 4;   // 6
x %= 4;   // 2
x **= 3;  // 8
console.log(x); // 8

Because + is overloaded for strings, += also builds text โ€” useful for assembling output:

let greeting = 'Hello';
greeting += ' World'; // 'Hello World'

let label = 'Count: ';
label += 5; // number is coerced โ†’ 'Count: 5'

๐Ÿ›’ Worked example โ€” a running cart total

Compound assignment keeps a running total tidy as items are added:

function createCart() {
  const items = [];
  let total = 0;

  return {
    add(name, price, qty = 1) {
      items.push({ name, price, qty });
      total += price * qty;   // compound assignment
      return this;            // enable chaining
    },
    applyDiscount(percent) {
      total *= (1 - percent / 100);
      return this;
    },
    summary() {
      return `${items.length} item(s), total $${total.toFixed(2)}`;
    }
  };
}

const cart = createCart()
  .add('Laptop', 999.99)
  .add('Mouse', 24.99, 2)
  .applyDiscount(10);

console.log(cart.summary()); // 3 item(s), total $945.87

Destructuring Assignment

Introduced in ES6, destructuring is a special assignment syntax that unpacks values from arrays or properties from objects into their own variables โ€” in a single, readable line.

๐Ÿ’ก Analogy โ€” unpacking a labelled box. An array is a box where position matters ("first item, second item"). An object is a box with labelled slots ("give me name and age"). Destructuring lets you take out exactly what you need and leave the rest.

Array destructuring

const [first, second] = [10, 20, 30];
console.log(first, second); // 10 20

// Skip elements with a bare comma
const [primary, , tertiary] = ['red', 'green', 'blue'];
console.log(primary, tertiary); // red blue

// Collect the rest
const [head, ...tail] = [1, 2, 3, 4];
console.log(head, tail); // 1 [2, 3, 4]

// Provide defaults
const [a, b, c = 99] = [1, 2];
console.log(a, b, c); // 1 2 99

// Swap two variables with no temp variable
let m = 1, n = 2;
[m, n] = [n, m];
console.log(m, n); // 2 1

Object destructuring

const person = {
  name: 'Alice',
  age: 30,
  address: { city: 'New York' }
};

const { name, age } = person;
console.log(name, age); // Alice 30

// Rename while unpacking
const { name: fullName } = person;
console.log(fullName); // Alice

// Reach into nested objects
const { address: { city } } = person;
console.log(city); // New York

// Defaults for missing keys
const { role = 'guest' } = person;
console.log(role); // guest

โœ… Where you will use it constantly

Destructuring function parameters keeps signatures self-documenting, and unpacking an API response turns a deeply nested object into flat, named variables:

async function loadUser(id) {
  const res = await fetch(`/api/users/${id}`);
  const data = await res.json();

  const { name, email, roles = ['user'] } = data;
  return { name, email, isAdmin: roles.includes('admin') };
}

Quirks & Floating-Point Gotchas

JavaScript's operators have a few behaviours that surprise newcomers. Knowing them ahead of time saves hours of debugging.

Division by zero does not throw

console.log(5 / 0);  // Infinity
console.log(-5 / 0); // -Infinity
console.log(0 / 0);  // NaN

+ is overloaded โ€” number OR string

console.log(5 + 5);   // 10  (number addition)
console.log('5' + 5); // '55' (string concatenation wins)
console.log('5' - 2); // 3   (- has no string meaning โ†’ numeric)
console.log('5' * 2); // 10  (numeric)

The rule: + prefers string concatenation whenever either operand is a string. Every other arithmetic operator forces numeric conversion.

The floating-point trap

Numbers are stored in the binary IEEE-754 format, which cannot represent every decimal exactly โ€” just as 1/3 cannot be written exactly in decimal.

Try it in a console:

console.log(0.1 + 0.2);          // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3);  // false
Why 0.1 + 0.2 is not exactly 0.3 The decimals 0.1 and 0.2 each become slightly-off binary approximations; adding them yields 0.30000000000000004 instead of exactly 0.3. 0.1 โ‰ˆ 0.1000โ€ฆ0006 + 0.2 โ‰ˆ 0.2000โ€ฆ0001 = 0.30000000000000004 not exactly 0.3 Tiny rounding errors in each value add up.
Figure 1 โ€” Binary floating point cannot store 0.1 or 0.2 exactly, so their sum drifts by a hair. Never compare money with ===.

The professional fix for money is to work in the smallest whole unit โ€” integer cents โ€” and only convert to dollars for display:

class Cart {
  #cents = 0;

  add(priceDollars, qty = 1) {
    const cents = Math.round(priceDollars * 100); // to integer cents
    this.#cents += cents * qty;
    return this;
  }
  applyDiscount(percent) {
    this.#cents -= Math.round(this.#cents * (percent / 100));
    return this;
  }
  totalDollars() {
    return (this.#cents / 100).toFixed(2); // back to dollars only to display
  }
}

const cart = new Cart().add(3.99, 2).add(2.50);
console.log(`$${cart.totalDollars()}`); // $10.48 โ€” exact
cart.applyDiscount(15);
console.log(`$${cart.totalDollars()}`); // $8.91 โ€” exact

Hands-on Exercise

๐Ÿ‹๏ธ Build a penny-accurate tip splitter

Objective: Write splitBill(subtotal, tipPercent, people) that returns the tip, the grand total, and the amount each person owes โ€” with no floating-point drift and no one shorted by a rounding error.

Requirements:

  1. Validate: subtotal > 0, tipPercent >= 0, and people a positive integer. Throw a clear error otherwise.
  2. Compute the tip and total working in integer cents.
  3. Split the total across people. If it does not divide evenly, the remaining cents go to the first few people so the parts sum exactly to the total.
  4. Return dollar strings via .toFixed(2).
๐Ÿ’ก Hint

Convert with Math.round(subtotal * 100). The base share is Math.floor(totalCents / people); the leftover is totalCents % people. Give one extra cent to the first leftover people. Notice both % and integer division doing real work here.

โœ… Sample solution
function splitBill(subtotal, tipPercent = 15, people = 1) {
  if (!(subtotal > 0)) throw new Error('Subtotal must be positive');
  if (tipPercent < 0) throw new Error('Tip cannot be negative');
  if (people < 1 || !Number.isInteger(people)) {
    throw new Error('People must be a positive whole number');
  }

  const subCents = Math.round(subtotal * 100);
  const tipCents = Math.round(subCents * (tipPercent / 100));
  const totalCents = subCents + tipCents;

  const base = Math.floor(totalCents / people);
  const leftover = totalCents % people;

  const shares = Array.from({ length: people }, (_, i) =>
    ((base + (i < leftover ? 1 : 0)) / 100).toFixed(2)
  );

  return {
    tip: (tipCents / 100).toFixed(2),
    total: (totalCents / 100).toFixed(2),
    perPerson: shares
  };
}

console.log(splitBill(50, 20, 3));
// { tip: '10.00', total: '60.00', perPerson: ['20.00', '20.00', '20.00'] }
console.log(splitBill(20, 15, 3));
// { tip: '3.00', total: '23.00', perPerson: ['7.67', '7.67', '7.66'] }
// The three shares add up to exactly $23.00.

๐ŸŽฏ Quick Quiz

Question 1: What does let y = x++; assign to y when x starts at 5?

Question 2: What is the value of '5' + 3?

Question 3: Why do professional apps store money as integer cents instead of dollars with decimals?

Summary & Quiz

๐ŸŽ‰ Key Takeaways

  • The six arithmetic operators are + - * / % **; % gives a remainder and ** raises to a power.
  • Prefix (++x) changes then returns; postfix (x++) returns then changes.
  • Unary + converts to a number; guard the result with Number.isNaN.
  • Compound assignment (+=, *=, โ€ฆ) and destructuring make reassignment concise and readable.
  • + concatenates when either side is a string; floating-point math is inexact, so do money in integer cents.

๐Ÿ“š Further Reading

๐Ÿš€ What's Next?

Now that you can transform values, the next step is comparing them. In Comparison and Logical Operators you'll learn how ===, &&, ||, and ?? drive every decision your program makes.

๐ŸŽ‰ Well done!

You can now do math in JavaScript without falling into the classic traps. On to decisions.