Explain pure functions and side effects
Explain pure functions and side effects
A pure function always produces the same output for the same input and does not cause any observable changes outside itself. It does not read or write global state, mutate its parameters, perform I/O, or rely on time or randomness unless those are passed in as inputs. Pure functions are predictable, easy to test, and simple to reason about.
A side effect in programming refers to any action a function performs that affects something outside itself, or depends on something that can change outside its control. It's called a "side" effect because it happens alongside the main purpose of the function — instead of just returning a value, the
function is also changing the world around it.
Let's break that down clearly.
- The idea of "outside world or shared state"
Every function has its own scope — the variables and values defined inside it.
If a function reads or modifies something beyond its scope, that's considered interacting with the outside world or shared state.
Examples of shared state:
- Global variables
- Data stored in files, databases, or APIs
- The browser DOM (elements on a web page)
- External services (network requests)
- Console logs (which affect the program's visible output)
- Any variable or object that was created outside the function and then modified inside it
So, whenever a function touches these — by reading, writing, or depending on them — it is performing a side effect.
- Why side effects happen
Most real-world programs exist to cause side effects.
If you write to a database, send an email, or update the screen — you're changing the world outside the function. That's useful and necessary.
However, side effects make code less predictable because the same function call might produce different results depending on what's happening elsewhere in the system.
For example:
- A function that reads the current time or a random number will give different outputs each call.
- A function that modifies a shared variable affects other parts of the program in ways that may not be obvious.
- This means that side effects create coupling — parts of the program become dependent on each other's hidden behaviors.
- Why minimizing side effects matters
When a function has side effects, it becomes harder to test, debug, or reason about:
- You can't easily predict the result without knowing the full program state.
- You can't safely reuse it in different contexts. Bugs may appear when multiple functions modify the same data in unexpected orders.
- That's why in good program design, side effects are not eliminated (since they're needed for interaction) but isolated — kept at the boundaries of the system.
- This approach makes your core logic pure (depending only on inputs and producing outputs) and pushes unavoidable side effects to specific, controlled areas (like one module handling I/O or DOM updates).
- Example to think about
Imagine a web application that calculates total price and then updates the page: The calculation part (subtotal + tax) is pure — it always gives the same result for the same inputs.
The DOM update (document.querySelector("#total").textContent = total) is a side effect — it changes something visible outside the function.
If you separate these concerns — one function that purely computes, and another that updates the DOM — you can easily test the computation logic without worrying about browser behavior.
Later, if the display code changes (say, switching to a different UI framework), your pure logic remains safe and reusable.
- Side effects are not evil — they just need boundaries
Every useful program must eventually perform side effects, because that's how it communicates with the world (displaying output, saving data, sending requests).
The key is controlling them:
- Keep pure logic functions independent and predictable.
- Let a small part of the code handle side effects explicitly (for example, one function responsible for logging or rendering).
- This approach keeps your codebase more modular, easier to test, and less prone to bugs that come from unpredictable changes in shared state.
- Examples
// pure
function add(a, b) {
return a + b;
};
// impure: reads and writes external state
let total = 0;
function addToTotal(x) {
total += x;
return total;
};
// impure: mutates input
function pushItem(arr, v) {
arr.push(v);
return arr;
};
// pure alternative: returns a new array
function append(arr, v) {
return arr.concat(v);
};
Common misconceptions
- Pure functions cannot use variables. They can use parameters and constants; they just cannot rely on or mutate external changing state.
- Logging is harmless. Logging is a side effect.
- A function that happens to return the same value today is pure. Purity requires guarantees for all time with identical inputs and no external changes.
Practice questions
- Give three benefits of keeping your core logic pure.
- Trivial Testability and Confidence
- Predictable Rendering and Memoization
- Decoupling State from Business Logic
- Convert an impure function that pushes into an array into a pure version.
// ❌ Impure: Mutates the original array reference.
// If 'cart' is a React state variable, modifying it like this will NOT trigger a re-render.
const addItemImpure = (cart: string[], newItem: string): string[] => {
cart.push(newItem);
return cart;
};
const myCart = ['apple', 'banana'];
addItemImpure(myCart, 'orange');
// myCart is now ['apple', 'banana', 'orange'] - the original data was altered.
// Pure: Leaves the original array untouched and returns a new reference.
// Safe for React state, Redux reducers, and memoization dependencies.
const addItemPure = (cart: readonly string[], newItem: string): string[] => {
return [...cart, newItem];
};
const myCart = ['apple', 'banana'];
const updatedCart = addItemPure(myCart, 'orange');
// myCart remains ['apple', 'banana']
// updatedCart is ['apple', 'banana', 'orange']
- List five examples of side effects in typical web applications.
- Network Mutations (I/O)
- Direct DOM Interventions
- Persistent Storage I/O
- Asynchronous Timers
- Global Event Subscriptions