Skip to content
Part IA Michaelmas Term

Imperative versus Functional Languages

Two Paradigms

Programming languages can be broadly classified by the style of computation they encourage. The two paradigms most relevant to the Part IA course are the imperative and functional paradigms.

Imperative Programming

In an imperative language, a program is a sequence of statements that mutate the program’s state. Execution order matters because each statement may depend on the state left behind by the previous one.

Key characteristics:

  • Mutable state: variables hold values that change over time. x = x + 1 is a common expression because x really does take on a new value.
  • Explicit control flow: loops (for, while), conditionals (if/else), and subroutine calls drive computation
  • Side effects: functions can modify global state, perform I/O, or alter their arguments — the function’s effect is not limited to its return value
  • Statement-oriented: computation is structured as “do this, then do that”

Examples of imperative languages: C, Java, Python, JavaScript (though all have absorbed some functional features).

Java as an Imperative Language

Java is fundamentally imperative and object-oriented. Consider:

int total = 0;
for (int i = 0; i < numbers.length; i++) {
    total = total + numbers[i];
}

The variable total is explicitly mutated through each iteration. The loop counter i changes with every step. The state of the program at any point is the collection of all variable values at that point.

Functional Programming

In a functional language, a program is a collection of expressions built from pure functions. Computation proceeds by evaluating expressions rather than executing statements.

Key characteristics:

  • Immutability: once a value is bound to a name, it does not change. There is no assignment statement that alters an existing variable.
  • Pure functions: a function’s output depends only on its inputs. Calling the same function with the same arguments always produces the same result. There are no side effects.
  • Recursion over loops: instead of mutating a loop counter, functional languages use recursive function calls to process sequences
  • Expression-oriented: everything evaluates to a value. An if expression returns a value; a function body is a single expression.

Examples of functional languages: OCaml, Haskell, F#, Elm.

OCaml in the Part IA Course

OCaml is taught in Foundations of Computer Science (FoCS). It is a statically typed functional language with powerful type inference and pattern matching. A typical OCaml function:

let rec sum lst =
  match lst with
  | [] -> 0
  | x :: xs -> x + sum xs

There is no mutable variable being updated. The computation is expressed as a recursive decomposition of the list. The function is pure — its result depends only on its input list.

Where Java Stands

Java is fundamentally an imperative, object-oriented language, but it has absorbed functional features since Java 8:

  • Lambda expressions: anonymous functions that can be passed as arguments
  • Streams API: a functional-style pipeline for processing collections without explicit loops
  • Method references: shorthand for lambdas that call a single method
  • Optional: a container type that avoids null and encourages functional-style chaining

The imperative loop above can be rewritten in a functional style:

int total = Arrays.stream(numbers).sum();

No explicit mutation of a running total. No loop counter. The stream pipeline abstracts away the iteration. However, Java lambdas are still ultimately compiled into objects and invoked through interfaces — the functional style is syntactic sugar over an imperative core.

Trade-offs

Imperative

StrengthsWeaknesses
Fine-grained control over state and memoryUnexpected mutation is a major source of bugs
Natural fit for inherently stateful problems (GUIs, games, network protocols)Reasoning about program behaviour requires mentally tracking all state changes
Often maps directly to hardware execution modelConcurrency is hard — shared mutable state must be protected with locks

Functional

StrengthsWeaknesses
Easier to reason about — a function’s behaviour depends only on its inputsAwkward for inherently stateful problems
Referential transparency enables safe substitution and equational reasoningCan be less intuitive for programmers trained in imperative style
Trivially parallelisable — pure functions have no shared state to conflictPerformance can suffer from creating many intermediate immutable structures

Complementarity

The two courses — OOP (Java) and FoCS (OCaml) — are designed to give you fluency in both paradigms. Each illuminates the other: understanding immutability and pure functions from OCaml helps you write better Java code (fewer mutable fields, prefer immutable objects where possible), and understanding object modelling from Java helps you appreciate OCaml’s module system as an alternative approach to abstraction.