Skip to content

Functional Programming

Functional programming (FP) is a programming paradigm that treats computation as the evaluation of mathematical functions and avoids changing state and mutable data. Rather than telling the computer how to do something step by step, FP focuses on what to compute by composing functions.

FP concepts have become mainstream — even traditionally object-oriented languages like Java, C#, and Python have adopted functional features such as lambdas, map/filter/reduce, and pattern matching. Understanding FP makes you a better programmer regardless of which language you use.


Imperative vs Functional Paradigm

The imperative paradigm describes computation as a sequence of statements that change program state. The functional paradigm describes computation as the evaluation of expressions that produce values without side effects.

Imperative (HOW): Functional (WHAT):
───────────────── ──────────────────
"Walk to the store. "I need groceries
Turn left on Main St. from the nearest
Enter the store. store."
Pick up milk.
Go to checkout.
Pay. Walk home."
Step-by-step instructions Declare desired outcome
Mutable state throughout No mutable state

Side-by-Side Comparison

# IMPERATIVE: Sum of squares of even numbers
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# Mutable state, explicit loops, step-by-step
result = 0
for n in numbers:
if n % 2 == 0:
result += n ** 2
print(result) # 220
# FUNCTIONAL: Same computation, declarative style
from functools import reduce
result = reduce(
lambda acc, n: acc + n,
map(lambda n: n ** 2,
filter(lambda n: n % 2 == 0, numbers))
)
print(result) # 220
# FUNCTIONAL (Pythonic): Using comprehension
result = sum(n ** 2 for n in numbers if n % 2 == 0)
print(result) # 220

Core Principles of Functional Programming

1. First-Class and Higher-Order Functions

Functions are values. They can be assigned to variables, passed as arguments, and returned from other functions. A function that takes or returns another function is called a higher-order function.

First-class functions:
add = (a, b) => a + b -- function assigned to a variable
apply(add, 3, 4) -- function passed as argument
createAdder(5) -- function returns a function

2. Pure Functions

A pure function always produces the same output for the same input and has no side effects. It does not modify external state, perform I/O, or depend on anything outside its parameters.

Pure: add(2, 3) → 5 (always, no side effects)
Impure: getTime() → ??? (depends on external clock)
Impure: print("hi") (side effect: I/O)

3. Immutability

Data is never modified after creation. Instead of changing existing data, new data structures are created with the desired changes. This eliminates an entire class of bugs related to shared mutable state.

Mutable (imperative):
list = [1, 2, 3]
list.append(4) -- modifies the original list
list is now [1, 2, 3, 4]
Immutable (functional):
list = [1, 2, 3]
newList = list + [4] -- creates a new list
list is still [1, 2, 3]
newList is [1, 2, 3, 4]

4. Declarative over Imperative

FP code describes what should happen, not how. This leads to code that is more concise, easier to reason about, and less prone to off-by-one errors.

5. Function Composition

Complex operations are built by composing simple functions, similar to mathematical function composition where (f . g)(x) = f(g(x)).

from functools import reduce
def compose(*functions):
"""Compose multiple functions: compose(f, g, h)(x) = f(g(h(x)))"""
def composed(x):
return reduce(lambda acc, fn: fn(acc),
reversed(functions), x)
return composed
def double(x): return x * 2
def increment(x): return x + 1
def square(x): return x ** 2
# Compose: square(increment(double(x)))
transform = compose(square, increment, double)
print(transform(3)) # square(increment(double(3)))
# = square(increment(6))
# = square(7) = 49

Benefits of Immutability

Immutability is one of the most impactful FP concepts, even outside purely functional languages.

BenefitExplanation
No race conditionsIf data cannot change, concurrent access is always safe
Easier debuggingValues do not change out from under you; easier to trace bugs
Undo/redo for freeKeep previous versions of data structures
Predictable codeFunctions cannot surprise you by modifying their inputs
Cache-friendlyImmutable data can be freely cached and shared

Immutable Data in Practice

# Tuples are immutable (lists are not)
point = (3, 4)
# point[0] = 5 # TypeError!
# Named tuples for immutable records
from typing import NamedTuple
class User(NamedTuple):
name: str
age: int
email: str
alice = User("Alice", 30, "alice@example.com")
# alice.age = 31 # AttributeError!
# Create a modified copy
older_alice = alice._replace(age=31)
print(alice.age) # 30 (unchanged)
print(older_alice.age) # 31
# dataclasses with frozen=True
from dataclasses import dataclass, replace
@dataclass(frozen=True)
class Point:
x: float
y: float
p1 = Point(1.0, 2.0)
# p1.x = 3.0 # FrozenInstanceError!
p2 = replace(p1, x=3.0) # New instance

FP vs OOP: Not a War

FP and OOP are not mutually exclusive. Most modern languages support both paradigms, and the best code often combines them.

AspectOOPFP
Primary abstractionObjects (data + behavior)Functions (transformations)
State managementEncapsulated mutable stateImmutable values
PolymorphismSubtype polymorphism (inheritance)Parametric polymorphism (generics)
Code reuseInheritance, compositionFunction composition
Side effectsAllowed, encapsulatedMinimized, isolated
ConcurrencyLocks, synchronizationNaturally safe (immutability)

Functional Languages Spectrum

Pure FP ◄──────────────────────────────────────────► Pure OOP
Haskell Erlang Scala Kotlin Python Java C++
│ Elixir Clojure F# JavaScript C#
│ │ │ │ │ │
│ │ │ │ │ │
Purely Strongly Multi- Multi- Multi- OOP with
functional FP paradigm paradigm paradigm FP features
(FP+OOP) (FP+OOP) (pragmatic) (added later)

Topics in This Section

Pure Functions

Pure functions, referential transparency, side effects, and immutability patterns.

Explore Pure Functions

Pattern Matching

Pattern matching, algebraic data types, exhaustive matching, and destructuring.

Explore Pattern Matching