A practical guide to understanding purity in JavaScript, with real-world code and why it matters for maintainable, testable apps.

Introduction
“Pure functions” get thrown around in every functional programming discussion, but many developers wonder: What actually makes a function pure? And why should I care when building apps?
Here’s the simple truth: pure functions are predictable, testable, and composable. Impure functions are powerful, but risky — they depend on or mutate external state. Both have their place, but knowing the difference will help you avoid bugs, simplify tests, and design better APIs.
This guide breaks it down with plain-English rules, code examples, real-world use cases, and gotchas.
1. What Is a Pure Function?
A pure function is one that:
- Always returns the same output for the same input.
- Has no side effects. It doesn’t modify external state, global variables, DOM, databases, or logs.
👉 Think of it like math: f(x) = 2x
always returns the same answer for a given x
.
2. What Is an Impure Function?
An impure function either:
- Depends on external state (global variables, current time, random numbers).
- Produces side effects (modifies objects, writes to files, logs to console).
👉 Think of it like Math.random()
— same input, different output.
3. Pure vs Impure — Quick Examples
Pure Example
function add(a, b) {
return a + b;
}
add(2, 3); // always 5
add(2, 3); // still 5
Impure Example
let counter = 0;
function increment() {
counter++;
return counter;
}
increment(); // 1
increment(); // 2 (different output, same input)
The impure function depends on and mutates external state.
4. Real-World Pure Functions
Array transformations
const nums = [1, 2, 3];
const doubled = nums.map(n => n * 2);
// [2, 4, 6] → predictable
String formatting
function capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
capitalize("hello"); // "Hello"
Data validation
function isEmailValid(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
isEmailValid("dev@example.com"); // true
Pure functions make utilities and helpers easy to test and reuse.
5. Real-World Impure Functions
Modifying global variables
let theme = "light";
function setDarkMode() {
theme = "dark"; // mutates external state
}
Reading system time
function getCurrentHour() {
return new Date().getHours(); // depends on external system clock
}
Randomness
function rollDice() {
return Math.floor(Math.random() * 6) + 1;
}
DOM manipulation
function addButton() {
const btn = document.createElement("button");
document.body.appendChild(btn); // side effect
}
These are impure by nature — but necessary for I/O, UI, and APIs.
6. Why Pure Functions Are Valuable
- ✅ Predictable: No surprises — same input, same output.
- ✅ Easy to test: Just pass arguments, check return. No setup/teardown needed.
- ✅ Composability: Can combine them like Lego blocks.
- ✅ Memoization-friendly: Cache results safely.
- ✅ Parallelizable: No shared state = no race conditions.
7. Why We Still Need Impure Functions
You can’t build a real app with pure functions alone. You need impurity to:
- Read/write from APIs and databases.
- Interact with the DOM.
- Log, track metrics, or send emails.
- Handle randomness and time.
👉 The trick is isolate side effects at the boundaries and keep the core pure.
8. Functional Core, Imperative Shell
A great architecture pattern:
- Core logic = pure functions. (e.g., calculate totals, validate input).
- Shell = impure functions. (e.g., read HTTP request, write DB, log).
Example: Shopping Cart
// Pure: calculates total
function calculateTotal(cart) {
return cart.reduce((sum, item) => sum + item.price * item.qty, 0);
}
// Impure: logs total
function checkout(cart) {
const total = calculateTotal(cart);
console.log("Charging $" + total); // side effect
// chargeCreditCard(total) ...
}
By keeping calculateTotal
pure, you can test it in isolation without mocking databases or logs.
9. Common Gotchas
Mutating arguments
// ❌ Impure
function addToCart(cart, item) {
cart.push(item);
return cart;
}
This mutates the original array.
// ✅ Pure
function addToCart(cart, item) {
return [...cart, item];
}
Hidden dependencies
let taxRate = 0.1;
// ❌ Impure
function addTax(amount) {
return amount * (1 + taxRate); // depends on external variable
}
Better: pass it in explicitly.
function addTax(amount, rate) {
return amount * (1 + rate);
}
Logging in “pure” helpers
// ❌ Impure
function double(n) {
console.log("doubling"); // side effect
return n * 2;
}
Remove logs inside core functions; log at the call site instead.
10. Pure vs Impure in Frameworks
React
- Pure: Components that always render the same UI given the same props.
- Impure: Components that call
useEffect
to fetch data or update DOM.
// Pure
function Greeting({ name }) {
return <h1>Hello, {name}!</h1>;
}
// Impure
function UserFetcher({ id }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/api/users/${id}`).then(r => r.json()).then(setUser);
}, [id]);
return user ? <h1>{user.name}</h1> : "Loading...";
}
Node.js
- Pure: Data formatters, validators, business rules.
- Impure: Express route handlers (read request, write response).
11. Testing: Why Purity Wins
Pure
test("tax calculation", () => {
expect(addTax(100, 0.1)).toBe(110);
});
No mocks. Easy.
Impure
test("checkout logs", () => {
const spy = jest.spyOn(console, "log").mockImplementation(() => {});
checkout([{ price: 10, qty: 2 }]);
expect(spy).toHaveBeenCalledWith("Charging $20");
spy.mockRestore();
});
Harder — you must intercept side effects.
12. When to Choose Purity vs Impurity
- Use pure functions for calculations, transformations, validation, formatting.
- Use impure functions for I/O, randomness, time, external APIs.
- When in doubt: push impurity to the edges, keep the core pure.
13. Quick Reference Table

Conclusion
Pure vs impure isn’t about “good vs bad” — it’s about knowing when to use each. Pure functions give you safety, speed, and simplicity. Impure functions connect your app to the real world. The art of clean code is balancing both: keep your core pure and shell impure.
Pro tip: Next time you write a function, ask: If I call this twice with the same inputs, do I always get the same output? Does it affect anything outside itself? If not, congrats — it’s pure.
Call to Action
What’s the sneakiest impure function you’ve accidentally written — logging inside a “utility,” or mutating arrays in a reducer?
💬 Share your story in the comments.
🔖 Bookmark this as your purity checklist.
👩💻 Share with a teammate who still thinks Math.random()
is pure.
Leave a Reply