Side effect

On this page 7

A side effect is any observable interaction between a function and the world outside it, other than computing and returning a value. Writing to a file, sending a network request, printing to the console, mutating a shared variable, reading the system clock or throwing an exception are all side effects. In functional programming, the concept defines the boundary between pure functions, whose result depends only on their arguments, and effectful code, whose result or consequences depend on or change external state.

Side effects are not a defect: a program without any of them could never display output or store data. The discipline of Programming in a functional style is to make effects explicit, isolate them and control their order, so that most of the code base remains predictable, testable and safe to reason about.

What counts as a side effect

A function has a side effect when calling it changes something that persists after the call or when its result depends on something other than its arguments. Strictly speaking, the second case is an input dependency rather than an output effect, but both break referential transparency: the call can no longer be replaced by its result without changing program behavior.

The table below groups the common kinds of effects and shows why each matters.

CategoryTypical examplesWhy it matters
State mutationChanging a field, global or shared arrayOther code sees altered data
Input and outputFiles, console, sockets, databasesInteracts with the outside world
Nondeterministic inputClock, random numbers, environment variablesSame call returns different results
ExceptionsThrowing or aborting executionAlters control flow of callers
Resource allocationOpening handles, starting threadsRequires cleanup and ordering
Logging and metricsWriting log lines, countersUsually benign but still observable
Common categories of side effects

Some effects are unobservable from outside and therefore harmless to purity. A function may build a local mutable array, fill it in a loop and return a copy; callers cannot tell the difference. This local mutation is accepted even in strict functional languages, for example through Haskell’s ST monad.

Why side effects complicate reasoning

Side effects make program behavior depend on hidden state and execution order. A pure expression can be evaluated once, many times, lazily or in parallel with the same outcome. An effectful call cannot: running it twice may charge a customer twice, and running two effectful calls in a different order may produce a different result.

This has direct consequences for lazy evaluation, memoization, parallelism and testing. A compiler or runtime may cache, reorder or skip a pure call, but must preserve every effect exactly as written. Tests of pure functions need only inputs and expected outputs, whereas effectful code needs mocks, fixtures or a controlled environment.

PropertyPure functionFunction with side effects
Same input, same outputAlwaysNot guaranteed
Safe to cache resultsYesOnly with care
Safe to reorder or parallelizeYesDepends on shared state
Testing approachInput and expected outputMocks, stubs or test doubles
Safe to retry after failureYesOnly if idempotent
Reasoning scopeThe function body aloneThe function plus external state
Pure versus effectful code

How languages track and control effects

Programming languages handle side effects along a spectrum from unrestricted to fully tracked in the type system. Most mainstream languages allow effects anywhere and rely on conventions, while purely functional languages make every effect visible in a function’s type.

ApproachExample languages or toolsHow effects are controlled
UnrestrictedC, Java, Python, JavaScriptConventions, code review, const and final
Immutability by defaultClojure, Scala, RustMutation requires explicit opt-in
Monadic effectsHaskell IO, Scala Cats Effect, ZIOEffects are values run by a runtime
Effect systemsKoka, OCaml 5 handlers, UnisonTypes list which effects may occur
Ownership and borrowingRustCompiler restricts shared mutation
Effect handling approaches

In Haskell, a function of type Int -> Int cannot perform input or output; one that does must return IO Int. The IO value is a description of an effect, and only the runtime executes it, starting from main. This is how a monad sequences effects while keeping the rest of the program pure. Libraries such as Cats Effect and ZIO bring the same model to Scala, and similar patterns exist in TypeScript through libraries like Effect.

Managing side effects in practice

The most effective practical technique is the functional core, imperative shell structure: business logic is written as pure functions that take data and return decisions, and a thin outer layer performs the actual reading, writing and network calls. The core becomes trivially testable, and the shell stays small enough to verify with a few integration tests.

  • Push effects to the edges: read inputs first, compute purely, then write outputs.
  • Pass dependencies explicitly, such as a clock or random generator, instead of calling them inside logic.
  • Return new values instead of mutating arguments, using immutability for shared data.
  • Make effects idempotent where possible, so a retried request does not duplicate work.
  • Name effectful functions clearly, for example saveOrder rather than order, so readers expect the effect.

Front-end frameworks show the same pattern. In React, rendering is expected to be pure, and effects such as data fetching belong in useEffect or event handlers. Redux reducers must be pure, while asynchronous work lives in middleware.

Common mistakes and misconceptions

A frequent misconception is that functional programming forbids side effects. It does not; it separates and sequences them. Another is that returning a value guarantees purity, even though the same function may also mutate its argument or read a global.

SymptomLikely causeRemedy
Test passes alone, fails in suiteShared mutable state between testsReset state or remove globals
Different result on each runHidden clock or random dependencyInject the clock or seed
Duplicate records after retryNon-idempotent writeUse idempotency keys or upserts
Caller’s list changes unexpectedlyFunction mutated its argumentCopy or use immutable data
Race condition under loadConcurrent mutation of shared dataIsolate state or synchronize access
Typical problems caused by hidden side effects

Stripped to the essentials

A side effect is anything a piece of code does besides handing back an answer. A calculator that only shows the sum has no side effect. A calculator that also prints a receipt, updates a running total and emails the result has three.

Side effects are necessary, because they are how software saves data or talks to people. The trouble is that they are easy to forget, so good programmers keep them in a few clearly marked places and keep the rest of the code as simple calculations.

Where it is applied

An online shop calculates shipping costs. In the original code, the function calculateShipping(order) read the current date from the system clock, fetched exchange rates over the network and wrote a log entry. Tests were slow and flaky, failing whenever rates changed or a holiday surcharge rule depended on the date.

The team rewrote it so the outer handler first loads the order, today’s date and the rates, then calls a pure function shippingCost(order, date, rates), and finally saves and logs the result. The pure function now has over a hundred fast unit tests covering every rule, the effectful shell has a handful of integration tests, and bugs in pricing became reproducible from a single set of inputs.