Tutorial: Learn OCaml from Scratch (2026)
OCaml is a functional programming language with a strong type system and practical tooling. I have used it for compilers, static analysis tools, and financial systems. OCaml's type inference means you rarely write type annotations, yet the compiler catches most mismatches at compile time.
The module system is OCaml's superpower — it scales from small programs to million-line codebases with clean separation and abstraction.
Pattern Matching
Pattern matching in OCaml is exhaustive and concise. The match expression destructures values and binds variables in each arm. The compiler warns about non-exhaustive patterns. Patterns can match on constructors, tuples, records, lists, and literals. Guards with when add conditional logic.
type 'a tree =
| Leaf of 'a
| Node of 'a tree * 'a tree
let rec sum = function
| Leaf v -> v
| Node (l, r) -> sum l + sum r
let describe = function
| [] -> "empty"
| [x] -> "singleton: " ^ string_of_int x
| h :: _ when h > 0 -> "starts positive"
| _ -> "other"
Algebraic Data Types
Algebraic data types (variants) let you model data as combinations of products (tuples/records) and sums (variants). Options handle optional values: Some or None. Result types represent success or failure. The type system ensures you handle all cases — null pointer errors do not exist in OCaml.
type color = Red | Green | Blue | RGB of int * int * int
type optional_int = None | Some of int
let of_string s =
try Some (int_of_string s)
with Failure _ -> None
type 'a result = Ok of 'a | Error of string
let divide x y =
if y = 0 then Error "division by zero"
else Ok (x / y)
Modules and Functors
Modules are structures containing types and values. Signatures (module types) provide abstraction. Functors are functions from modules to modules — they parameterize code over implementations. The module system enables generic data structures (Map.Make, Set.Make) and dependency injection at compile time.
module type ORDERED = sig
type t
val compare : t -> t -> int
end
module Set = functor (Elt : ORDERED) -> struct
type t = Elt.t list
let empty = []
let rec add x = function
| [] -> [x]
| h :: t as s ->
match Elt.compare x h with
| 0 -> s
| n when n < 0 -> x :: s
| _ -> h :: add x t
end
module IntSet = Set(struct
type t = int
let compare = Int.compare
end)
Type Inference
OCaml uses Hindley-Milner type inference: types are inferred globally without annotations. The principal type is always found. Polymorphic functions work on any compatible type. The compiler rejects ambiguous code and reports type errors with location information. You can add type annotations for documentation or to narrow types.
let id x = x
(* val id : 'a -> 'a *)
let compose f g x = f (g x)
(* val compose : ('a -> 'b) -> ('c -> 'a) -> 'c -> 'b *)
let rec map f = function
| [] -> []
| h :: t -> f h :: map f t
(* val map : ('a -> 'b) -> 'a list -> 'b list *)
let add x y = x + y
(* val add : int -> int -> int *)
Imperative Features
OCaml supports imperative programming with references (mutable cells), arrays, and records with mutable fields. The ref type creates updatable pointers. While loops and for loops exist for iteration. OCaml is a multi-paradigm language — use immutable functional style by default and imperative when performance demands it.
let counter () =
let c = ref 0 in
fun () ->
c := !c + 1;
!c
let next = counter ()
let () = Printf.printf "%d\n" (next ()) (* 1 *)
let swap arr i j =
let tmp = arr.(i) in
arr.(i) <- arr.(j);
arr.(j) <- tmp
let sum_array arr =
let s = ref 0 in
for i = 0 to Array.length arr - 1 do
s := !s + arr.(i)
done;
!s
Build System (Dune)
Dune is the standard OCaml build system. It handles dependencies, library building, and testing. Configuration goes in dune-project and dune files. Use dune build to compile, dune test to run tests, and dune exec to run executables. Dune integrates with opam (the OCaml package manager).
;; dune-project
(lang dune 3.0)
(name myapp)
;; src/dune
(library
(name mylib)
(public_name myapp.mylib))
(executable
(name main)
(libraries mylib stdio))
;; test/dune
(test
(name test_mylib)
(libraries mylib))
Frequently Asked Questions
OCaml vs Haskell?
OCaml is strict (eager), has an object system, a richer module system, and better imperative support. Haskell is lazy with pure purity by default.
What is the pipe operator?
|> is reverse application: x |> f means f x. It chains transformations left to right. Not built-in but trivially defined as let (|>) x f = f x.
How does OCaml handle null?
It does not. Use option types ('a option = Some | None) and pattern matching. Null pointer errors are impossible.
What is a functor?
A module parameterized by another module. Like a generic at the module level. Used for containers (Map, Set) and abstract interfaces.
Originally published on Ayodhyyya. Last updated June 1, 2026.