Suggest an editImprove this articleRefine the answer for “Pure functions and side effects in JavaScript”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Pure function** - a function that returns the same output for the same inputs and has no side effects. It does not read or write anything outside its own scope. ```javascript const add = (a, b) => a + b; // Pure: always 5 for (2, 3) let total = 0; const addToTotal = (n) => { total += n; return total; }; // Impure: mutates total ``` **Key point:** pure functions can be memoized, tested without mocks, and called in any order safely.Shown above the full answer for quick recall.Answer (EN)Image**Pure function** - a function that returns the same output for the same input every time and changes nothing outside itself. ## Theory ### TL;DR - Analogy: a pure function is like a vending machine. Same coins in, same soda out. It does not touch the store's inventory. - Main difference: pure means output depends only on inputs; impure means it depends on time, globals, or external state. - Decision rule: use pure for math, logic, and data transforms. Impure only where you must (API calls, DOM, timers). - `const` does not mean pure. Objects declared with `const` are still mutable. - Memoization (`React.memo`, `useMemo`) only works correctly with pure functions. ### Quick example ```javascript // Pure: same input always returns same output const add = (a, b) => a + b; add(2, 3); // 5, always // Impure: result changes when discount changes let discount = 0.1; const getPrice = (price) => price * (1 - discount); getPrice(100); // 90 today, 80 if discount becomes 0.2 // Pure fix: pass discount as an argument const getPricePure = (price, discount) => price * (1 - discount); getPricePure(100, 0.1); // 90, always for these inputs ``` The impure `getPrice` reads `discount` from the outer scope. Reset that variable in a test, or run the function in a different order, and you get a different result. That is the whole problem. ### Key difference Pure functions work like math equations: the output is fully determined by the inputs, nothing more. Side effects break that guarantee by reading from the outside world (`Date.now()`, a global variable) or writing to it (`array.push()`, `localStorage.setItem()`). Once a function does either, you cannot trust that the same call produces the same result. ### When to use - Math or logic calculations: pure. No exceptions. - Sorting, filtering, transforming arrays: pure (return a new array, never mutate the input). - React component rendering, Redux reducers: pure. React's reconciliation depends on this. - API calls, timers, DOM updates, logging: impure by nature. Isolate them at the edge of your code. - Need memoization: pure only. Caching breaks silently on impure functions. ### How V8 handles this V8 (the engine behind Chrome and Node.js) applies inline caching and escape analysis to functions that read no external state and produce no mutations. It can place return values on the stack instead of the heap and skip redundant property lookups. As soon as a function touches external state or performs I/O, V8 treats it as unpredictable and backs off those optimizations. This is why Redux recommends pure reducers, and why Immer uses Proxy traps to simulate in-place mutations while actually producing a new state object. ### Common mistakes **Mistake 1: thinking `Array.map` is always pure** `map` returns a new array, but the callback runs arbitrary code. If it touches external state, the whole call is impure. ```javascript let calls = 0; [1, 2].map(n => { calls++; return n * 2; }); // New array returned, but calls === 2 now. Side effect. ``` Fix: keep callbacks stateless. Every dependency goes in as an argument. **Mistake 2: treating `const` as immutable** `const` prevents reassignment. It does not prevent mutation of what the variable points to. ```javascript const arr = [1, 2]; arr.push(3); // arr is now [1, 2, 3]. Mutated. ``` Fix: use spread to create a new array, or `Object.freeze` for shallow immutability. **Mistake 3: closures with captured mutable state** A closure captures variables from its surrounding scope. If those variables are mutable, the function is impure even if it looks clean. ```javascript let x = 1; const increment = () => x++; // Reads and writes external x. Impure. ``` Fix: pass the value as a parameter instead of capturing it. **Mistake 4: assuming async functions can be pure** `fetch` is always impure: it depends on the network, time, and server state. Wrapping it in a named function does not change that. ```javascript const getData = () => fetch('/api'); // Impure. Network is external state. ``` Fix: mock I/O in tests. Accept that impure code lives at the boundary. **Mistake 5: `Object.freeze` freezes deeply** `Object.freeze` freezes only the top level. Nested objects remain mutable. ```javascript const state = Object.freeze({ user: { name: 'Alex' } }); state.user.name = 'Bob'; // Works. No error. Nested object is not frozen. ``` Fix: spread every level you update: `{ ...state, user: { ...state.user, name: 'Bob' } }`. ### Real-world usage - React: pure components re-render correctly with `React.memo`. Impure ones may produce stale renders. - Redux: reducers must be pure. Immer lets you write mutative-looking syntax that still produces new state. - Node/Express: pure validators (Joi schemas) run before any DB write. Validation is pure; the write is not. - Ramda/Lodash-fp: designed around pure functions. `R.map` and `R.filter` never mutate inputs. I once spent two hours debugging a totals calculation in a checkout flow. The bug was a function reading a global discount flag that an unrelated test had reset. Making the function pure and passing the discount as an argument fixed the test and the bug in a single change. ### Follow-up questions **Q:** Can a function that calls `console.log` inside be considered pure? **A:** No. `console.log` writes to an external I/O stream. That is a side effect, regardless of what the function returns. **Q:** Is `Array.sort()` pure? **A:** No. It mutates the original array in place. Use `[...arr].sort()` to get a sorted copy and leave the original untouched. **Q:** Why does `React.memo` fail with impure components? **A:** `React.memo` skips re-render when props have not changed. An impure component might produce different output with the same props (reading `Date.now()`, a global counter), so the cached result becomes wrong. **Q:** Redux Toolkit uses Immer inside `createSlice`. Does that make reducers impure? **A:** No. Immer intercepts mutations via Proxy and returns a new state object. The reducer receives state and returns a new one. V8 does not deoptimize because no real mutation escapes the function scope. **Q:** What is referential transparency? **A:** A pure function is referentially transparent: you can replace any call with its return value and the program behaves identically. That property is what makes memoization and safe parallel execution possible. ## Examples ### Basic: making a price function pure ```javascript // Impure: reads external discount variable let discount = 0.1; const getPriceImpure = (price) => price * (1 - discount); console.log(getPriceImpure(100)); // 90 discount = 0.2; console.log(getPriceImpure(100)); // 80 - same call, different result // Pure: all inputs are explicit const getPricePure = (price, discount) => price * (1 - discount); console.log(getPricePure(100, 0.1)); // 90, always console.log(getPricePure(100, 0.2)); // 80, always - predictable ``` Every external dependency becomes a parameter. Now you can test this with any combination of values without any setup or teardown. ### Intermediate: filtering a todo list in React ```javascript // Impure: mutates the original array (breaks React reconciliation) const filterCompletedImpure = (todos) => { todos.forEach((todo, i) => { if (todo.complete) todos.splice(i, 1); }); return todos; }; // Pure: returns a new array, original untouched const filterCompleted = (todos) => todos.filter(todo => !todo.complete); const todos = [ { id: 1, complete: true }, { id: 2, complete: false }, ]; console.log(filterCompleted(todos)); // [{ id: 2, complete: false }] console.log(todos); // [{ id: 1, complete: true }, { id: 2, complete: false }] - unchanged ``` React compares previous and next props to decide whether to re-render. Mutating an array in place keeps the same reference, so React may skip the update and leave the UI stale. ### Advanced: shallow freeze vs deep clone in Redux-style state ```javascript const state = { count: 5, user: { name: 'Alex' } }; // Object.freeze is shallow const frozen = Object.freeze({ ...state }); frozen.count = 10; // Silent fail in sloppy mode, TypeError in strict frozen.user.name = 'Bob'; // Works. Nested object is NOT frozen. // Pure update with spread const nextState = { ...state, count: state.count + 1 }; console.log(nextState); // { count: 6, user: { name: 'Alex' } } console.log(state); // { count: 5, user: { name: 'Alex' } } - untouched // Deep update: spread every level you change const nextStateDeep = { ...state, user: { ...state.user, name: 'Bob' }, }; console.log(nextStateDeep.user); // { name: 'Bob' } console.log(state.user); // { name: 'Alex' } - still safe ``` In production Redux, Immer handles nested updates automatically. Knowing why it exists shows you understand the constraint, not just the API.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.