JavaScript Interview Prep
Functions Deep Dive

Pure Functions & Side Effects

The Vending Machine Rule

LinkedIn Hook

A pure function is like a vending machine. Same coin, same button, same snack — every time.

It doesn't log to console. It doesn't mutate your array. It doesn't call an API. It doesn't check the current time. It takes input, produces output, and changes nothing else.

Sounds strict? It is. But that strictness is why Redux reducers, React hooks, memoization, and parallel execution all work. Pure functions are testable without mocks, cacheable without invalidation, and safe to run concurrently.

In this lesson you'll see what makes a function pure, the full list of common side effects that make functions impure, and how to refactor an impure cart-calculation into a pure pipeline.

Pure functions aren't a "nice to have." They're the architectural boundary that separates a reasonable codebase from a bug swamp.

Read the full lesson -> [link]

#JavaScript #InterviewPrep #PureFunctions #FunctionalProgramming #SideEffects #ReactRedux #CodingInterview


Pure Functions & Side Effects thumbnail


What You'll Learn

  • The two rules that define a pure function
  • The full list of common side effects that make a function impure
  • Why pure functions are testable, cacheable (memoizable), and safe to parallelize

The Two Rules

A pure function is a function that:

  1. Same input = same output — given the same arguments, it always returns the same result.
  2. No side effects — it doesn't modify anything outside its own scope (no mutating external variables, no DOM manipulation, no API calls, no console.log).

Think of a pure function like a vending machine. You put in the same coin and press the same button, you get the same snack. Every time. The machine doesn't change the weather, call your boss, or rearrange the store. It just takes input and produces output.

Pure Function Examples

// PURE — same input always gives same output, no side effects
function add(a, b) {
  return a + b;
}

console.log(add(2, 3)); // 5 — always
console.log(add(2, 3)); // 5 — always
console.log(add(2, 3)); // 5 — always

// PURE — creates and returns new data without mutating input
function toUpperCase(str) {
  return str.toUpperCase();
}

// PURE — returns new array, doesn't mutate original
function addItem(array, item) {
  return [...array, item];
}

const original = [1, 2, 3];
const newArr = addItem(original, 4);
console.log(original); // [1, 2, 3] — unchanged!
console.log(newArr);   // [1, 2, 3, 4]

Impure Function Examples (Same Logic, With Side Effects)

// IMPURE — depends on external state
let taxRate = 0.2;
function calculateTax(price) {
  return price * taxRate; // depends on external 'taxRate'
}
// If taxRate changes, same input gives different output

// IMPURE — mutates external state
let total = 0;
function addToTotal(amount) {
  total += amount; // modifies external variable
  return total;
}
console.log(addToTotal(10)); // 10
console.log(addToTotal(10)); // 20 — different result for same input!

// IMPURE — mutates input
function addItemMutating(array, item) {
  array.push(item); // mutates the original array
  return array;
}

const arr = [1, 2, 3];
addItemMutating(arr, 4);
console.log(arr); // [1, 2, 3, 4] — original is mutated!

Side-by-Side Comparison: Pure vs Impure

// IMPURE version — mutates input, depends on external state
let discount = 0.1;
function calculatePrice(cart) {
  let total = 0;
  for (let item of cart) {
    item.discountedPrice = item.price * (1 - discount); // MUTATES input
    total += item.discountedPrice;
  }
  console.log("Total: " + total); // SIDE EFFECT (console.log)
  return total;
}

// PURE version — no mutation, no side effects
function calculatePricePure(cart, discount) {
  const pricedCart = cart.map(item => ({
    ...item,
    discountedPrice: item.price * (1 - discount)
  }));
  const total = pricedCart.reduce((sum, item) => sum + item.discountedPrice, 0);
  return { pricedCart, total }; // returns new data
}

const cart = [{ name: "Shirt", price: 100 }, { name: "Pants", price: 200 }];
const result2 = calculatePricePure(cart, 0.1);
console.log(cart[0].discountedPrice); // undefined — original untouched!
console.log(result2.total);           // 270

Common Side Effects to Watch For

// Each of these makes a function impure:
function sideEffectExamples() {
  console.log("Logging");            // I/O
  document.title = "New Title";      // DOM mutation
  localStorage.setItem("k", "v");    // Storage
  Math.random();                     // Non-deterministic
  new Date();                        // Non-deterministic
  fetch("/api/data");                // Network call
  someExternalVariable = 42;         // External mutation
  arr.push(item);                    // Input mutation
}

Benefits of Pure Functions

// 1. TESTABLE — no setup/teardown needed
function multiply(a, b) { return a * b; }
// Test: assert multiply(3, 4) === 12. Done. No mocking needed.

// 2. CACHEABLE (memoization) — same input = same output = cache it
function memoize(fn) {
  const cache = {};
  return function(...args) {
    const key = JSON.stringify(args);
    if (cache[key] !== undefined) {
      return cache[key];
    }
    cache[key] = fn(...args);
    return cache[key];
  };
}

function expensiveCalculation(n) {
  console.log("Computing...");
  return n * n * n;
}

const memoized = memoize(expensiveCalculation);
console.log(memoized(5)); // "Computing..." -> 125
console.log(memoized(5)); // 125 (cached — no "Computing...")
console.log(memoized(5)); // 125 (cached)

// 3. PREDICTABLE — easy to reason about
// 4. PARALLELIZABLE — no shared state, safe to run concurrently

Pure Functions & Side Effects visual 1


Common Mistakes

  • Thinking a function is pure because it doesn't console.log — pure also means no mutation of inputs. arr.push(x); return arr; is impure even though it seemingly just "returns a value."
  • Treating Math.random() and new Date() as pure — they produce different outputs each call, which breaks rule 1 (same input, same output). Pass the random value or timestamp as an argument instead.
  • Mutating nested objects inside a "pure" spread — return { ...state, user: state.user } still shares the user reference. Deep updates need nested spreads or a library like Immer.

Interview Questions

Q: What is a pure function? Give two rules.

A pure function: (1) always returns the same output for the same input (deterministic), and (2) produces no side effects (doesn't modify external state, no I/O, no mutation of inputs).

Q: Is console.log a side effect?

Yes. Any I/O operation (logging, DOM manipulation, network requests, file access) is a side effect. A function that calls console.log is impure.

Q: Why are pure functions easier to test?

Because they have no dependencies on external state, you don't need to mock anything, set up environments, or clean up afterward. You just call the function with inputs and assert the output. No surprises.

Q: Can a real application be 100% pure?

No. Real apps need side effects — user input, API calls, DOM updates. The goal is to push side effects to the edges of your system and keep the core logic pure. This is the architecture of Redux, React hooks, and functional programming patterns.

Q: Is Math.random() pure? Why or why not?

No. Math.random() returns a different value on every call with no input, so it violates rule 1 (same input, same output) and is non-deterministic. To keep callers pure, pass the random number in as an argument.

Q: Name 5 common side effects.

console.log (I/O), DOM mutation (document.title = ...), network calls (fetch), non-determinism (Math.random, new Date), and mutation of external variables or input arguments (arr.push(x), obj.prop = y).

Q: What is memoization and why does it only work with pure functions?

Memoization caches a function's result keyed by its inputs so repeated calls with the same arguments return the cached value instead of recomputing. It only works for pure functions because only pure functions guarantee that the same input always produces the same output — caching an impure function would serve stale or wrong results.


Quick Reference — Cheat Sheet

PURE FUNCTIONS — QUICK MAP

The 2 rules:
  1. Same input  -> same output   (deterministic)
  2. No side effects              (no external mutation, no I/O)

Common side effects (make a fn impure):
  - console.log / I/O
  - DOM mutation (document.title = ...)
  - Network (fetch, XHR)
  - Storage (localStorage, cookies)
  - Non-determinism (Math.random, new Date)
  - Mutating inputs (arr.push, obj.prop = ...)
  - Writing to external variables

Benefits:
  - TESTABLE      no mocks, no setup
  - CACHEABLE     safe to memoize
  - PREDICTABLE   easy to reason about
  - PARALLEL      safe to run concurrently

Architecture rule:
  Keep the core pure. Push side effects to the edges.
  (This is Redux. This is React hooks. This is FP.)

Previous: IIFE Next: Function Currying -> The Assembly Line


This is Lesson 6.4 of the JavaScript Interview Prep Course — 14 chapters, 87 lessons.

On this page