~ / track H / applied patterns

Refactoring imperative to functional

Intermediate

Most working programmers come to functional programming from an imperative background. The translation rules are surprisingly mechanical: loops become reductions, mutable accumulators become return values, state machines become data threaded through pure functions. Once you know the patterns, you can refactor incrementally — module by module — without rewriting the whole codebase.

Move 1: a for-loop with an accumulator → reduce

A Python-shaped imperative loop:

total = 0
for x in xs:
    if x > 0:
        total += x * 2

Three things happen at once: filter, transform, sum. In Clojure, each is a named operation; the whole thing is one expression:

loading sci
press ⌘/Ctrl-↵ or click ▶ run to evaluate

Or, with threading and transducers, even tighter:

loading sci
press ⌘/Ctrl-↵ or click ▶ run to evaluate

The shape moved from "step through, mutate, return mutated thing" to "describe the transformation, apply it once."

Move 2: a mutable counter → an explicit accumulator

Imperative:

seen = {}
for x in xs:
    seen[x] = seen.get(x, 0) + 1

Functional: pass the dict-being-built through a reduce:

loading sci
press ⌘/Ctrl-↵ or click ▶ run to evaluate

The dictionary is no longer mutable; each step produces a new map sharing structure with the old one. The shape of the code is the same; the meaning of = shifts from assignment to binding.

Move 3: nested loops → flatmap (mapcat)

Imperative:

pairs = []
for a in xs:
    for b in ys:
        if a != b:
            pairs.append([a, b])

Functional:

loading sci
press ⌘/Ctrl-↵ or click ▶ run to evaluate

for (which is not a loop; it's a list comprehension) replaces the nested imperative scan with declarative syntax. Each "loop variable" binds in turn, :when filters, the body produces each output.

Move 4: state machines → step functions

Imperative state machines mutate a state variable. The functional version treats each transition as a pure function from old state and event to new state:

state = {"phase": "idle", "n": 0}
for ev in events:
    if ev == "start": state["phase"] = "running"
    elif ev == "tick": state["n"] += 1
    elif ev == "stop": state["phase"] = "done"

Functional:

loading sci
press ⌘/Ctrl-↵ or click ▶ run to evaluate

The state is data; the transitions are pure; the loop is reduce. You can test step with example inputs in isolation, which is hard with the mutating version.

Move 5: I/O at the edges, pure logic in the middle

The biggest refactor isn't a code shape — it's a topology shift. Push the pure transformations to the center of your function and the I/O (reads, writes, prints, network calls) to the edges. The function then becomes testable in two layers: trivial tests of the pure core, integration tests of the thin shell. The next topic (Functional core, imperative shell) goes deep on this.

What to expect

  • Less code. A loop-and-mutate sequence often collapses 5 lines into 1.
  • Easier tests. Pure transformations need no setup/teardown.
  • Different debugging. You can no longer pause and inspect a mutable variable; instead you inspect the value passed between steps with tap> or prn.
  • Some friction with libraries. Java APIs are imperative; you'll write small wrappers that present a functional surface to your own code.

Check yourself

? quiz

An imperative loop builds up a vector by appending in each iteration. What's the canonical functional refactor?

Exercise

Refactor this imperative pseudocode into one functional expression:

result = []
for x in xs:
    if x is even:
        result.append(x * x)
return sum(result)
loading sci
press ⌘/Ctrl-↵ or click ▶ run to evaluate
 status: new