Introduction to Functional Programming
advanced25 minLearning objectives
- Explain the characteristics of functional programming
- Compare it with procedural and object-oriented paradigms
Learn
AQA 4.12.1 — Functional programming: characteristics and paradigm comparison
Retrieval: Sequence 11 named three paradigms this course covers — procedural, object-oriented and functional — and had you classify short code examples by paradigm, including one built entirely from map() and filter() calls, noting it matched "Sequence 10's own recursion-and-immutability preview." This lesson finally opens that box: what functional programming actually is, and how it genuinely differs from the object-oriented work you've just spent a whole sequence building.
Key vocabulary
- Functional programming — a paradigm that builds programs primarily by combining and transforming pure functions, rather than issuing a sequence of instructions that change state.
- Side effect — any change a function makes that is visible outside itself: modifying a variable it didn't create, printing, writing to a file, mutating an argument it was given.
- Immutable — unable to be changed after creation; a new value is produced instead of modifying the existing one.
- Function composition — building a new function by chaining two or more existing functions together, so the output of one feeds directly into the next.
Understand — what functional programming actually changes
Procedural programming (Sequence 11) organises a program as functions that act on data held separately from them. Object-oriented programming (also Sequence 11) bundles that data and the functions that act on it together inside objects, and generally expects an object's internal state to change over its lifetime — hobbit.is_available genuinely flips from True to False when borrow() runs; that state change is the entire point of the method. Functional programming takes close to the opposite position: instead of objects whose state changes over time, values are treated as fixed once created, and computation happens by transforming one value into a brand-new one — never by mutating the original.
See it — the same task, three paradigms
# Procedural - a loop that mutates a list in place
def apply_discount_procedural(prices):
for i in range(len(prices)):
prices[i] = prices[i] * 0.8 # mutates the caller's own list
# Object-oriented - a method that mutates the object's own state
class Basket:
def __init__(self, prices):
self.prices = prices
def apply_discount(self):
for i in range(len(self.prices)):
self.prices[i] = self.prices[i] * 0.8 # mutates self.prices
# Functional - transforms the input into a brand-new list, nothing mutated
def apply_discount_functional(prices):
return [price * 0.8 for price in prices] # a genuine transformation
All three "apply a 20% discount." Only the third never changes anything that existed before it ran — the original prices list is untouched, and a new list is returned instead.
Identify it — classify these paradigms
For each, identify which paradigm (procedural, object-oriented or functional) it most directly illustrates, and justify your answer in one sentence:
def total_score(scores): return sum(scores), used throughout a program that never reassignsscoresitself.- A
Scoreclass whoseadd_bonus(amount)method doesself.value += amount. def with_bonus(score, amount): return score + amount, called asnew_score = with_bonus(old_score, 10), withold_scorenever reassigned.
(1: could be read as either procedural or functional in isolation - the paradigm only becomes clear from how it's used across the wider program; a genuinely functional program would use it without ever mutating scores elsewhere either. 2: object-oriented - state (value) and the method that changes it (add_bonus) are bundled together, and the method mutates that state in place. 3: functional - the original score is never touched; a new value is produced and given a new name. This is the clearest tell: functional code produces new values instead of reassigning existing ones.)
Common mistake
Assuming "uses map() or lambda" is what makes code functional. It isn't — a lambda that mutates a list it was given is not behaving functionally, no matter how functional-looking its syntax is; a plain hand-written loop that only ever returns new values without mutating anything is behaving functionally even though it uses no special syntax at all. What matters is behaviour — does it mutate shared state, or only produce new values? — not which keywords appear.
Check your understanding
A student claims: "This code is object-oriented because it uses a class." Explain what more you'd need to check before agreeing. (A class alone doesn't guarantee genuinely object-oriented behaviour - you'd need to check whether the class actually bundles meaningful state with the methods that use it, and whether those methods mutate that state, the way Sequence 11's Book.borrow() does. A class that only ever holds already-computed, never-mutated data and whose methods just return new values without changing anything is behaving closer to functional style, dressed in class syntax - exactly the same "syntax isn't the paradigm" point as this lesson's Common Mistake, just from the opposite direction.)
Challenge
Take the procedural apply_discount_procedural function above and rewrite the object-oriented Basket.apply_discount method so that it, too, never mutates self.prices — instead it should return a brand-new list, leaving self.prices exactly as it was. Is the result still meaningfully "object-oriented," or has it become something else?
Looking ahead: the next lesson gets precise about exactly what makes a function "functional" in the first place — pure functions and immutable data — and asks you to judge real functions against those exact criteria.