Skip to content

Expert · Functional patterns and testing · topic 47 of 61 · 25 min

Pure functions and immutability

Explanation

A pure function returns the same output for the same input and changes nothing outside itself. Immutability means you never change existing data; you create updated copies instead. Together they make bugs easier to find, code easier to test, and they are exactly what frameworks like React expect.

Why functional style?

Many bugs come from data changing unexpectedly: a function deep inside the code changes an array that three other parts of the program are also using. Functional programming is a style that reduces this by preferring pure functions and data that does not change. You do not need a special language; JavaScript supports this style well.

Pure functions

A function is pure when (1) its result depends only on its arguments, and (2) it has no side effects. A side effect is anything a function does besides returning a value: changing a variable outside, changing its arguments, writing to a file or database, logging, reading the clock or Math.random.

javascript
// Impure: depends on and changes outside state
let total = 0;
function addToTotal(x) { total += x; return total; }

// Pure: same input → same output, nothing changed outside
function add(a, b) { return a + b; }

// Impure: result depends on the current time
function isWeekend() { return [0, 6].includes(new Date().getDay()); }
// Pure version: pass the date in
function isWeekendOn(date) { return [0, 6].includes(date.getDay()); }

Real programs need side effects (saving orders, sending SMS). The goal is to push them to the edges: keep the core logic (prices, discounts, validation) pure, and do the I/O in a thin outer layer. The pure core is then trivial to test.

Immutable updates

Immutability means you never change an existing object or array. To "change" something, you make a new version with the change, leaving the old one untouched. Spread, map, filter, slice, toSorted and with all return new values.

javascript
const cart = [{ sku: 'A', qty: 1 }, { sku: 'B', qty: 2 }];

const added = [...cart, { sku: 'C', qty: 1 }];                         // add
const removed = cart.filter((item) => item.sku !== 'A');                // remove
const updated = cart.map((item) => item.sku === 'B' ? { ...item, qty: 5 } : item); // change one
const sorted = cart.toSorted((a, b) => b.qty - a.qty);                  // sort without mutating

console.log(cart); // still the original two items, untouched
Mutating (avoid in shared data)Immutable alternative
arr.push(x)[...arr, x]
arr.splice(i, 1)arr.filter((_, j) => j !== i)
arr[i] = xarr.with(i, x) or arr.map(...)
arr.sort()arr.toSorted()
obj.key = v{ ...obj, key: v }
delete obj.keyconst { key, ...rest } = obj

Why this helps

  • Easier debugging: data only changes where you create new versions, so you can follow it.
  • Easy "undo" and history: keep the old versions.
  • Cheap change detection: if oldState !== newState, something changed. React, Redux and many state libraries rely on this.
  • Safe sharing: no function can surprise another by changing shared data.
Careful: Spread copies only one level. { ...order, address: order.address } still shares the same address object, so order.address.city = 'X' on the copy changes both. Copy every level you change: { ...order, address: { ...order.address, city: 'X' } }.
Think of it like this: A pure function is like a weighing scale at a sabzi shop: put 1 kg of onions on it and it always shows 1 kg, and weighing does not change the onions. An impure function is like a scale that also secretly takes one onion every time you use it.
Tip: Making big copies in a hot loop can be slow. Immutability is the default for shared state and logic; inside a function, mutating a local array you just created (and nobody else sees) is perfectly fine.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Pure functions and immutability · 1/6

Pure functions and immutability

In this lesson you will write pure functions and update data without changing it.

Prefer a video by a teacher? Find free videos on “javascript pure functions immutability functional programming” on YouTube ↗

In real life: Undo in a billing screen

A restaurant POS app lets the cashier undo the last change to a bill. Because every edit creates a new bill object instead of changing the old one, the app simply keeps a list of previous bills. Undo means showing the previous version.

javascript
const history = [{ items: [] }];
const current = () => history.at(-1);
const addItem = (bill, item) => ({ ...bill, items: [...bill.items, item] });
history.push(addItem(current(), 'Paneer tikka'));
history.push(addItem(current(), 'Naan'));
history.pop(); // undo
console.log(current().items); // ['Paneer tikka']

Practice (0/3 done)

Task 1 · Code · Javascript

Make addTax pure: it should not change the global taxTotal, just return amount plus 18% tax, rounded.

Task 2 · Code · Javascript

Write setQty(cart, sku, qty) that returns a NEW cart where the item with that sku has the new qty; items with qty 0 are removed. The original cart and its item objects must not change.

Task 3 · Code · Javascript

Write moveCity(state, userId, city) for state = { users: [{ id, name, address: { city } }] }. Return a new state where that user's address.city is changed. Every object on the path must be copied; untouched users must keep the SAME object (so change detection works).

Quick quiz

Question 1

1.Which function is pure?

Question 2

2.Which is an immutable way to add an item?

Question 3

3.Why do React and Redux like immutable updates?

Question 4

4.const b = { ...a }; b.inner.x = 1; Does a.inner.x change?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

Which function is pure?

Answer: (a) => a * 2

Only a * 2 depends only on its input and changes nothing.

Which is an immutable way to add an item?

Answer: [...list, x]

Spread creates a new array.

Why do React and Redux like immutable updates?

Answer: A changed reference cheaply shows that something changed

Comparing references is fast change detection.

const b = { ...a }; b.inner.x = 1; Does a.inner.x change?

Answer: Yes, inner is shared

Spread is shallow.

Which function is pure?Related

Answer: One that returns a * b

a * b depends only on its inputs and changes nothing outside.

From Functions: declarations, expressions and return

Which of these is a pure function?Related

Answer: (n) => n * 2

n * 2 always gives the same output for the same input and changes nothing. The others change an outer variable, print (a side effect) or give random results.

From Scope, hoisting and closures

Why do interfaces make testing easier?Related

Answer: You can pass a fake implementation instead of a real network or database one

Code that depends on an interface can be tested with a fake or in-memory implementation.

From Abstract classes, interfaces and good class design

What is a functional interface?Related

Answer: An interface with exactly one abstract method

Lambdas implement functional interfaces.

From Lambdas and functional interfaces

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 4 more topics in “Functional patterns and testing” before its exam: Composition, currying and pipelines, Memoization, debounce and throttle, Testing your code, Test doubles and testable design.
  2. Module 10 exam: Functional patterns and testing · 12 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, Performance and Node.js. 61 topics left in the course before the final exam.

← Previous: Utility types and real-world TypeScript