Skip to main content
Version: 1.0

Functional Programming

Functional programming (FP) is a programming paradigm that emphasizes pure functions, immutability, and composition. FP produces code that is easier to reason about, test, and parallelize by minimizing hidden state and side effects.

Core principles

  • Pure functions

    • Deterministic: same inputs → same outputs.
    • No observable side effects (no I/O, mutable global state, time, random values).
  • Immutability

    • Data structures are not mutated in place; new values are produced instead.
  • No shared mutable state

    • Avoid global/mutable state to reduce coupling and concurrency bugs.
  • Higher-order functions

    • Functions accept other functions or return them (map, filter, reduce, decorators).
  • Referential transparency

    • Expressions can be replaced by their evaluated value without changing program behavior.

Side effects — definition & handling

A side effect is any observable interaction besides returning a value (I/O, logging, DB, mutating arguments, reading time/random, raising uncaught exceptions). Best practice: keep a small, isolated impure edge (input/output) and a pure core that performs computation.

Practical examples (Python)

Pure function:

def add(a: int, b: int) -> int:
return a + b

Impure function (avoid for core logic):

GLOBAL = 0
def add_and_store(a: int, b: int) -> int:
global GLOBAL
GLOBAL = a + b # side effect
print(GLOBAL) # I/O side effect
return GLOBAL

Immutability with dataclasses:

from dataclasses import dataclass

@dataclass(frozen=True)
class User:
user_id: int
email: str

Higher-order function / composition:

from functools import reduce

def compose(*funcs):
def composed(x):
return reduce(lambda v, f: f(v), reversed(funcs), x)
return composed

Use map/filter instead of loops:

emails = list(map(lambda u: u.email.lower(), users))
active = list(filter(lambda u: u.is_active, users))

Patterns & libraries

  • Use small pure transformation functions; isolate I/O at boundary layers.
  • Use immutable containers: tuples, frozenset, frozen dataclasses.
  • Use libraries: toolz, funcy, returns (Maybe/Result types) for composition and safer error handling.
  • Prefer built-in functions and list/dict comprehensions for clarity and speed.

Benefits

  • Easier unit testing (pure functions require no fixtures/mocks).
  • Better concurrency/safety (no shared mutable state).
  • Clear reasoning and smaller cognitive load for each unit of code.

Trade-offs & caveats

  • Some FP patterns can be less familiar to teams.
  • Excessive immutability copies can cost performance; measure before optimizing.
  • Interop with imperative systems (DB, frameworks) requires clear boundaries.

Testing & teaching tips

  • Write small tests for pure functions; test composition rather than plumbing.
  • Use property-based tests (Hypothesis) to validate invariants.
  • Demonstrate moving side effects outside the pure core (IO at edges).

Quick checklist for converting code to FP-style

  • Identify pure core computations and extract them.
  • Replace in-place mutations with pure transforms returning new values.
  • Wrap side effects in a thin adapter layer.
  • Add unit tests for pure functions before integration tests.