codex-lv3-may-2025

Pure Functions vs Functions with Side Effects

What is a Pure Function?

A pure function is a function that:

  1. Always returns the same output for the same input
  2. Has no side effects - it doesn’t modify anything outside of itself
  3. Doesn’t depend on external state - it only uses its parameters

Example of a Pure Function

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

// Always returns the same result for the same inputs
add(2, 3); // Always returns 5
add(2, 3); // Always returns 5 (same input = same output)

What are Side Effects?

Side effects are changes that a function makes outside of its own scope, such as:

Example of a Function with Side Effects

let total = 0;

// Function with side effects
function addToTotal(amount) {
  total += amount; // Modifies external variable
  console.log(`Total is now: ${total}`); // Side effect: console output
  return total;
}

addToTotal(5); // Returns 5, but also changes `total` and logs to console
addToTotal(5); // Returns 10 (different result even with same input!)

Comparison

Pure Function Function with Side Effects
✅ Same input → Same output ❌ Same input → May produce different output
✅ No external dependencies ❌ May depend on external state
✅ Easy to test ❌ Harder to test (need to mock/setup state)
✅ Easy to reason about ❌ Can be unpredictable
✅ Can be reused safely ❌ May cause unexpected behavior

Why Use Pure Functions?

  1. Easier to test - No need to set up external state
  2. More predictable - Same input always gives same output
  3. Easier to debug - No hidden dependencies
  4. Reusable - Can be used anywhere without side effects
  5. Better for refactoring - Safe to move or modify

Mutability vs Immutability

What is Mutability?

Mutable means “can be changed.” A mutable value or object can be modified after it’s created.

Immutable means “cannot be changed.” An immutable value or object cannot be modified after it’s created. Instead, you create a new value.

Mutable Example (Modifies Original)

// Mutable: modifies the original array
const numbers = [1, 2, 3];

function addNumber(arr, num) {
  arr.push(num); // Modifies the original array!
  return arr;
}

addNumber(numbers, 4);
console.log(numbers); // [1, 2, 3, 4] - Original array was changed!

Immutable Example (Creates New Value)

// Immutable: creates a new array
const numbers = [1, 2, 3];

function addNumber(arr, num) {
  return [...arr, num]; // Creates a new array, doesn't modify original
}

const newNumbers = addNumber(numbers, 4);
console.log(numbers); // [1, 2, 3] - Original unchanged
console.log(newNumbers); // [1, 2, 3, 4] - New array created

Why Immutability Matters for Pure Functions

Pure functions should not mutate their inputs. Instead, they should return new values:

// ❌ BAD: Mutates input (not pure)
function addItemToCart(cart, item) {
  cart.items.push(item); // Modifies the input!
  return cart;
}

// ✅ GOOD: Returns new value (pure)
function addItemToCart(cart, item) {
  return {
    ...cart,
    items: [...cart.items, item] // Creates new object and array
  };
}

Common Immutable Patterns in JavaScript

Arrays

// Instead of: arr.push(item)
const newArr = [...arr, item];

// Instead of: arr.pop()
const newArr = arr.slice(0, -1);

// Instead of: arr.sort()
const newArr = [...arr].sort(); // Copy first, then sort

// Instead of: arr.reverse()
const newArr = [...arr].reverse(); // Copy first, then reverse

Objects

// Instead of: obj.property = value
const newObj = { ...obj, property: value };

// Instead of: obj.nested.property = value
const newObj = {
  ...obj,
  nested: {
    ...obj.nested,
    property: value
  }
};

Mutable vs Immutable Comparison

Mutable Immutable
❌ Modifies original value ✅ Creates new value
❌ Can cause unexpected changes ✅ Original always safe
❌ Harder to track changes ✅ Easy to see what changed
❌ Can break pure functions ✅ Works perfectly with pure functions
⚡ Faster (no copying) 🐌 Slightly slower (creates new values)

When to Use Each

Example: Converting Mutable to Immutable

// Before: Mutable function
function updateUser(user, newName) {
  user.name = newName; // Mutates input
  return user;
}

// After: Immutable function (pure)
function updateUser(user, newName) {
  return {
    ...user,
    name: newName // Creates new object
  };
}

// Usage:
const user = { name: 'Alice', age: 30 };
const updatedUser = updateUser(user, 'Bob');
console.log(user); // { name: 'Alice', age: 30 } - unchanged
console.log(updatedUser); // { name: 'Bob', age: 30 } - new object

Converting Functions with Side Effects to Pure Functions

Before (with side effects):

let userCount = 0;

function incrementUserCount() {
  userCount++; // Side effect: modifies external variable
  return userCount;
}

After (pure function):

function incrementUserCount(currentCount) {
  return currentCount + 1; // Pure: takes input, returns output
}

// Usage:
let userCount = 0;
userCount = incrementUserCount(userCount); // Explicitly pass and assign

When Side Effects are Necessary

Sometimes side effects are necessary and appropriate:

The key is to separate pure logic from side effects:

// Pure function (logic)
function calculateTotal(items) {
  return items.reduce((sum, item) => sum + item.price, 0);
}

// Function with side effects (UI update)
function updateDisplay(items) {
  const total = calculateTotal(items); // Use pure function
  document.getElementById('total').textContent = `$${total}`; // Side effect
}

Summary

Resources