thecodex.expert  ·  The Codex Family of Knowledge
Programming Paradigms

Functional Programming

Programs as mathematical functions — no hidden state, no surprises, and every output determined entirely by the inputs.

Paradigms Requires: Functions, Type Systems ~22 min read Last verified:
Canonical Definition

Functional programming is a programming paradigm that treats computation as the evaluation of mathematical functions, emphasising pure functions that produce no side effects, immutable data that cannot be modified after creation, and function composition as the primary mechanism for building programs, rooted in Alonzo Church's lambda calculus (1936).

One sentence that captures it

Functional programming treats your program as a series of transformations: data flows in, gets transformed by functions, and flows out — with no hidden changes to anything along the way.

What makes a function "pure"

A pure function has two properties. First: given the same inputs, it always returns the same output — no matter when you call it, no matter how many times, no matter what state the rest of the program is in. Second: it produces no side effects — it does not modify any variable outside itself, does not write to a file, does not print anything, does not change any shared state.

Contrast with an impure function:

Python pure_vs_impure.py
# IMPURE: result depends on external state
total = 0
def add_to_total(x):
    global total
    total += x      # side effect: modifies external state
    return total    # result depends on total, not just x

# PURE: same input always gives same output, no side effects
def add(x, y):
    return x + y    # always returns x + y, changes nothing

# add(3, 4) is always 7 — no matter what
# add_to_total(3) depends on what total currently is

Pure functions are predictable, testable, and parallelisable. You can test add(3, 4) in complete isolation — no setup required, no cleanup, no mocking. You can run it on eight CPU cores simultaneously — no shared state to conflict.

Immutability: data that doesn't change

In functional programming, once a value is created, it cannot be modified. Instead of changing a list, you create a new list with the change applied. Instead of updating a variable, you pass the new value to the next function.

This sounds wasteful, but efficient functional languages use persistent data structures — structures that share unchanged parts between the old and new version, so creating "a copy with one change" costs far less than copying the whole structure.

Python immutable_style.py
# Mutable style (imperative)
prices = [100, 200, 300]
for i in range(len(prices)):
    prices[i] = prices[i] * 1.18   # modifies the list in place

# Immutable style (functional) — original list unchanged
prices = [100, 200, 300]
prices_with_gst = [p * 1.18 for p in prices]  # new list, original intact
# prices is still [100, 200, 300]

Higher-order functions: functions that work with functions

In functional programming, functions are first-class values — you can pass them to other functions, return them from functions, and store them in variables. A higher-order function is one that takes another function as an argument or returns one.

Three higher-order functions appear in nearly every language:

  • map — apply a function to every element of a list, producing a new list
  • filter — keep only elements where a function returns true
  • reduce — combine all elements into one value using a function
Python map_filter_reduce.py
from functools import reduce

prices = [100, 200, 300, 50, 75]

# map: apply GST to every price
with_gst = list(map(lambda p: p * 1.18, prices))
# [118.0, 236.0, 354.0, 59.0, 88.5]

# filter: keep only prices over 100
expensive = list(filter(lambda p: p > 100, prices))
# [200, 300]

# reduce: sum all prices
total = reduce(lambda acc, p: acc + p, prices, 0)
# 725

Building programs from small pieces

Functional programs are built by composing small, pure functions into larger ones. The output of one function becomes the input of the next. Because each function is pure, you can reason about each piece independently and be confident the combination will work correctly.

This is the functional equivalent of the OOP principle of composition over inheritance — but at the function level rather than the object level.

Why functional ideas matter in every language

You don't need to use Haskell to benefit from functional programming ideas. Python, JavaScript, Java, Rust, Kotlin, and Go all support functional-style programming. Preferring pure functions, avoiding mutation where possible, and using map/filter instead of loops makes code in any language more testable and easier to reason about. Modern JavaScript and Python are written in a heavily functional style by many practitioners.

Functional languages

Pure functional: Haskell — all functions are pure; side effects (I/O, mutable state) are handled via a type-system mechanism (monads) that makes them explicit. Primarily functional: Erlang, Clojure, F#, Elm, Elixir. Multi-paradigm with strong functional support: Scala, Kotlin, Swift, Rust, JavaScript, Python.

Referential transparency

Referential transparency is the formal property that every pure function enjoys: an expression is referentially transparent if it can be replaced by its value without changing the program's behaviour. If add(3, 4) is always 7, you can replace every occurrence of add(3, 4) with 7 and the program behaves identically. This property enables reasoning about programs equationally — as you would in algebra. It also enables the compiler to cache results (memoisation), reorder calls, and parallelise evaluation.

Closures and lexical scope

A closure is a function that captures variables from its enclosing scope. Higher-order functions depend on closures to be practical:

Python closures.py
def make_multiplier(n):
    def multiply(x):    # closes over n from the outer function
        return x * n
    return multiply     # returns a function

double = make_multiplier(2)
triple = make_multiplier(3)

print(double(5))  # 10
print(triple(5))  # 15
# double and triple are closures — they capture the value of n

Persistent data structures

Purely functional languages require data structures that support efficient "update without mutation." A persistent data structure preserves previous versions when modified. Clojure's persistent vectors use a 32-branch tree: "updating" an element at index i creates a new root node and a new path of log₃₂(n) nodes to the changed leaf, sharing all other nodes with the original. For a vector of 1 million elements, an update creates only 4 new nodes — O(log n) time and space, not O(n).

Currying and partial application

Currying (named after Haskell Curry) transforms a function of multiple arguments into a chain of single-argument functions: f(x, y) becomes f(x)(y). Every Haskell function is automatically curried. Partial application applies a function to some of its arguments, producing a new function waiting for the rest. Together, these make higher-order functions and composition more expressive.

JavaScript currying.js
// Curried add function
const add = x => y => x + y;

const add5 = add(5);   // partial application — add5 waits for y
console.log(add5(3));  // 8
console.log(add5(10)); // 15

// Composing functions
const compose = (f, g) => x => f(g(x));
const double = x => x * 2;
const addTen = x => x + 10;
const doubleThenAdd = compose(addTen, double);
console.log(doubleThenAdd(5)); // 20  (5*2 = 10, 10+10 = 20)

Functional patterns in mainstream languages

Java 8+ introduced lambdas, streams (map/filter/reduce pipelines), and Optional (a functional way to handle nullable values). Kotlin has first-class function support, extension functions, and a rich collections API with functional operations. JavaScript's array methods (.map(), .filter(), .reduce()) and arrow functions enable functional style directly. React's design is explicitly functional: UI components are functions from state to view; state is immutable; updates produce new state.

Commonly confused
Functional programming is not about using map and filter. Those are functional-style tools, but the paradigm is about the properties of the functions themselves — purity and referential transparency. A program that uses map() with an impure function (one that writes to a database on each call) is not functional programming.
Functional programming does not mean no side effects ever. Every useful program eventually reads input, writes output, or communicates over a network — all side effects. Functional programming manages side effects by making them explicit and controlled, pushing them to the boundaries of the program. Haskell uses the IO monad to encode that a function performs I/O in its type; the function is not impure — its impurity is declared and visible.
Immutability is not the same as constants. A const in JavaScript prevents the variable from being reassigned — but the object it points to can still be mutated. True immutability means the data structure itself cannot be modified. JavaScript's const arr = [1,2,3]; arr.push(4) is not immutable. Object.freeze() creates shallow immutability; libraries like Immutable.js or Immer provide deep immutability.
How this connects
Requires first
Enables

Lambda calculus: the formal foundation

Alonzo Church introduced the lambda calculus in 1936 as a formal system for expressing computation through function abstraction and application. Lambda calculus has three constructs: variables (x), abstraction (λx.M — a function with parameter x and body M), and application (M N — apply function M to argument N).

Reduction rules: β-reduction applies a function: (λx.M)N → M[x := N] (substitute N for x in M). α-conversion renames bound variables to avoid capture. These two rules, applied repeatedly, compute the result of any lambda calculus expression. Church proved that lambda calculus is Turing-complete — any computable function can be expressed in lambda calculus, establishing that functions are sufficient for any computation (without needing mutation or sequencing).

Monads: making side effects explicit

A monad is a design pattern (formally: a type constructor M with two operations — return :: a → M a and bind :: M a → (a → M b) → M b — satisfying three monad laws: left identity, right identity, and associativity). Monads provide a structured way to sequence computations that have effects (IO, state, failure, non-determinism) while preserving referential transparency at the type level.

In Haskell, IO a is a type that describes a computation producing a value of type a as a side effect. A function of type IO String is not an impure function — it is a pure description of an I/O action. The main :: IO () function hands this description to the Haskell runtime, which executes it. Purity is preserved because the description is just a value; it is the runtime that performs the actual I/O.

Specification reference

Church, A. (1936). "An unsolvable problem of elementary number theory." American Journal of Mathematics, 58(2), 345–363. — Original lambda calculus paper. Hughes, J. (1989). "Why functional programming matters." The Computer Journal, 32(2), 98–107. — The canonical argument for the practical value of functional programming; motivates higher-order functions and lazy evaluation. Wadler, P. (1992). "The essence of functional programming." POPL '92. ACM. — Introduces monads to a programming audience.

How this connects
Formal basis
Enables
Source confidence: High Last verified: Primary source: Church (1936) · Hughes, The Computer Journal (1989)

Sources

1
Church, A. (1936). "An unsolvable problem of elementary number theory." American Journal of Mathematics, 58(2), 345–363. — Original lambda calculus paper; formal foundation of functional computation.
2
Hughes, J. (1989). "Why functional programming matters." The Computer Journal, 32(2), 98–107. — The canonical practical argument for functional programming; motivates higher-order functions, lazy evaluation, and modularity through function composition.
3
Wadler, P. (1992). "The essence of functional programming." Proceedings of the 19th ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages (POPL '92), 1–14. — Introduces monads as a general structure for functional programs with effects.
4
Marlow, S. (ed.) (2010). Haskell 2010 Language Report. haskell.org/onlinereport/haskell2010/. — The authoritative Haskell language specification; reference for pure functional programming in practice.
5
Bird, R. & Wadler, P. (1988). Introduction to Functional Programming. Prentice Hall. — Primary textbook on functional programming concepts; covers referential transparency, higher-order functions, and lazy evaluation.