Coding · Programming languages
OCaml tutorial for beginners
Short answer
OCaml is a statically typed functional language from the ML family. Its compiler infers types for you, catches many mistakes before the program runs, and produces fast native code. Programs are built from immutable values, variants (tagged unions), pattern matching and modules. Install it with opam, run a file with ocaml hello.ml, or compile it with ocamlopt.
What OCaml is and who uses it
OCaml was created at Inria in France and is now developed by an open-source community that includes companies such as Jane Street and Tarides. It combines functional programming with optional imperative features (mutable references, arrays, loops) and an object system that most code does not need. OCaml 5 added shared-memory parallelism through domains and effect handlers.
OCaml is used for compilers and program analysis tools, proof assistants such as Rocq (formerly Coq), trading systems (Jane Street is the best-known industrial user), and developer tools such as Meta's Flow and Infer. It is also a common teaching language for functional programming and type systems.
Install OCaml and run Hello, world
The standard way to install OCaml is opam, the OCaml package manager. After installing opam, run opam init once; it sets up a compiler. Then install the build tool dune and the friendlier REPL utop with opam install dune utop.
(* hello.ml *)
let () = print_endline "Hello, world!"ocaml hello.ml # run with the interpreter
ocamlopt -o hello hello.ml
./hello # native executable
# Hello, world!let () = ... is the usual way to write the entry point: it evaluates the expression and checks that it returns unit (no value). In the REPL (utop or ocaml), end each phrase with ;;; in source files you do not need ;;. For real projects, dune init project myapp creates a project and dune build compiles it.
Values and basic types
| Type | Example | Notes |
|---|---|---|
| int | 42 | 63-bit on 64-bit systems; operators + - * / mod |
| float | 3.14 | Has its own operators: +. -. *. /. |
| string | "hello" | Immutable bytes; join with ^ |
| char | 'a' | A single byte |
| bool | true, false | &&, ||, not |
| unit | () | The value of expressions with no result |
let name = "Ana"
let age = 30
let height = 1.68 *. 100.0
let () = Printf.printf "%s is %d, %.0f cm\n" name age height
(* Ana is 30, 168 cm *)
let count = ref 0
let () = incr count; Printf.printf "count = %d\n" !countBindings are immutable. A ref is a mutable cell: !r reads it and r := v writes it. OCaml never converts between int and float automatically; use float_of_int and int_of_float.
Control flow and functions
if is an expression. Functions are defined with let; recursive ones need let rec. Arguments are separated by spaces, and every function is curried, so partial application is free.
let sign n =
if n > 0 then "positive"
else if n < 0 then "negative"
else "zero"
let rec factorial n = if n <= 1 then 1 else n * factorial (n - 1)
let add x y = x + y (* int -> int -> int *)
let add5 = add 5
let () =
for i = 1 to 3 do
Printf.printf "%d squared is %d\n" i (i * i)
done;
Printf.printf "%s %d %d\n" (sign (-2)) (factorial 5) (add5 10)
(* negative 120 15 *)Note the parentheses in sign (-2): without them OCaml reads sign - 2 as a subtraction. Use ; to sequence expressions that return unit.
Lists, arrays, tuples and records
| Structure | Literal | Notes |
|---|---|---|
| List | [1; 2; 3] | Immutable singly linked list; x :: xs adds to the front |
| Array | [| 1; 2; 3 |] | Fixed size, mutable, a.(0) indexing |
| Tuple | ("Ana", 30) | Fixed group of values of different types |
| Record | { name = "Ana"; age = 30 } | Named fields, type declared first |
| Hashtbl / Map | Hashtbl.create 16 | Mutable hash table / immutable balanced tree map |
type person = { name : string; age : int }
let people = [ { name = "Ana"; age = 30 }; { name = "Ben"; age = 17 } ]
let () =
people
|> List.filter (fun p -> p.age >= 18)
|> List.map (fun p -> p.name)
|> List.iter print_endline
(* Ana *)
let older = { (List.hd people) with age = 31 }The pipeline operator x |> f means f x. The List module provides map, filter, fold_left, find_opt and many more.
Signature features: variants, pattern matching and modules
A variant type lists every possible case of a value. Pattern matching takes a value apart by case, and the compiler warns when a case is missing, which makes refactoring safe.
type shape =
| Circle of float
| Rectangle of float * float
let area = function
| Circle r -> Float.pi *. r *. r
| Rectangle (w, h) -> w *. h
let () =
List.iter (fun s -> Printf.printf "%.2f\n" (area s))
[ Circle 1.0; Rectangle (2.0, 3.0) ]
(* 3.14 then 6.00 *)
module StringMap = Map.Make (String)
let ages = StringMap.(empty |> add "Ana" 30 |> add "Ben" 17)
let () = Printf.printf "%d\n" (StringMap.find "Ana" ages) (* 30 *)Modules group types and functions, and every file is a module (list.ml would be List). A functor is a function from modules to modules: Map.Make (String) builds a map module whose keys are strings. Signatures (.mli files) hide implementation details.
Error handling
OCaml has three tools: option for a value that may be missing, result for an operation that can fail with information, and exceptions for exceptional situations.
let parse_age s =
match int_of_string_opt s with
| Some n when n >= 0 -> Ok n
| Some _ -> Error "Age must not be negative"
| None -> Error "Not a number"
let () =
match parse_age "abc" with
| Ok n -> Printf.printf "Age %d\n" n
| Error msg -> print_endline ("Error: " ^ msg)
(* Error: Not a number *)
exception Empty_input
let first = function [] -> raise Empty_input | x :: _ -> x
let () =
try print_int (first []) with Empty_input -> print_endline "empty list"A small complete program: word frequencies
let () =
let text = "the cat and the hat and the bat" in
let counts = Hashtbl.create 16 in
String.split_on_char ' ' text
|> List.iter (fun w ->
let n = Option.value (Hashtbl.find_opt counts w) ~default:0 in
Hashtbl.replace counts w (n + 1));
Hashtbl.to_seq counts
|> List.of_seq
|> List.sort (fun (_, a) (_, b) -> compare b a)
|> List.filteri (fun i _ -> i < 2)
|> List.iter (fun (w, n) -> Printf.printf "%s %d\n" w n)
(* the 3
and 2 *)The program counts words in a mutable hash table, converts the table to a list of (word, count) pairs, sorts by count in descending order and prints the first two. ~default:0 is a labeled argument.
Where to go next
- ocaml.org: the installation guide, "A Tour of OCaml" and the other tutorials under Docs.
- The OCaml manual for the full language and standard library reference.
- Learn dune for building projects and opam for installing libraries.
- Try the free online book "Real World OCaml" for larger, practical examples.
FAQ
Is OCaml hard to learn?
The syntax is small, but thinking in types, recursion and pattern matching takes practice if you come from imperative languages. Error messages from the type checker become easier to read quickly.
Why does OCaml have separate operators for floats?
OCaml does not overload arithmetic operators, so + works only on int and +. only on float. This keeps type inference simple and predictable.
What is the difference between OCaml and F#?
F# is derived from OCaml and shares much of its core syntax, but F# runs on .NET and uses significant indentation, while OCaml compiles to native code or bytecode and has a more powerful module system with functors.
When do I need ;; in OCaml?
Only in the interactive toplevel (ocaml or utop), where it tells the REPL to evaluate what you typed. In .ml source files you normally omit it.