Skip to content
Lists and Recursion
step 1/7

Reading — step 1 of 7

Learn

~3 min readLists, Recursion, IO

OCaml lists are linked lists, immutable, homogeneous (all elements same type). Combined with pattern matching and recursion, they're the central data structure for functional OCaml.

Creating lists

let empty = []
let xs = [1; 2; 3]                  (* semicolons, NOT commas *)
let ys = 0 :: xs                    (* 0 prepended; xs unchanged *)

The :: operator is cons — adds an element to the front. Cheap (O(1)). Lists are built tail-first.

1 :: 2 :: 3 :: []                   (* same as [1; 2; 3] *)

A list is either empty [] or head :: tail.

Pattern matching on lists

let rec length xs =
  match xs with
  | [] -> 0
  | _ :: rest -> 1 + length rest

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

The _ is a wildcard — "any value, don't bind it." Always handle both [] (empty) and x :: rest (non-empty) — exhaustiveness check warns if missing.

Tail recursion

Naive sum above is NOT tail-recursive — the addition happens AFTER the recursive call. For huge lists, switch to the accumulator pattern:

let sum xs =
  let rec loop acc = function
    | [] -> acc
    | x :: rest -> loop (acc + x) rest
  in
  loop 0 xs

The recursive call is the LAST operation. OCaml optimizes this to a loop with no stack growth.

function is shorthand for fun x -> match x with — saves typing.

Common list operations

List.length [1; 2; 3]              (* 3 *)
List.rev [1; 2; 3]                  (* [3; 2; 1] *)
List.map (fun x -> x * 2) [1; 2; 3] (* [2; 4; 6] *)
List.filter (fun x -> x > 2) [1; 2; 3; 4]  (* [3; 4] *)
List.fold_left (+) 0 [1; 2; 3]      (* 6 *)
List.fold_right (+) [1; 2; 3] 0     (* 6 *)
List.iter print_int [1; 2; 3]       (* prints 123 *)
List.mem 2 [1; 2; 3]                 (* true *)
List.nth [10; 20; 30] 1              (* 20 — 0-indexed *)
List.append [1; 2] [3; 4]            (* [1; 2; 3; 4]; or use @ *)
List.concat [[1]; [2]; [3]]          (* [1; 2; 3] *)
List.find (fun x -> x > 2) [1; 2; 3] (* 3 *)
List.exists (fun x -> x > 5) [1; 2]  (* false *)
List.for_all (fun x -> x > 0) [1; 2] (* true *)
List.sort compare [3; 1; 2]          (* [1; 2; 3] *)

The @ operator concatenates lists. It's O(n) in the LEFT operand — xs @ [x] traverses xs every time. Build with :: and reverse if needed.

fold_left vs fold_right

List.fold_left (fun acc x -> acc + x) 0 [1; 2; 3]
(* ((0 + 1) + 2) + 3 = 6 *)

List.fold_right (fun x acc -> x + acc) [1; 2; 3] 0
(* 1 + (2 + (3 + 0)) = 6 *)

fold_left is tail-recursive (constant stack); fold_right isn't (uses stack proportional to list length). For numeric folds on large lists, fold_left.

Argument order differs between them — fold_left takes (acc, elem); fold_right takes (elem, acc). Easy to swap by accident.

Common mistakes

  • Using commas instead of semicolons[1, 2, 3] is a TUPLE wrapped in a list (length 1!), not three elements. Use semicolons.
  • Non-tail-recursive list traversals — stack overflow on long lists. Use accumulator pattern.
  • xs @ [x] in a loop — O(n²). Build with :: and reverse at the end.
  • Forgetting rec for recursive functions — without it, OCaml looks for an existing function; you get a name resolution error.
  • Mixing list types — lists are HOMOGENEOUS. [1; "a"] is a type error.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…