ESC

Type to search the knowledge base.

Pure Functions

Same inputs → same output, no side effects — why purity helps tests and UI, and how to isolate effects at the edges.

beginner3 min read
  • javascript
  • pure-functions

A pure function (1) returns a value determined only by its arguments, and (2) does not mutate external state or perform I/O. Same inputs → same output. Always.

// pure
function add(a, b) {
  return a + b;
}

function fullName({ first, last }) {
  return `${first} ${last}`;
}

// impure
let tax = 0.2;
function priceWithTax(n) {
  return n * (1 + tax); // depends on external mutable tax
}

function save(user) {
  localStorage.setItem('user', JSON.stringify(user)); // I/O
  return user;
}

Why care in frontend work

Benefit How purity helps
Tests No mocks for clock/network if core logic is pure
Reasoning Refactors don’t summon hidden state bugs
Memoization Safe to cache (see memoization)
Concurrent UI Fewer “run twice” surprises when effects are explicit

React’s ideal: render function pure w.r.t. props/state; effects live in useEffect / event handlers.

Mutations of arguments

// impure — mutates input
function addItem(cart, item) {
  cart.items.push(item);
  return cart;
}

// pure — new structure
function addItemPure(cart, item) {
  return {
    ...cart,
    items: [...cart.items, item],
  };
}

Returning a new object isn’t required by math, but in UI apps immutable updates make purity and change detection easier.

Hidden impurity

function id() {
  return Math.random(); // non-deterministic
}

function nowLabel() {
  return Date.now(); // time
}

function readLen() {
  return document.querySelectorAll('li').length; // DOM
}

Push nondeterminism to the edge:

function label(ts) {
  return new Date(ts).toISOString();
}
// caller: label(Date.now())

Partial purity is still useful

async function loadUser(id, fetchImpl = fetch) {
  const res = await fetchImpl(`/api/users/${id}`);
  if (!res.ok) throw new Error('load failed');
  return normalizeUser(await res.json()); // normalizeUser pure
}

Inject fetch for tests; keep normalizeUser pure and unit-tested hard.

Red flags in code review

  • Function name is a noun computation but it writes to a module-level cache without documentation
  • “Helper” that toggles DOM classes
  • Selectors that both filter and kick off analytics

Interview answer (out loud)

“Pure functions depend only on inputs and have no side effects, so they’re easy to test and memoize. In UI code I keep rendering and transforms pure and push I/O, time, and random to the edges. Mutating arguments counts as impurity in practice for frontend state.”

Referential transparency

If f(x) is pure, any occurrence can be replaced by its result without changing behavior. That’s why pure helpers compose and why property-based tests work well on them.

Local mutation is OK

function sum(nums) {
  let s = 0; // local mutation
  for (const n of nums) s += n;
  return s;
}

Purity is about observable external effects and deterministic output — not “never use let.” Mutating a locally created object and returning it is still pure if nothing outside could see the intermediate states.

Time and pure UI snapshots

function viewModel(state, now) {
  return { ...state, stale: now - state.updatedAt > 60_000 };
}

Pass now in; don’t call Date.now() inside if you want snapshot tests to be stable.

Further reading

Related guides