Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

OCaml  Module 5 – Lists, higher-order functions and laziness

Map, filter and fold

Replace hand-written OCaml list recursion with map, filter, filter_map and fold, see how fold_left and fold_right differ, and build pipelines with |>.

  • Beginner
  • 10 minutes
  • Examples run with ocaml 5.5.1, opam 2.6.0, dune 3.24.2 and Python 3.14.8
  • By MySmartCoPilot
Free lesson, Pro answers: 10 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Use List.map, filter, filter_map, concat_map and partition instead of explicit recursion
  • Explain how fold_left and fold_right differ in order and in stack use
  • Derive map and filter from a fold, and apply laws such as map fusion
  • Build small data pipelines over lists with the |> operator

Before you start

What you’ll learn · Before you start

What you will learn

  • Use List.map, filter, filter_map, concat_map and partition instead of explicit recursion
  • Explain how fold_left and fold_right differ in order and in stack use
  • Derive map and filter from a fold, and apply laws such as map fusion
  • Build small data pipelines over lists with the |> operator

Before you start

On this page

List.fold_left ( - ) 10 [1; 2; 3] gives 4, and List.fold_right ( - ) [1; 2; 3] 10 gives -8: the same list, the same function, the same start value, two answers. Most loops over a list are a map, a filter or a fold, and knowing which fold you hold decides both the answer and whether a list of a million elements fits on the stack.

You will replace hand-written recursion with the List module’s higher-order functions, explain how the two folds differ, write map and filter as folds and merge two passes into one, and build pipelines over lists with |>.

Map, filter and their relatives replace the usual recursions

Most recursive functions over a list do one of a few jobs: change every element, keep some of them, or both. The List module has a higher-order function: A function that takes another function as an argument or returns one, such as List.map, which applies the function it is given to every element of a list. for each, a function that takes another function, the step, as its argument and does the walking itself. The session runs each one on four students’ marks:

map, filter, partition, filter_map and concat_map on a list of marks Python · marks_session.py
from session import toplevel

# Higher-order functions of the List module replace the usual recursions: map transforms every element, filter keeps
# some, filter_map does both with a function that may fail, partition splits a list in two and concat_map joins the
# lists a function returns.
toplevel("""
let marks = [("asha", 72); ("ravi", 35); ("meera", 91); ("kiran", 58)];;
List.map snd marks;;
List.filter (fun (_, m) -> m >= 40) marks;;
List.partition (fun (_, m) -> m >= 60) marks;;
let raw = ["72"; "absent"; "91"; ""; "58"];;
List.filter_map int_of_string_opt raw;;
List.map int_of_string_opt raw;;
List.concat_map (fun (name, m) -> if m >= 90 then [name; "distinction"] else [name]) marks;;
""")

Output

$ ocaml
OCaml version 5.5.1
Enter #help;; for help.

# let marks = [("asha", 72); ("ravi", 35); ("meera", 91); ("kiran", 58)];;
val marks : (string * int) list =
  [("asha", 72); ("ravi", 35); ("meera", 91); ("kiran", 58)]
# List.map snd marks;;
- : int list = [72; 35; 91; 58]
# List.filter (fun (_, m) -> m >= 40) marks;;
- : (string * int) list = [("asha", 72); ("meera", 91); ("kiran", 58)]
# List.partition (fun (_, m) -> m >= 60) marks;;
- : (string * int) list * (string * int) list =
([("asha", 72); ("meera", 91)], [("ravi", 35); ("kiran", 58)])
# let raw = ["72"; "absent"; "91"; ""; "58"];;
val raw : string list = ["72"; "absent"; "91"; ""; "58"]
# List.filter_map int_of_string_opt raw;;
- : int list = [72; 91; 58]
# List.map int_of_string_opt raw;;
- : int option list = [Some 72; None; Some 91; None; Some 58]
# List.concat_map (fun (name, m) -> if m >= 90 then [name; "distinction"] else [name]) marks;;
- : string list = ["asha"; "ravi"; "meera"; "distinction"; "kiran"]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 marks_session.py

What each function did with the session’s marks:

Function What it does On the session’s data
List.map f l applies f to every element List.map snd marks gave [72; 35; 91; 58]
List.filter p l keeps the elements for which p is true 3 of 4 students had 40 or more
List.partition p l splits into kept and dropped, in one pass m >= 60: 2 pairs and 2 pairs
List.filter_map f l keeps x for every f that returned Some x [72; 91; 58] from 5 strings
List.concat_map f l joins the lists f returns 5 strings from 4 students

List.filter_map int_of_string_opt read the marks typed as text and skipped "absent" and the empty string in one pass; a plain List.map gave [Some 72; None; Some 91; None; Some 58], which still needs a filter and an unwrap. Each of these functions keeps the order of the elements, and none of them changes the list it is given.

One pass for a step that may fail

When a step can fail, as parsing can, use List.filter_map with a function that returns an option, instead of a List.map followed by a filter for Some and an unwrap. The result has no None left to forget.

The two folds combine from opposite ends

A fold: Combining all the elements of a list into one value with a function and a start value; List.fold_left starts from the first element and List.fold_right from the last. combines all the elements of a list into one value, with a function that takes the result so far and one element. The manual defines List.fold_left f init [b1; …; bn] as f (… (f (f init b1) b2) …) bn, which starts from the first element, and List.fold_right f [a1; …; an] init as f a1 (f a2 (… (f an init) …)), which starts from the last. Step through both on [1; 2; 3] with subtraction:

Interactive · step by step fold_left and fold_right on the same list Step through both folds of [1; 2; 3] with subtraction. fold_left is on the left, fold_right on the right.
fold_left and fold_right on the same list, step 5 of 5. Showing fold_left starts (one frame in all), step 1 (10 - 1 = 9), step 2 (9 - 2 = 7), step 3 (7 - 3 = 4), fold_right starts (one frame per element), step 1 (3 - 10 = -7), step 2 (2 - (-7) = 9), step 3 (1 - 9 = -8). In focus: fold_left starts, fold_right starts.fold_left startsone frame in allstep 110 - 1 = 9step 29 - 2 = 7step 37 - 3 = 4fold_right startsone frame per elementstep 13 - 10 = -7step 22 - (-7) = 9step 31 - 9 = -8

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. Two folds, one list. Both folds get the list [1; 2; 3], the start value 10 and subtraction. fold_left begins with the first element; fold_right must first walk to the last one.
  2. The first subtraction. fold_left computes 10 - 1 = 9 and moves on. fold_right starts at the end: 3 - 10 = -7, with the element on the left and the result so far on the right.
  3. The second subtraction. fold_left: 9 - 2 = 7. fold_right: 2 - (-7) = 9. Each fold feeds its own running result into the next step.
  4. Two different answers. fold_left ends with 7 - 3 = 4 and fold_right with 1 - 9 = -8, the values the session printed. Subtraction depends on the order, so the direction of the fold decides the result.
  5. Why the stack differs. fold_left carried its result forward, so each step was a tail call in one frame. fold_right kept a frame waiting for every element until it reached the end, one per element of the list.

The two folds with -, +, :: and ^ Python · fold_session.py
from session import toplevel

# fold_left combines from the first element, fold_right from the last. With an operator that is not associative, such
# as subtraction, the two give different results; with :: one reverses the list and the other copies it.
toplevel("""
List.fold_left ( - ) 10 [1; 2; 3];;
List.fold_right ( - ) [1; 2; 3] 10;;
List.fold_left ( + ) 0 [1; 2; 3];;
List.fold_left (fun acc x -> x :: acc) [] [1; 2; 3];;
List.fold_right (fun x acc -> x :: acc) [1; 2; 3] [];;
(List.fold_left ( ^ ) "" ["a"; "b"; "c"], List.fold_right ( ^ ) ["a"; "b"; "c"] "");;
List.fold_left (fun (lo, hi) x -> (min lo x, max hi x)) (max_int, min_int) [72; 35; 91; 58];;
""")

Output

$ ocaml
OCaml version 5.5.1
Enter #help;; for help.

# List.fold_left ( - ) 10 [1; 2; 3];;
- : int = 4
# List.fold_right ( - ) [1; 2; 3] 10;;
- : int = -8
# List.fold_left ( + ) 0 [1; 2; 3];;
- : int = 6
# List.fold_left (fun acc x -> x :: acc) [] [1; 2; 3];;
- : int list = [3; 2; 1]
# List.fold_right (fun x acc -> x :: acc) [1; 2; 3] [];;
- : int list = [1; 2; 3]
# (List.fold_left ( ^ ) "" ["a"; "b"; "c"], List.fold_right ( ^ ) ["a"; "b"; "c"] "");;
- : string * string = ("abc", "abc")
# List.fold_left (fun (lo, hi) x -> (min lo x, max hi x)) (max_int, min_int) [72; 35; 91; 58];;
- : int * int = (35, 91)

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 fold_session.py

With ( - ) the answers were 4 and -8, because subtraction depends on the order. With ( + ) the order cannot matter, and with ( ^ ) both folds gave "abc", because joining strings is associative. Folding :: shows the directions most plainly: fold_left built [3; 2; 1], a reversed list, while fold_right rebuilt [1; 2; 3]. The last line carried a pair, the lowest and highest mark so far, and gave (35, 91) in one pass.

The two also differ in stack use. fold_left hands its result forward in a tail call, so it runs in one frame; fold_right cannot combine anything until it has reached the end, and keeps a frame waiting for every element. The manual says so in the documentation of fold_right: “Not tail-recursive”. The program below folds a million numbers both ways under a stack limited to 100,000 words:

fold_left and fold_right over 1,000,000 elements under OCAMLRUNPARAM=l=100000 Python · stack_session.py
from session import run, write

# fold_right must reach the end of the list before it can combine anything, so it keeps one frame per element;
# fold_left combines as it goes, in constant stack. Under a small stack limit only fold_right overflows.
write("folds.ml", """let () =
  let l = List.init 1_000_000 (fun i -> i mod 10) in
  let total =
    match Sys.argv.(1) with
    | "left" -> List.fold_left ( + ) 0 l
    | _ -> List.fold_right ( + ) l 0
  in
  Printf.printf "fold_%s over %d elements: %d\\n" Sys.argv.(1) (List.length l) total
""")
run("""
ocamlopt folds.ml -o folds
export OCAMLRUNPARAM=l=100000
./folds left
!./folds right
""")

Output

$ ocamlopt folds.ml -o folds
$ export OCAMLRUNPARAM=l=100000
$ ./folds left
fold_left over 1000000 elements: 4500000
$ ./folds right
Fatal error: exception Stack_overflow
[exit status 2]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 stack_session.py

fold_left printed 4500000, and fold_right stopped with “Fatal error: exception Stack_overflow” and exit status 2.

Key idea

fold_left combines from the first element in constant stack; fold_right combines from the last and needs a frame per element. Pick the one whose order your function needs, and fold_left for long lists.

Checkpoint

Check: map, filter and their relatives · fold_left and fold_right

  1. Question 1 of 2 raw is ["72"; "absent"; "91"; ""; "58"]. Which call gives [72; 91; 58]?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: List.filter_map int_of_string_opt raw

    filter_map does the map and the filter in one pass, with no None left over.

  2. Question 2 of 2 What do List.fold_left (fun acc x -> x :: acc) [] [1; 2; 3] and List.fold_right (fun x acc -> x :: acc) [1; 2; 3] [] return?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: [3; 2; 1] and [1; 2; 3]

    The session printed [3; 2; 1] and [1; 2; 3]. A fold of :: from the left is List.rev.

Map and filter are folds, and two laws merge passes

A fold can rebuild a list, so map and filter are folds whose step adds an element in front of the result so far. fold_right is the natural choice, because it meets the elements from the last one and so builds the new list in the right order. The session writes both in one line each, then checks two laws on the list [3; 8; 1; 6; 4]:

map and filter written as folds, two laws, and what fusion saves Python · fusion_session.py
from session import run, toplevel, write

# map and filter are folds that rebuild a list. Two laws hold for any pure functions: two maps equal one map of the
# composed function, and two filters equal one filter of both tests. The fused form builds one list instead of two.
toplevel("""
let map f l = List.fold_right (fun x acc -> f x :: acc) l [];;
let filter p l = List.fold_right (fun x acc -> if p x then x :: acc else acc) l [];;
let l = [3; 8; 1; 6; 4];;
(map (fun x -> x * 2) l, filter (fun x -> x > 3) l);;
(map (fun x -> x * 2) l = List.map (fun x -> x * 2) l, filter (fun x -> x > 3) l = List.filter (fun x -> x > 3) l);;
let f x = x + 1 and g x = x * 10;;
List.map f (List.map g l) = List.map (fun x -> f (g x)) l;;
let p x = x mod 2 = 0 and q x = x > 3;;
List.filter p (List.filter q l) = List.filter (fun x -> q x && p x) l;;
List.filter (fun y -> y > 20) (List.map g l) = List.filter_map (fun x -> let y = g x in if y > 20 then Some y else None) l;;
""")
write("fused.ml", """let words f =
  let before = Gc.minor_words () in
  let r = f () in
  (r, Gc.minor_words () -. before)

let () =
  let l = List.init 1000 Fun.id in
  let f x = x + 1 and g x = x * 10 in
  let a, w2 = words (fun () -> List.map f (List.map g l)) in
  let b, w1 = words (fun () -> List.map (fun x -> f (g x)) l) in
  Printf.printf "same result: %b\\n" (a = b);
  Printf.printf "two maps: %.0f words, one fused map: %.0f words\\n" w2 w1
""")
run("""
ocamlopt fused.ml -o fused
./fused
""")

Output

$ ocaml
OCaml version 5.5.1
Enter #help;; for help.

# let map f l = List.fold_right (fun x acc -> f x :: acc) l [];;
val map : ('a -> 'b) -> 'a list -> 'b list = <fun>
# let filter p l = List.fold_right (fun x acc -> if p x then x :: acc else acc) l [];;
val filter : ('a -> bool) -> 'a list -> 'a list = <fun>
# let l = [3; 8; 1; 6; 4];;
val l : int list = [3; 8; 1; 6; 4]
# (map (fun x -> x * 2) l, filter (fun x -> x > 3) l);;
- : int list * int list = ([6; 16; 2; 12; 8], [8; 6; 4])
# (map (fun x -> x * 2) l = List.map (fun x -> x * 2) l, filter (fun x -> x > 3) l = List.filter (fun x -> x > 3) l);;
- : bool * bool = (true, true)
# let f x = x + 1 and g x = x * 10;;
val f : int -> int = <fun>
val g : int -> int = <fun>
# List.map f (List.map g l) = List.map (fun x -> f (g x)) l;;
- : bool = true
# let p x = x mod 2 = 0 and q x = x > 3;;
val p : int -> bool = <fun>
val q : int -> bool = <fun>
# List.filter p (List.filter q l) = List.filter (fun x -> q x && p x) l;;
- : bool = true
# List.filter (fun y -> y > 20) (List.map g l) = List.filter_map (fun x -> let y = g x in if y > 20 then Some y else None) l;;
- : bool = true
$ ocamlopt fused.ml -o fused
$ ./fused
same result: true
two maps: 6000 words, one fused map: 3000 words

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 fusion_session.py

The home-made map and filter gave [6; 16; 2; 12; 8] and [8; 6; 4], the same lists as List.map and List.filter. The first law, map fusion, says that mapping g and then f equals one map of the function that applies both: List.map f (List.map g l) = List.map (fun x -> f (g x)) l printed true. The second law joins two filters into one test, List.filter p (List.filter q l) = List.filter (fun x -> q x && p x) l, also true, and the last line turned a map followed by a filter into one List.filter_map, true again.

Laws like these let you rewrite a pipeline without changing its answer, as long as f, g, p and q are pure: no printing, no refs, no exceptions. The compiled program measured what fusion saves on 1,000 elements: two maps allocated 6,000 words, since the list in the middle is built and thrown away, and the fused map 3,000.

The three rewrites the session checked, side by side:

Two passes One pass with the same result
List.map f (List.map g l) List.map (fun x -> f (g x)) l
List.filter p (List.filter q l) List.filter (fun x -> q x && p x) l
List.filter p (List.map f l) List.filter_map (fun x -> let y = f x in if p y then Some y else None) l

Fusing functions that have effects

Wrong: turning List.map save (List.map parse lines) into one map, where save writes a file and parse may raise. In two passes a bad line stops the program before anything is saved; fused, the lines before it may already be saved. Right: fuse only pure functions, or choose the new order on purpose.

A pipeline lists the steps in the order the data takes them

x |> f is f x, so a chain of |> reads like a list of instructions: start from this data, keep these elements, take this part, combine. The session totals the passing marks both ways, as a pipeline and as nested calls. Step through the pipeline stage by stage:

Interactive · stage by stage Four students through a pipeline, stage by stage Step through the pipeline in reading order and watch the value each stage hands to the next one.
Four students through a pipeline, stage by stage, step 5 of 5. Showing marks, List.filter m >= 40, List.map snd, List.fold_left ( + ) 0. In focus: marks, List.fold_left ( + ) 0.marks4 studentsList.filter m >= 40asha, meera, kiranList.map snd[72; 91; 58]List.fold_left ( + ) 0221

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. The data. marks pairs each name with a mark: asha 72, ravi 35, meera 91 and kiran 58. The pipeline starts from this list.
  2. Keep the passes. List.filter keeps the pairs whose mark is 40 or more. Ravi's 35 is dropped, so three pairs go on, in their original order.
  3. Keep only the marks. List.map snd replaces each pair with its second part, the mark, which gives [72; 91; 58].
  4. Combine into one number. List.fold_left ( + ) 0 adds the marks from the left: 0 + 72 + 91 + 58 = 221, the total the session printed.
  5. Reading order. Each |> hands the value on its left to the function on its right, so the steps read top to bottom in the order they run. The nested call gave the same 221 written inside out.

Totals, an average and the top two, as pipelines Python · pipeline_session.py
from session import toplevel

# x |> f is f x, so a chain of |> lists the steps in the order the data goes through them. The nested form gives the
# same result written inside out.
toplevel("""
let marks = [("asha", 72); ("ravi", 35); ("meera", 91); ("kiran", 58)];;
marks |> List.filter (fun (_, m) -> m >= 40) |> List.map snd |> List.fold_left ( + ) 0;;
List.fold_left ( + ) 0 (List.map snd (List.filter (fun (_, m) -> m >= 40) marks));;
let passed = List.filter (fun (_, m) -> m >= 40) marks;;
List.fold_left ( + ) 0 (List.map snd passed) / List.length passed;;
marks |> List.sort (fun (_, a) (_, b) -> compare b a) |> List.map fst |> List.take 2;;
""")

Output

$ ocaml
OCaml version 5.5.1
Enter #help;; for help.

# let marks = [("asha", 72); ("ravi", 35); ("meera", 91); ("kiran", 58)];;
val marks : (string * int) list =
  [("asha", 72); ("ravi", 35); ("meera", 91); ("kiran", 58)]
# marks |> List.filter (fun (_, m) -> m >= 40) |> List.map snd |> List.fold_left ( + ) 0;;
- : int = 221
# List.fold_left ( + ) 0 (List.map snd (List.filter (fun (_, m) -> m >= 40) marks));;
- : int = 221
# let passed = List.filter (fun (_, m) -> m >= 40) marks;;
val passed : (string * int) list =
  [("asha", 72); ("meera", 91); ("kiran", 58)]
# List.fold_left ( + ) 0 (List.map snd passed) / List.length passed;;
- : int = 73
# marks |> List.sort (fun (_, a) (_, b) -> compare b a) |> List.map fst |> List.take 2;;
- : string list = ["meera"; "asha"]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 pipeline_session.py

Both forms gave 221, but the nested call reads from the innermost function outwards, the opposite of the order in which the steps run. The average of the three passing marks was 73, because / on int drops the fraction of 221 / 3. Sorting by mark from the highest, keeping the names and taking two with List.take, new in OCaml 5.3, gave ["meera"; "asha"]: three stages after the data, each on its own line in a real program, each easy to test on its own.

Key idea

Write the steps of a list computation as a |> pipeline of map, filter and fold: it reads in the order it runs, and each stage is a small function you can test.

Checkpoint

Check: Map and filter as folds, fusion · Pipelines with |>

  1. Question 1 of 2 Which single pass gives the same result as List.map f (List.map g l) for pure functions f and g?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: List.map (fun x -> f (g x)) l

    The law holds for pure functions; with effects, fusing changes when each function runs.

  2. Question 2 of 2 What does [5; 12; 8; 3] |> List.filter (fun x -> x > 4) |> List.map (fun x -> x * 2) |> List.fold_left ( + ) 0 give?

    Type a number.

    Show the answer: Question 2 of 2

    Answer: 50

    The filter keeps [5; 12; 8], the map doubles them to [10; 24; 16], and the fold adds them: 50.

Practise: map, filter, fold and pipelines

The questions use the session’s marks, folds and laws with new lists. Work out each result, or decide whether two forms are equal, before you check:

Practice that adapts to you

Practise this lesson

12 questions about 4 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 12 What is the type of List.map?

    Choose one answer.

    Show the answer: Question 1 of 12

    Answer: ('a -> 'b) -> 'a list -> 'b list

    List.map snd marks turned a (string * int) list into an int list, so 'a and 'b can be different types.

  2. Question 2 of 12 What does List.partition (fun x -> x > 2) [1; 2; 3; 4] return?

    Choose one answer.

    Show the answer: Question 2 of 12

    Answer: ([3; 4], [1; 2])

    partition does the work of a filter and of its opposite in one pass.

  3. Question 3 of 12 How many elements does List.concat_map (fun x -> List.init x Fun.id) [1; 2; 3] return?

    Type a number.

    Show the answer: Question 3 of 12

    Answer: 6 elements

    The function returns [0], [0; 1] and [0; 1; 2], and concat_map joins them into one list of 1 + 2 + 3 = 6 elements.

  4. Question 4 of 12 According to the manual, what is List.fold_left f init [b1; b2; b3]?

    Choose one answer.

    Show the answer: Question 4 of 12

    Answer: f (f (f init b1) b2) b3

    The definition in the List documentation is f (... (f (f init b1) b2) ...) bn.

  5. Question 5 of 12 What does List.fold_left ( - ) 20 [5; 3] give?

    Type a number.

    Show the answer: Question 5 of 12

    Answer: 12

    (20 - 5) - 3 = 12. List.fold_right ( - ) [5; 3] 20 would give 5 - (3 - 20) = 22.

  6. Question 6 of 12 An import adds up 2,000,000 amounts. Which call is safe whatever the stack limit?

    Choose one answer.

    Show the answer: Question 6 of 12

    Answer: List.fold_left ( + ) 0 amounts

    When the order does not matter, as for +, choose fold_left for long lists.

  7. Question 7 of 12 Which fold gives the same result as List.map f l?

    Choose one answer.

    Show the answer: Question 7 of 12

    Answer: List.fold_right (fun x acc -> f x :: acc) l []

    The session's home-made map, List.fold_right (fun x acc -> f x :: acc) l [], equalled List.map on [3; 8; 1; 6; 4].

  8. Question 8 of 12 For pure p and q, which single filter equals List.filter p (List.filter q l)?

    Choose one answer.

    Show the answer: Question 8 of 12

    Answer: List.filter (fun x -> q x && p x) l

    The session checked this law on [3; 8; 1; 6; 4] with p x = x mod 2 = 0 and q x = x > 3: true.

  9. Question 9 of 12 Two maps over 1,000 elements allocated 6,000 words and the fused map 3,000. How many words would three maps over the same list allocate, if each builds a list of 1,000 elements?

    Type a number.

    Show the answer: Question 9 of 12

    Answer: 9000 words

    Each map builds 1,000 cells of three words, 3,000 words; three maps build three lists, 9,000 words. One fused map would still allocate 3,000.

  10. Question 10 of 12 What is l |> List.map f the same as?

    Choose one answer.

    Show the answer: Question 10 of 12

    Answer: List.map f l

    Each |> hands the value on its left to the function on its right.

  11. Question 11 of 12 The passing marks were [72; 91; 58]. What does their total divided by List.length give with integer division?

    Type a number.

    Show the answer: Question 11 of 12

    Answer: 73

    221 / 3 is 73 with / on int, which drops the fraction; Float.of_int and /. would give about 73.67.

  12. Question 12 of 12 A pipeline sorts 10,000 orders by amount with List.stable_sort, then filters the 30 orders of one customer. What change gives the same result with less work?

    Choose one answer.

    Show the answer: Question 12 of 12

    Answer: Filter first, then sort the 30 orders

    Moving a filter before an expensive stage is a safe rewrite when the stage keeps the elements' order or does not depend on the dropped elements.

Interview questions

Warm-up (fresher to 2–5 years): how do fold_left and fold_right differ in OCaml?

Both combine the elements of a list into one value with a function and a start value. fold_left starts from the first element, f (f (f init a) b) c, and passes its result forward in a tail call, so it runs in constant stack space. fold_right starts from the last element, f a (f b (f c init)), and needs one stack frame per element, which the standard library documents as not tail-recursive. With an operator that depends on order the results differ: subtraction from 10 over [1; 2; 3] gives 4 from the left and -8 from the right. Folding :: from the left reverses a list, and from the right rebuilds it in order.

Two harder questions follow, about a total that overflows on a large import and about a parsing pipeline that keeps options around too long. For each, name the pass or the fold you would change first, then open its first step:

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Mid to senior

    A data import totals its amounts with List.fold_right (fun x acc -> x.amount + acc) rows 0. It works on every test file, but a customer's file of 3 million rows stops it with Stack_overflow. Explain why, fix it, and say how you would keep the fix from regressing.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Fresher to mid

    A pipeline parses lines with lines |> List.map parse |> List.filter Option.is_some |> List.map Option.get, where parse returns an option. It works, but a teammate calls it fragile and slow. Rewrite it, and explain what each change improves.

    Model answer, Pro. First step and follow-ups

Flashcards · 6 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. Which List functions replace the usual hand-written recursions over a list?

    map, filter, partition, filter_map and concat_map. They keep the order of the elements and never change the list they are given.

  2. When is List.filter_map better than a map followed by a filter?

    When a step can fail, as parsing can: the function returns an option, and the result has no None left to forget.

  3. What do List.fold_left ( - ) 10 [1; 2; 3] and List.fold_right ( - ) [1; 2; 3] 10 give?

    4 and -8. fold_left combines from the first element, fold_right from the last, so an order-sensitive function gives two answers.

  4. Which fold suits a list of a million elements, and why?

    fold_left: its recursive call is a tail call, so it needs constant stack space, while fold_right needs one stack frame per element.

  5. When can two List.map passes be fused into one?

    When both functions are pure: one pass then gives the same list without building one in the middle. With effects, such as saving after a parse that may raise, fusing changes what happens before a failure.

  6. Why write a list computation as a |> pipeline of map, filter and fold?

    It lists the steps in the order the data takes them, each stage is a small testable function, and the result equals the nested calls.

Key takeaways

  • List.map, filter, partition, filter_map and concat_map replace the usual recursions over a list, keep the order of the elements and never change the list they are given.
  • fold_left combines from the first element in constant stack, while fold_right combines from the last and needs a frame per element, so the two give different answers for subtraction and different stack use for long lists.
  • map and filter are folds that rebuild a list, and for pure functions two maps or two filters can be fused into one pass that builds no list in the middle.
  • A |> pipeline of map, filter and fold lists the steps in the order they run, and gives the same result as the nested calls.

Terms in this lesson

fold
Combining all the elements of a list into one value with a function and a start value; List.fold_left starts from the first element and List.fold_right from the last.
higher-order function
A function that takes another function as an argument or returns one, such as List.map, which applies the function it is given to every element of a list.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.