Originally published on php-to-js.learnio.dev — part of the PHP to JavaScript course. View this lesson on the original site.

Pure functions are predictable; impure ones touch the outside world

A pure function returns the same result for the same inputs and does not change anything outside itself. PHP services are often impure (database, cache, mail). JavaScript UI code mixes both — knowing which is which makes tests and AI-generated code easier to review.

PHP habit

<?php
// Pure: depends only on arguments
function lineTotal(int $qty, float $price): float {
    return $qty * $price;
}

// Impure: reads/writes outside state
function applyVat(float $amount): float {
    return $amount * (float) config('shop.vat_rate');
}

config() is the outside world. lineTotal is easy to unit test with two numbers.

JavaScript equivalent

// Pure
function lineTotal(qty, price) {
  return qty * price;
}

// Impure
function applyVat(amount) {
  return amount * getVatRateFromDom();
}

function getVatRateFromDom() {
  return Number(document.querySelector('[data-vat]')?.dataset.vat ?? 0.2);
}
Signal Usually impure
Reads document, window, fetch Yes
console.log, Date.now(), Math.random() Yes
Mutates an argument object in place Often yes
Returns new data from arguments only Often pure

Impure is not bad — you need impure code to show UI and call APIs. The habit is to keep pure helpers small and push impure edges to the boundary (one function that fetches, then pure functions that shape data).

Pure helpers inside map and reduce

const rows = [
  { qty: 2, price: 10 },
  { qty: 1, price: 5 },
];

const subtotal = rows
  .map((r) => lineTotal(r.qty, r.price))
  .reduce((sum, n) => sum + n, 0);

map and reduce here stay pure if lineTotal is pure. Calling fetch inside map would be impure and harder to test.

Failure mode: hidden mutation

function addFee(cart) {
  cart.total += 5; // mutates caller's object
  return cart;
}

Prefer return { ...cart, total: cart.total + 5 } when you want predictable data flow (see lesson 2.2).

Try it

Use the amber Try it panel below. Click Run.

function lineTotal(qty, price) {
  return qty * price;
}

function bill(rows, vatRate) {
  const subtotal = rows
    .map((r) => lineTotal(r.qty, r.price))
    .reduce((sum, n) => sum + n, 0);
  return { subtotal, total: subtotal * (1 + vatRate) };
}

const rows = [
  { qty: 2, price: 10 },
  { qty: 1, price: 5 },
];

console.log('pure bill', bill(rows, 0.2));
console.log('same inputs again', bill(rows, 0.2));

Running twice with the same inputs prints the same numbers — that is what you want from pure core logic before you add logging or DOM updates around it.

From PHP to JavaScript — Control Flow, Functions, and Scope