Skip to main content

Sign in to CleanKata

Track your progress, earn XP, and unlock every lesson.

By signing in you agree to our Terms of Use and Privacy Policy.

Clean Code60 XP7 min

Pure Functions

A pure function always returns the same output for the same input and changes nothing outside itself.

What Makes a Function Pure

A pure function has two properties: (1) deterministic — same input always produces the same output, (2) no side effects — it doesn't modify anything outside its scope: no globals, no database writes, no I/O, no mutations of its arguments. Pure functions are trivial to test (no setup, no mocks), safe to parallelize, and easy to reason about in isolation.

Impure — depends on external state

discount = 0.10  // global state

function calculateTotal(price, qty):
    // depends on external variable — not deterministic
    return price * qty * (1 - discount)

discount = 0.20  // change this → function behaves differently

Pure — all inputs explicit

function calculateTotal(price, qty, discount):
    // same inputs → always same output
    return price * qty * (1 - discount)

// Testing is trivial:
assert calculateTotal(10.0, 3, 0.10) == 27.0
assert calculateTotal(10.0, 3, 0.20) == 24.0

Side Effects and Mutations

Mutating an argument is a hidden side effect — the caller doesn't expect it. If a function receives a list and modifies it in place, every piece of code sharing that list is silently affected. Return new values instead of mutating. Isolate I/O (DB writes, HTTP calls, logging) at the boundaries of your system — keep the core logic pure.

Mutates argument — surprise!

function applyDiscount(items, discount):
    for each item in items:
        item.price = item.price * (1 - discount)  // mutates in-place!
    return items

cart = [{ name: "book", price: 20 }]
applyDiscount(cart, 0.1)
// cart is now mutated — caller doesn't know

Returns new data — no surprises

function applyDiscount(items, discount):
    result = []
    for each item in items:
        newItem = copy(item)
        newItem.price = item.price * (1 - discount)
        append newItem to result
    return result

cart = [{ name: "book", price: 20 }]
discounted = applyDiscount(cart, 0.1)
// cart unchanged, discounted is a new list

Push Side Effects to the Edge

You can't eliminate all side effects — programs must read input and produce output. The goal is to push them to the boundaries: UI layer, API handlers, repository methods. Keep the business logic layer pure. Pure core + thin impure shell is the architecture that makes codebases testable, readable, and maintainable long-term.

// ✗ Business logic entangled with I/O
function processOrder(orderId):
    order = db.find(orderId)           // I/O
    if order.total > 1000:
        order.discount = 0.05
    else:
        order.discount = 0
    order.final = order.total * (1 - order.discount)
    db.save(order)                      // I/O
    email.send(order.user, "Confirmed") // I/O
    return order.final
// ✓ Pure core — I/O isolated at the edges
function calculateDiscount(total):   // pure
    return 0.05 if total > 1000 else 0.0

function calculateFinal(total, discount):  // pure
    return total * (1 - discount)

function processOrder(orderId):   // impure shell — thin and obvious
    order    = db.find(orderId)                         // I/O
    discount = calculateDiscount(order.total)           // pure
    final    = calculateFinal(order.total, discount)    // pure
    db.saveFinal(orderId, final)                        // I/O
    email.send(order.user, "Confirmed")                 // I/O
    return final

Code Challenge

This function reads a global, mutates its argument, and has a hidden side effect. Make all inputs explicit parameters and return a value without touching anything else.

💡Key takeaway

Pure functions are the safest units of code — predictable, testable, and honest. Push I/O to the edges, keep the core pure.

🔧 Some exercises may still have errors. If something seems wrong, use the Feedback button (bottom-right of the page) to report it — it helps us fix it fast.

Hint: Look for three things: (1) any variable not in the parameter list, (2) any mutation of a parameter or external state, (3) any call with a side effect like writing or logging. Each one needs to become a parameter or a return value.

✗ Your version

Pure Functions — CleanKata — CleanKata