Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Side Effect

Concept

A foundational idea to recognize and understand.

Understand This First

  • Algorithm – the pure algorithmic core is where side effects should be absent.

Context

At the architectural level, functions in software do two kinds of things: they compute a return value, and they change the world around them. A side effect is any change that happens beyond the function’s return value, whether that’s writing to a database, sending an email, modifying a global variable, printing to the screen, or altering a file on disk.

Side effects aren’t inherently bad. Without them, software could never save data, communicate with users, or interact with other systems. But unmanaged side effects are one of the most common sources of bugs, surprises, and difficulty in software. Understanding where side effects live in your system is how you build reliable software and direct AI agents that produce reliable code.

Problem

A function is supposed to calculate a shipping cost. It returns the right number, but it also quietly updates a database record, logs a message that triggers a downstream process, and changes a shared counter. When something goes wrong downstream, the cause is invisible from the function’s signature. How do you build systems where you can understand what a piece of code does without reading every line of its implementation?

Forces

  • Usefulness vs. predictability: Side effects are how software interacts with the world, but they make behavior harder to predict and test.
  • Convenience vs. clarity: It’s easy to add “just one more” side effect to a function, but accumulation makes the system opaque.
  • Performance vs. purity: Avoiding side effects sometimes means copying data or passing extra parameters, which can feel like overhead.
  • Testability vs. realism: Functions without side effects are trivial to test, but testing the side-effectful parts requires mocks, stubs, or real infrastructure.

Solution

Make side effects visible, intentional, and concentrated. The practical approach has three parts:

Separate pure logic from effectful operations. Functions that compute results should not also send emails or write to databases. Keep the calculation in one function and the action in another. This is the same “functional core, imperative shell” approach described in Determinism.

Make side effects explicit in function signatures or naming. If a function writes to a database, its name or documentation should say so. In some languages, type systems enforce this (Haskell’s IO monad, Rust’s ownership model). In others, it’s a matter of convention and discipline.

Control the order and scope of effects. Side effects that happen in an unpredictable order or that affect shared global state are the hardest to reason about. Localizing effects (writing to a specific output rather than mutating a global) makes them manageable.

How It Plays Out

An AI agent generates a function to process a customer order. The function validates the order, calculates the total, charges the payment, sends a confirmation email, and updates inventory, all in one block. It works, but it’s untestable as a unit: you can’t check the pricing logic without also triggering a real payment. A developer who understands side effects asks the agent to separate the pure calculation from the effectful actions, producing a testable core and a thin orchestration layer.

Warning

When reviewing agent-generated code, watch for hidden side effects: logging calls that trigger alerts, database writes buried inside utility functions, or HTTP calls inside what looks like a pure calculation. These are common in generated code because the agent optimizes for “it works” rather than “the effects are visible.”

A team experiences a mysterious bug: a report shows incorrect totals, but the calculation function looks correct. After hours of investigation, they discover that a “helper” function called during the calculation modifies a shared list in place, a side effect invisible from the call site. The fix is to make the helper return a new list instead of modifying the input.

Example Prompt

“Separate the pure order calculation logic from the side effects. The function should return the computed total and a list of actions to perform (charge payment, send email, update inventory) rather than performing them inline.”

Consequences

Managing side effects makes software easier to test, debug, and understand. Pure functions can be tested with simple input-output assertions. Side-effectful code can be tested separately with focused integration tests. When bugs appear, you can narrow the search to the effectful boundaries rather than suspecting every function.

The cost is that strict separation requires more functions and sometimes more explicit plumbing: passing dependencies in rather than reaching out for them. It also requires discipline that AI agents don’t naturally exhibit. You’ll need to review and restructure agent-generated code to maintain clean boundaries.

  • Refines: Determinism — controlling side effects is the primary technique for achieving deterministic behavior.
  • Depends on: Algorithm — the pure algorithmic core is where side effects should be absent.
  • Enables: Event — event-driven designs often separate the recording of “what happened” (an event) from the side effects triggered by it.
  • Contrasts with: API — API calls are intentional, visible side effects; the problem is unintentional or hidden ones.
  • Contrasts with: Algorithmic Complexity — complexity analysis measures computation cost, while side effects concern observable changes beyond the return value.
  • Enables: Concurrency — minimizing shared side effects is the most effective way to reduce concurrency bugs.