Docs

README

5.9 Pure Functions and Side Effects

Overview

Pure functions are functions that always produce the same output for the same input and have no side effects. Understanding pure functions is fundamental to functional programming and writing predictable, testable, and maintainable code.


Table of Contents

  1. •What is a Pure Function?
  2. •Characteristics of Pure Functions
  3. •Side Effects Explained
  4. •Pure vs Impure Examples
  5. •Benefits of Pure Functions
  6. •Common Side Effects
  7. •Managing Side Effects
  8. •Immutability
  9. •Referential Transparency
  10. •Best Practices

What is a Pure Function?

A pure function:

  1. •Always returns the same output for the same input
  2. •Has no side effects (doesn't modify external state)
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│                    PURE FUNCTION                         │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│                                                          │
│   INPUT ──────► PURE FUNCTION ──────► OUTPUT            │
│                      │                                   │
│                      │                                   │
│                 No external                              │
│                 interactions                             │
│                                                          │
│   Same input ALWAYS produces same output                │
│                                                          │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

Simple Example

// āœ… Pure function
function add(a, b) {
  return a + b;
}

add(2, 3); // Always 5
add(2, 3); // Always 5

// āŒ Impure function
let total = 0;
function addToTotal(value) {
  total += value; // Modifies external state
  return total;
}

addToTotal(5); // Returns 5
addToTotal(5); // Returns 10 (different output!)

Characteristics of Pure Functions

1. Deterministic

Same inputs always produce same outputs.

// āœ… Deterministic
function multiply(a, b) {
  return a * b;
}

// Always predictable
multiply(4, 5); // 20
multiply(4, 5); // 20
multiply(4, 5); // 20

// āŒ Non-deterministic
function randomMultiply(a) {
  return a * Math.random();
}

randomMultiply(5); // 2.3456...
randomMultiply(5); // 4.1234... (different!)

2. No Side Effects

Doesn't modify anything outside its scope.

// āœ… No side effects
function formatName(firstName, lastName) {
  return `${firstName} ${lastName}`;
}

// āŒ Has side effects
let greeting;
function setGreeting(name) {
  greeting = `Hello, ${name}`; // Modifies external variable
}

3. Depends Only on Input

Doesn't use external mutable state.

// āœ… Depends only on input
function calculateTax(amount, rate) {
  return amount * rate;
}

// āŒ Depends on external state
const taxRate = 0.2;
function calculateTaxImpure(amount) {
  return amount * taxRate; // Uses external variable
}

Side Effects Explained

A side effect is any interaction with the outside world.

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│                    SIDE EFFECTS                          │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│                                                          │
│  • Modifying external variables                          │
│  • Modifying function arguments (mutation)               │
│  • console.log / DOM manipulation                        │
│  • HTTP requests (fetch, AJAX)                          │
│  • Reading from / writing to files                       │
│  • Accessing databases                                   │
│  • Getting current date/time                            │
│  • Random number generation                              │
│  • Throwing exceptions                                   │
│                                                          │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

Examples of Side Effects

// Modifying external variable
let count = 0;
function increment() {
  count++; // Side effect
}

// Mutating argument
function addItem(arr, item) {
  arr.push(item); // Side effect - mutates input
}

// Console output
function log(message) {
  console.log(message); // Side effect
}

// API call
async function fetchUser(id) {
  const response = await fetch(`/api/users/${id}`); // Side effect
  return response.json();
}

// Current time
function getTimestamp() {
  return Date.now(); // Side effect (non-deterministic)
}

Pure vs Impure Examples

Array Operations

// āŒ Impure - mutates original array
function addItemImpure(arr, item) {
  arr.push(item);
  return arr;
}

// āœ… Pure - returns new array
function addItemPure(arr, item) {
  return [...arr, item];
}

// āŒ Impure - mutates original
function removeFirstImpure(arr) {
  arr.shift();
  return arr;
}

// āœ… Pure - returns new array
function removeFirstPure(arr) {
  return arr.slice(1);
}

Object Operations

// āŒ Impure - mutates original object
function updateAgeImpure(person, newAge) {
  person.age = newAge;
  return person;
}

// āœ… Pure - returns new object
function updateAgePure(person, newAge) {
  return { ...person, age: newAge };
}

// āŒ Impure - mutates nested property
function updateAddressImpure(person, city) {
  person.address.city = city;
  return person;
}

// āœ… Pure - returns new object with new nested object
function updateAddressPure(person, city) {
  return {
    ...person,
    address: { ...person.address, city },
  };
}

String Operations

// āœ… String methods are pure (strings are immutable)
function formatTitle(str) {
  return str.toUpperCase().trim();
}

const original = '  hello world  ';
const formatted = formatTitle(original);

console.log(original); // "  hello world  " (unchanged)
console.log(formatted); // "HELLO WORLD"

Math Operations

// āœ… Pure mathematical functions
function square(x) {
  return x * x;
}

function average(numbers) {
  if (numbers.length === 0) return 0;
  const sum = numbers.reduce((a, b) => a + b, 0);
  return sum / numbers.length;
}

function clamp(value, min, max) {
  return Math.min(Math.max(value, min), max);
}

Benefits of Pure Functions

1. Predictability

// Always know what to expect
const result = multiply(4, 5); // Always 20

2. Testability

// Easy to test - no setup needed
function add(a, b) {
  return a + b;
}

// Simple tests
console.assert(add(1, 2) === 3);
console.assert(add(-1, 1) === 0);
console.assert(add(0, 0) === 0);

3. Memoization

// Can safely cache results
function memoize(fn) {
  const cache = {};
  return function (...args) {
    const key = JSON.stringify(args);
    if (!(key in cache)) {
      cache[key] = fn(...args);
    }
    return cache[key];
  };
}

const expensiveCalculation = memoize((n) => {
  console.log('Computing...');
  return n * n;
});

expensiveCalculation(5); // "Computing..." → 25
expensiveCalculation(5); // 25 (cached, no log)

4. Parallel Processing

// Safe to run in parallel - no shared state
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map((n) => n * 2); // Each call is independent

5. Easier Debugging

// Can reason about each function in isolation
const data = getData();
const filtered = filterData(data);
const sorted = sortData(filtered);
const formatted = formatData(sorted);
// Each step is isolated and predictable

Common Side Effects

1. Mutating Arguments

// āŒ Mutates argument
function sortArray(arr) {
  return arr.sort(); // sort() mutates!
}

// āœ… Doesn't mutate
function sortArrayPure(arr) {
  return [...arr].sort(); // Copy first
}

2. Using Global State

// āŒ Uses global state
let config = { debug: true };

function log(message) {
  if (config.debug) {
    console.log(message);
  }
}

// āœ… Pass config as argument
function logPure(message, debug) {
  if (debug) {
    console.log(message);
  }
}

3. Date/Time

// āŒ Non-deterministic
function getAge(birthYear) {
  return new Date().getFullYear() - birthYear;
}

// āœ… Pass current year as argument
function getAgePure(birthYear, currentYear) {
  return currentYear - birthYear;
}

4. Random Numbers

// āŒ Non-deterministic
function shuffle(arr) {
  return arr.sort(() => Math.random() - 0.5);
}

// āœ… Accept random function as parameter
function shufflePure(arr, randomFn) {
  return [...arr].sort(() => randomFn() - 0.5);
}

// In tests, you can pass a predictable "random" function

Managing Side Effects

Isolate Side Effects

Push side effects to the edges of your application.

// Pure business logic
function calculateDiscount(price, discountPercent) {
  return price * (1 - discountPercent / 100);
}

function formatPrice(price) {
  return `$${price.toFixed(2)}`;
}

// Side effects at the boundary
async function processOrder(orderId) {
  // Side effect: API call
  const order = await fetchOrder(orderId);

  // Pure functions for business logic
  const discountedPrice = calculateDiscount(order.price, order.discount);
  const formattedPrice = formatPrice(discountedPrice);

  // Side effect: update database
  await updateOrder(orderId, { finalPrice: discountedPrice });

  // Side effect: log
  console.log(`Order ${orderId}: ${formattedPrice}`);
}

Dependency Injection

// āŒ Hard-coded dependencies
function getUser(id) {
  return fetch(`/api/users/${id}`).then((r) => r.json());
}

// āœ… Dependencies injected
function createUserService(httpClient) {
  return {
    getUser(id) {
      return httpClient.get(`/users/${id}`);
    },
  };
}

// Can inject mock for testing
const mockClient = { get: () => Promise.resolve({ id: 1, name: 'Test' }) };
const userService = createUserService(mockClient);

Command Query Separation

// āœ… Queries (pure) - return data, no mutations
function getTotal(items) {
  return items.reduce((sum, item) => sum + item.price, 0);
}

function getActiveUsers(users) {
  return users.filter((user) => user.active);
}

// āœ… Commands (side effects) - perform actions
function saveOrder(order) {
  database.save(order); // Side effect
}

function sendEmail(to, subject, body) {
  emailService.send(to, subject, body); // Side effect
}

Immutability

Immutability is key to pure functions.

Array Immutability

const original = [1, 2, 3];

// āœ… Immutable operations
const added = [...original, 4]; // [1, 2, 3, 4]
const removed = original.filter((x) => x !== 2); // [1, 3]
const updated = original.map((x) => x * 2); // [2, 4, 6]
const sorted = [...original].sort(); // [1, 2, 3]

// āŒ Mutable operations (avoid)
original.push(4); // Mutates!
original.sort(); // Mutates!
original.splice(1, 1); // Mutates!

Object Immutability

const person = { name: 'Alice', age: 25 };

// āœ… Immutable updates
const older = { ...person, age: 26 };
const renamed = { ...person, name: 'Alicia' };

// Nested updates
const withAddress = {
  ...person,
  address: { city: 'NYC', zip: '10001' },
};

// Updating nested property
const newCity = {
  ...withAddress,
  address: { ...withAddress.address, city: 'LA' },
};

Deep Immutability

// Deep update helper
function updateIn(obj, path, value) {
  const [first, ...rest] = path;

  if (rest.length === 0) {
    return { ...obj, [first]: value };
  }

  return {
    ...obj,
    [first]: updateIn(obj[first] || {}, rest, value),
  };
}

const state = {
  user: {
    profile: {
      name: 'Alice',
    },
  },
};

const newState = updateIn(state, ['user', 'profile', 'name'], 'Bob');

Referential Transparency

An expression is referentially transparent if it can be replaced with its value without changing the program's behavior.

// Referentially transparent
const x = add(2, 3); // Can replace with 5
const y = x + x; // Can replace with 5 + 5

// Not referentially transparent
const now = Date.now(); // Can't replace - value changes
const random = Math.random(); // Can't replace - value changes

Benefits

// Can reason about code mathematically
const result = add(multiply(2, 3), multiply(2, 3));

// Can simplify to:
const temp = multiply(2, 3); // 6
const result = add(temp, temp); // 12

// Or directly:
const result = add(6, 6); // 12

Best Practices

1. Prefer Pure Functions

// āœ… Pure by default
function processData(data) {
  return data
    .filter((item) => item.active)
    .map((item) => transform(item))
    .sort((a, b) => a.name.localeCompare(b.name));
}

2. Don't Mutate Arguments

// āŒ Mutates input
function addDefaults(config) {
  config.timeout = config.timeout || 5000;
  return config;
}

// āœ… Returns new object
function addDefaults(config) {
  return {
    timeout: 5000,
    ...config,
  };
}

3. Return New Data Structures

// āœ… Always return new arrays/objects
function addUser(users, newUser) {
  return [...users, newUser];
}

function updateUser(users, id, updates) {
  return users.map((user) => (user.id === id ? { ...user, ...updates } : user));
}

function removeUser(users, id) {
  return users.filter((user) => user.id !== id);
}

4. Isolate Side Effects

// āœ… Side effects clearly separated
class OrderProcessor {
  // Pure methods
  calculateTotal(items) {
    return items.reduce((sum, item) => sum + item.price, 0);
  }

  applyDiscount(total, discount) {
    return total * (1 - discount);
  }

  // Side effects (clearly labeled)
  async saveOrder(order) {
    await database.save(order);
  }

  async sendConfirmation(email, order) {
    await emailService.send(email, order);
  }
}

5. Use Immutable Data Patterns

// āœ… Consistent immutable patterns
const addTodo = (todos, text) => [
  ...todos,
  { id: Date.now(), text, completed: false },
];

const toggleTodo = (todos, id) =>
  todos.map((todo) =>
    todo.id === id ? { ...todo, completed: !todo.completed } : todo
  );

const removeTodo = (todos, id) => todos.filter((todo) => todo.id !== id);

Summary

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│            PURE FUNCTIONS & SIDE EFFECTS                  │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│                                                           │
│  PURE FUNCTIONS:                                          │
│  • Same input → same output                              │
│  • No side effects                                        │
│  • Depend only on arguments                               │
│                                                           │
│  SIDE EFFECTS:                                            │
│  • Mutating external state                               │
│  • I/O operations (console, files, network)              │
│  • Date/time, random numbers                             │
│  • DOM manipulation                                       │
│                                                           │
│  BENEFITS OF PURE:                                        │
│  • Predictable                                            │
│  • Testable                                               │
│  • Cacheable (memoization)                               │
│  • Parallelizable                                         │
│  • Easy to debug                                          │
│                                                           │
│  BEST PRACTICES:                                          │
│  • Prefer pure functions                                  │
│  • Don't mutate arguments                                │
│  • Return new data structures                             │
│  • Isolate side effects                                   │
│  • Use dependency injection                               │
│                                                           │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

Next Steps

  • •Practice with the examples in examples.js
  • •Complete the exercises in exercises.js
  • •Learn about functional programming patterns
  • •Explore immutable data libraries (Immer, Immutable.js)
README - JavaScript Tutorial | DeepML