A side effect is anything evaluation does besides return a result: mutating a variable or object, changing data in place, throwing an exception, printing, reading input, accessing a network or file system, or drawing on a screen. A pure function has no observable effect beyond computing its result from its inputs. section 1.0 section 1.2
The difficulty is not that these actions are useless. Programs must interact with the outside world. The problem is allowing those interactions to be entangled with ordinary computation: results can then depend on hidden state or evaluation history, making a component harder to test, reuse, compose, parallelize, and understand locally. section 1.0 section 6.1
Charging for coffee
A coffee purchase that calls cc.charge(cup.price) returns only Coffee while also contacting a card company and recording a transaction. Passing a Payments dependency makes testing possible with a stub, but retains mutation and awkward test inspection. A functional redesign returns both the Coffee and Charge, leaving payment processing elsewhere. Charge values can then be combined for one card or coalesced across purchases. section 1.1.1 section 1.1.2
Side effects break referential transparency when replacing an expression with its value changes program behavior. Repeating StringBuilder.append mutates the builder, so two apparently identical calls can produce different results. With pure expressions, substitution needs only the function definition and its arguments; no sequence of prior state changes must be simulated. section 1.3 section 1.5
A practical refactoring strategy is to separate responsibilities:
- Extract pure logic that computes a result, such as choosing a contest winner. section 13.1
- Compute a value describing the required interaction, rather than performing it immediately. section 13.1
- Keep input and output procedures at the outer edge, where an interpreter performs printing, files, databases, or other external work. section 13.10 section 13.7.1
An IO value describes an external computation without itself having a side effect. Such values can be stored, passed to functions, and composed; a separate interpreter later performs the interaction. In this sense, a pure program may describe effects while preserving referential transparency. This use of “effect” differs from “side effect,” which specifically signals a violation of referential transparency. section 13.0 section 13.2.2 section 12.3.1
Not every internal mutation makes an enclosing function impure. Quicksort can create a mutable array, mutate it privately, and return a list; because callers cannot observe or retain the array, quicksort remains referentially transparent. The boundary is observability, not merely the presence of mutation. section 14.1
Listing 1.1 A Scala program with side effects
class Cafe: #1
def buyCoffee(cc: CreditCard): Coffee = #2
val cup = Coffee() #3
cc.charge(cup.price) #4
cup #5
class CreditCard: #6
def charge(price: Double): Unit = #7
println("charging " + price) #8
class Coffee:
val price: Double = 2.0
val cc = CreditCard() #9
val cafe = Cafe()
val cup = cafe.buyCoffee(cc)
Inspect the original coffee-purchase implementation to locate the returned Coffee and the separate credit-card interaction on the same execution path. section 1.1.1























