programming4 min read

Tutorial: Learn OCaml from Scratch (2026)

Tutorial: Learn OCaml from Scratch (2026)

Published:  |  Category: Programming  |  Reading time: ~15 min
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.