Control Flow in OCaml
Learn control flow in OCaml - if/else expressions, pattern matching, recursion, and imperative loops with Docker-ready examples
Control flow determines the order in which your program’s logic executes. OCaml is a multi-paradigm language, so it gives you two distinct styles: the functional approach built on expressions, pattern matching, and recursion, and a pragmatic imperative layer with for and while loops for when mutation is the clearest tool.
The first thing to internalize is that OCaml’s if/then/else is an expression, not a statement. It evaluates to a value, so you can bind its result to a name or use it directly as a function argument. This is fundamentally different from C-style languages where if is a control statement that does something but produces nothing.
The real star of OCaml control flow is pattern matching with the match expression. It replaces the switch/case of imperative languages but goes much further — it can destructure data, bind variables, and the compiler will warn you when you forget a case. Combined with recursion (which replaces most explicit loops in idiomatic code), pattern matching is how experienced OCaml programmers express branching logic.
In this tutorial you’ll learn how to write conditionals as expressions, match on values and data structures, use recursion to iterate, and reach for imperative loops when they fit. Because OCaml is statically typed and strongly inferred, the compiler checks that every branch of a conditional or match returns a compatible type — a powerful safety net you’ll see throughout.
Conditionals Are Expressions
In OCaml, if/else returns a value. Both branches must have the same type, and an if without an else implicitly returns unit (so its single branch must also be unit).
Create a file named conditionals.ml:
| |
Because if is an expression, OCaml has no need for a separate ternary operator — if cond then a else b already plays that role.
Pattern Matching: The match Expression
match is OCaml’s most powerful control-flow construct. It tests a value against a series of patterns, runs the first that matches, and can bind variables, deconstruct data, and apply guards with when.
Create a file named matching.ml:
| |
The x :: _ pattern splits a list into its first element (x) and the rest (ignored with _). This destructuring is what makes recursion over lists so natural in OCaml.
Recursion Replaces Loops
In idiomatic functional OCaml, recursion is the primary way to iterate. A function marked rec can call itself, and tail-recursive functions (where the recursive call is the last thing done) run in constant stack space — just like a loop.
Create a file named recursion.ml:
| |
The function keyword is shorthand for a fun that immediately pattern-matches its argument — a very common idiom for recursive list processing.
Imperative Loops When You Need Them
OCaml is pragmatic: when mutation is the clearest approach, it offers for and while loops. These work alongside mutable references (ref) and arrays. OCaml has no break statement, so early exit from a loop is done by raising an exception.
Create a file named loops.ml:
| |
Note the !n syntax: ! dereferences a ref (reads its current value), while := assigns a new one. The local exception Found lets us jump out of the for loop the moment we find a match.
Running with Docker
Run each example with the official OCaml image. The ocaml command interprets the file directly.
| |
Expected Output
conditionals.ml:
Bring a jacket
The weather is cool
Category: mild
Below 20 degrees
matching.ml:
two
negative
positive even
go
starts with 42
recursion.ml:
3... 2... 1... Liftoff!
factorial 5 = 120
sum 1..100 = 5050
sum of list = 60
loops.ml:
Counting up: 1 2 3 4 5
Counting down: 5 4 3 2 1
Halving: 16 8 4 2 1
First value over 20: 23
Key Concepts
if/elseis an expression — it evaluates to a value, so both branches must share the same type. Anifwith noelsemust returnunit. This removes the need for a separate ternary operator.matchis the workhorse of control flow — it compares a value against patterns, binds variables, and destructures data structures like lists and variants in one construct.- Guards (
when) attach extra boolean conditions to a pattern, letting you branch on computed properties such asn mod 2 = 0. - Exhaustiveness checking — the compiler warns when a
matchover a variant type misses a case, catching whole classes of bugs before the program runs. - Recursion replaces loops in idiomatic OCaml; tail-recursive functions with an accumulator run in constant stack space, just like an imperative loop.
- The
functionkeyword is shorthand for a one-argumentmatch, ideal for recursive list processing. - Imperative
forandwhileloops are available for side-effecting code, working withrefcells (!reads,:=assigns) and mutable arrays. - No
breakstatement — raise an exception (often a locallet exception ... in) to exit a loop early.
Comments
Loading comments...
Leave a Comment