0

Pure function — same input always gives same output, zero side effects

Beginner5 min read·eng-12-024
interviewcompare

Concept

Pure function — a function that:

  1. Same input → same output: Given the same arguments, always returns the same value. No randomness, no reading external state.
  2. No side effects: Does not modify anything outside itself. No DB writes, no logging, no global state changes.

Why pure functions are valuable:

  • Trivially testable: Just call with inputs, assert outputs. No mocking, no setup, no teardown.
  • Memoizable: Safe to cache the result keyed by inputs — calling again with same inputs gives same result.
  • Composable: Chain pure functions together. The output of one is the input of the next. Like Unix pipes.
  • Refactorable: Move, rename, extract — won't break other code because there are no hidden dependencies.
  • Parallelizable: No shared state → no race conditions.

Contrast with impure functions: Functions that read from the database, get the current time (time()), generate random numbers, read from a config file, or write logs are impure. Their output depends on external state.

PHP and purity: PHP doesn't enforce purity (unlike Haskell). But you can write pure functions by discipline — don't read $_GET, don't write to a file, don't use static/global variables.

Functional programming in PHP: array_map(), array_filter(), array_reduce() — these are higher-order functions designed to work with pure functions. usort($arr, fn($a, $b) => $a['age'] <=> $b['age']) — the comparison function is pure.

"Functional core, imperative shell": Architecture pattern. The core of your domain logic is pure. The shell (controllers, commands, event handlers) is where side effects happen. The core is the most testable part.

Code Example

php
<?php
// PURE FUNCTIONS — same input, same output, no side effects

// ✅ Pure — only depends on its arguments
function add(int $a, int $b): int { return $a + $b; }

// ✅ Pure — deterministic, no external state
function celsiusToFahrenheit(float $celsius): float { return $celsius * 9/5 + 32; }

// ✅ Pure — no side effects, same output for same input
function applyDiscount(float $price, float $discountPercent): float
{
    return $price * (1 - $discountPercent / 100);
}

// ✅ Pure — creates new array, doesn't mutate input
function filterAdults(array $users): array
{
    return array_values(array_filter($users, fn($u) => $u['age'] >= 18));
}

// ❌ IMPURE — reads external state (current time)
function isExpired(int $expiresAt): bool { return $expiresAt < time(); } // time() changes!
// Fix: pass the time as a parameter
function isExpiredAt(int $expiresAt, int $now): bool { return $expiresAt < $now; } // pure!

// ❌ IMPURE — reads database
function getUserName(int $id): string
{
    return \DB::table('users')->where('id', $id)->value('name'); // DB call!
}
// Fix: pass the name as a parameter

// ❌ IMPURE — modifies external state
function createOrderAndEmail(array $data): array
{
    $order = Order::create($data); // DB write!
    Mail::to($data['email'])->send(new OrderConfirmation($order)); // email!
    return $order->toArray();
}

// FUNCTIONAL CORE example
class PricingEngine // pure functions only
{
    public function applyDiscounts(float $price, array $discounts): float
    {
        return array_reduce($discounts, fn($p, $d) => $p * (1 - $d / 100), $price);
    }

    public function calculateTax(float $subtotal, float $rate): float
    {
        return round($subtotal * $rate, 2);
    }

    public function buildOrderSummary(array $items, float $taxRate, array $discounts): array
    {
        $subtotal    = array_sum(array_column($items, 'price'));
        $discounted  = $this->applyDiscounts($subtotal, $discounts);
        $tax         = $this->calculateTax($discounted, $taxRate);
        return ['subtotal' => $subtotal, 'discounted' => $discounted, 'tax' => $tax, 'total' => $discounted + $tax];
    }
}

// Test purely — no mocking at all
$engine  = new PricingEngine();
$summary = $engine->buildOrderSummary([['price' => 100], ['price' => 50]], 0.10, [10]); // 10% discount
assert($summary['discounted'] === 135.0);
assert($summary['total'] === 148.5);