Part 1: Pure Functions and Immutability - Predictable Code

Introduction

My first production bug came from a "simple" utility function that cached results in a global variable. In single-threaded tests, it worked perfectly. In production with concurrent requests, it returned wrong data to users. Customer A got customer B's account balance. The function wasn't pure - it had hidden state and side effects.

I refactored it to a pure function. No global state. Same input always produced same output. No side effects. The bug disappeared. Tests became trivial. That's when I understood the power of pure functions and immutability.

This article teaches you to write predictable, testable code through pure functions and immutable data structures, using real TypeScript backend examples.

What is a Pure Function?

A pure function has two properties:

  1. Deterministic - Same input always returns same output

  2. No Side Effects - Doesn't modify external state or interact with the outside world

Impure Function

// Impure - depends on external state
let discount = 0.1;

function calculatePrice(basePrice: number): number {
    return basePrice * (1 - discount);  // Depends on external 'discount'
}

console.log(calculatePrice(100));  // 90

discount = 0.2;  // Someone changed discount!
console.log(calculatePrice(100));  // 80 - same input, different output!

Problems:

  • Output depends on external state

  • Hard to test (must control discount)

  • Hard to reason about (must track all changes to discount)

  • Cannot parallelize safely

Pure Function

Benefits:

  • Deterministic - same input, same output

  • Easy to test - just verify input/output

  • Easy to reason about - no hidden dependencies

  • Can parallelize safely

Side Effects

Side effects are interactions with the outside world:

Common side effects:

  • Reading/writing files or databases

  • Network requests

  • Console logging

  • Modifying input arguments

  • Modifying global variables

  • Random number generation

  • Current time/date

Managing Side Effects

Pure functions can't eliminate side effects (we need them!), but we can isolate them:

Immutability

Immutable data cannot be changed after creation. Instead of modifying, we create new values.

The Problem with Mutations

Problems:

  • Hard to track changes

  • Race conditions in async code

  • Difficult to implement undo/redo

  • Cannot safely share data

  • Breaks time-travel debugging

Immutable Approach

Benefits:

  • Predictable - old data never changes

  • Safe to share across async operations

  • Easy to implement undo/redo

  • Enables time-travel debugging

  • No race conditions on data

Real Example: User Profile Updates

I built a user profile service that needed to track changes:

Real Example: Shopping Cart

Immutable Array Operations

Immutable Object Updates

Real Example: API Response Transformation

Benefits of Pure Functions

1. Easy Testing

2. Easy Debugging

3. Memoization

4. Parallelization

Best Practices

1. Make Dependencies Explicit

2. Avoid Mutating Arguments

3. Use TypeScript readonly

4. Separate Pure and Impure

What's Next

You now understand:

  • Pure functions and their benefits

  • Side effects and how to manage them

  • Immutability and why it matters

  • Immutable data transformations

  • Testing and debugging pure functions

In Part 2: Higher-Order Functions and Composition, you'll learn to use functions as first-class citizens, master map/filter/reduce, compose small functions into powerful pipelines, and apply functional patterns to real data processing.


Based on building production TypeScript backend services, learning to eliminate bugs through pure functions and immutability.

Last updated