Introduction to Functional Programming

advanced25 min

Learning 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:

  1. def total_score(scores): return sum(scores), used throughout a program that never reassigns scores itself.
  2. A Score class whose add_bonus(amount) method does self.value += amount.
  3. def with_bonus(score, amount): return score + amount, called as new_score = with_bonus(old_score, 10), with old_score never 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.

Test yourself

Check your understanding with exam-style questions.

Go to Exam Practice
Log in to track this lesson on your progress dashboard.
Log in