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.
| Category | Typical examples | Why it matters |
|---|---|---|
| State mutation | Changing a field, global or shared array | Other code sees altered data |
| Input and output | Files, console, sockets, databases | Interacts with the outside world |
| Nondeterministic input | Clock, random numbers, environment variables | Same call returns different results |
| Exceptions | Throwing or aborting execution | Alters control flow of callers |
| Resource allocation | Opening handles, starting threads | Requires cleanup and ordering |
| Logging and metrics | Writing log lines, counters | Usually benign but still observable |
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.
| Property | Pure function | Function with side effects |
|---|---|---|
| Same input, same output | Always | Not guaranteed |
| Safe to cache results | Yes | Only with care |
| Safe to reorder or parallelize | Yes | Depends on shared state |
| Testing approach | Input and expected output | Mocks, stubs or test doubles |
| Safe to retry after failure | Yes | Only if idempotent |
| Reasoning scope | The function body alone | The function plus external state |
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.
| Approach | Example languages or tools | How effects are controlled |
|---|---|---|
| Unrestricted | C, Java, Python, JavaScript | Conventions, code review, const and final |
| Immutability by default | Clojure, Scala, Rust | Mutation requires explicit opt-in |
| Monadic effects | Haskell IO, Scala Cats Effect, ZIO | Effects are values run by a runtime |
| Effect systems | Koka, OCaml 5 handlers, Unison | Types list which effects may occur |
| Ownership and borrowing | Rust | Compiler restricts shared mutation |
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
saveOrderrather thanorder, 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.
| Symptom | Likely cause | Remedy |
|---|---|---|
| Test passes alone, fails in suite | Shared mutable state between tests | Reset state or remove globals |
| Different result on each run | Hidden clock or random dependency | Inject the clock or seed |
| Duplicate records after retry | Non-idempotent write | Use idempotency keys or upserts |
| Caller’s list changes unexpectedly | Function mutated its argument | Copy or use immutable data |
| Race condition under load | Concurrent mutation of shared data | Isolate state or synchronize access |
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.