, , ,

Pure Functions vs. Impure Functions: Real Examples

Posted by

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

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:

  1. Always returns the same output for the same input.
  2. 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

Your email address will not be published. Required fields are marked *