Explore lists in F# and learn how to create, modify, and work with ordered collections using functional programming techniques. This tutorial covers essential list operations and best practices for efficient data handling in F# programming.
last modified May 1, 2025
This article demonstrates how to work with the List collection in F#.
A list in F# is an ordered, immutable sequence of elements, all of the same type. Lists are fundamental in functional programming and provide a convenient way to handle ordered collections without modifying their contents.
A list can be created using a list literal, where elements are separated by semicolons and enclosed in square brackets:
let vals = [ 1; 2; 3; 4; 5 ]
Alternatively, F# allows a more readable syntax where semicolons are omitted, and each element is placed on a new line inside square brackets:
let vals = [ 1 2 3 4 5 ]
Both syntaxes are valid, but the second format improves readability, especially for longer lists. Since lists in F# are immutable, any modifications require creating a new list rather than altering an existing one.
Lists in F# support various operations, including filtering, mapping, and folding, which allow developers to process data efficiently. Functions like List.map apply transformations to each element, while List.filter selects elements based on specific criteria. These operations embrace functional programming principles by emphasizing the use of pure functions to modify data.
Additionally, lists can be recursively processed, making them suitable for algorithms that require iteration without traditional loops. The combination of immutability, built-in list functions, and pattern matching makes F# lists a powerful tool for handling ordered collections with ease.
The following is a simple list example.
main.fsx
let vals = [ 1; 2; 3; 4; 5; 6 ]
printfn “%A” vals printfn “%d” vals.Head printfn “%d” vals.Length printfn “%A” vals.Tail
We have a list of integers. We print the contents of the list, its head, size, and tail.
let vals = [ 1; 2; 3; 4; 5; 6 ]
We define a list of integers with an array literal.
printfn “%A” vals
With the %A format specifier, we pretty-print the list.
printfn “%d” vals.Head
With the Head property, we print the first element of the list.
printfn “%d” vals.Length
We get the size of a list with Length.
printfn “%A” vals.Tail
With Tail, we get all but first elements of the list.
λ dotnet fsi main.fsx [1; 2; 3; 4; 5; 6] 1 6 [2; 3; 4; 5; 6]
In the next example, we loop over the elements of a list.
main.fsx
let vals = [ 1; 2; 3; 4; 5; 6 ]
vals |> List.iter (printfn “%d”)
printfn “————————”
for e in vals do printfn “%d” e
We provide two basic ways of iteration.
vals |> List.iter (printfn “%d”)
The List.iter is the functional way of looping over list elements.
for e in vals do printfn “%d” e
The classic, imperative way is via a for loop.
1 2 3 4 5 6
List elements are accessed through their indexes.
main.fsx
let words = [“pen”; “cup”; “dog”; “person”; “cement”; “coal”; “spectacles”; “cup”; “bread”]
let w1 = List.item 1 words printfn “%s” w1
let w2 = words[0] printfn “%s” w2
let i1 = List.findIndex(fun e -> e = “cup”) words printfn $“The first index of cup is {i1}”
let i2 = List.findIndexBack(fun e -> e = “cup”) words printfn $“The last index of cup is {i2}”
The program contains List indexing operations.
let w1 = List.item 1 words
We get the second item of the list with List.item.
let w2 = words[0]
We can also use the classic C style syntax.
let i1 = List.findIndex(fun e -> e = “cup”) words
Fith List.findIndex, we find the first element that satisfies the given predicate function.
let i2 = List.findIndexBack(fun e -> e = “cup”) words
Fith List.findIndexBack, we find the last element that satisfies the given predicate function.
λ dotnet fsi main.fsx cup pen The first index of cup is 1 The last index of cup is 7
The List.map function applies the given function to each of the elements of the collection.
main.fsx
let vals = [1..10]
let res = List.map(fun e -> e * 2) vals printfn “%A” res
We apply a map function on a list of integers.
let vals = [ 1 .. 10]
We define a list with a range operator.
let res = List.map(fun e -> e * 2) vals
Each of the elements of the list is multiplied by 2. The result is assigned to the res variable.
λ dotnet fsi main.fsx [2; 4; 6; 8; 10; 12; 14; 16; 18; 20]
We can filter list elements with List.filter.
main.fsx
let vals = [-3; -2; 0; 1; -5; 7; 9] let words = [“sky”; “war”; “rock”; “ocean”; “cloud”; “water”]
let pos = List.filter(fun e -> e > 0) vals printfn “%A” pos
let res = List.filter(fun (e:string) -> e.StartsWith(“w”)) words printfn “%A” res
In the program, we find out all positive numbers from a list of integers and all strings which start with ‘w’ in a list of words.
let pos = List.filter(fun e -> e > 0) vals
The List.filter function takes a predicate function. All elements must satisfy the given predicate.
let res = List.filter(fun (e:string) -> e.StartsWith(“w”)) words
Sometines, it is necessary to help the compiler with an explicity type definition.
λ dotnet fsi main.fsx [1; 7; 9] [“war”; “water”]
The List.zip function combines the two lists into a list of pairs. The two lists must have equal lengths.
main.fsx
let words = [“sky”; “cup”; “rock”; “pen”; “pearl”; “cloud”] let n = words.Length let idxs = [1..n]
let data = List.zip idxs words printfn “%A” data
printfn “—————–”
let m = data |> Map.ofList m |> Map.iter (fun k v -> printfn $"{k}: {v}");
We combine a list of strings with a list of integers. Then we convert the list into a map.
1: sky 2: cup 3: rock 4: pen 5: pearl 6: cloud
With the @ operator, we can merge two lists.
main.fsx
let a = [1; 2; 3; 4] let b = [4; 4; 5; 6]
let merged = a @ b |> List.distinct printfn “%A” merged
let merged2 = a @ b printfn “%A” merged2
The program merges two lists.
let merged = a @ b |> List.distinct
We merge two lists and pass the values to List.distinct, which removes duplicates.
let merged2 = a @ b
We merge two lists; we have all values, including duplicates.
λ dotnet fsi main.fsx [1; 2; 3; 4; 5; 6] [1; 2; 3; 4; 4; 4; 5; 6]
In the next example, we sort integers.
main.fsx
let nums = [ -1; 6; -2; 3; 0; -4; 5; 1; 2 ]
nums |> List.sort |> printfn “%A” nums |> List.sortDescending |> printfn “%A”
nums |> List.sortBy (abs) |> printfn “%A” nums |> List.sortByDescending (abs) |> printfn “%A”
We have a list of integers. We sort them with List.sort and List.sortDescending.
nums |> List.sortBy (abs) |> printfn “%A” nums |> List.sortByDescending (abs) |> printfn “%A”
With the help of the abs, we sort integers regarless of their sign.
λ dotnet fsi main.fsx [-4; -2; -1; 0; 1; 2; 3; 5; 6] [6; 5; 3; 2; 1; 0; -1; -2; -4] [0; -1; 1; -2; 2; 3; -4; 5; 6] [6; 5; -4; 3; -2; 2; -1; 1; 0]
In the next example, we sort a list of records.
main.fsx
type User = { Name: string Occupation: string Salary: int }
let users = [ { Name = “John Doe” Occupation = “gardener” Salary = 1280 } { Name = “Roger Roe” Occupation = “driver” Salary = 860 } { Name = “Tom Brown” Occupation = “shopkeeper” Salary = 990 } ]
users |> List.sortBy (fun u -> u.Salary) |> List.iter (fun u -> printfn “%A” u)
printfn “——————————–”
users |> List.sortByDescending (fun u -> u.Occupation) |> List.iter (fun u -> printfn “%A” u)
The program contains a list of User records. We sort the users by their salaries and occupation.
users |> List.sortBy (fun u -> u.Salary) |> List.iter (fun u -> printfn “%A” u)
The users are sorted by salaries in ascending order with List.sortBy.
users |> List.sortByDescending (fun u -> u.Occupation) |> List.iter (fun u -> printfn “%A” u)
Here, the users are sorted by their occupation in descending order with sortByDescending.
{ Name = “Tom Brown” Occupation = “shopkeeper” Salary = 990 } { Name = “John Doe” Occupation = “gardener” Salary = 1280 } { Name = “Roger Roe” Occupation = “driver” Salary = 860 }
List comprehension is a powerful syntax to generate lists. List comprehensions provide a concise way to create lists.
In F#, we can create list comprehensions with ranges and generators.
main.fsx
let vals = [ -1; 0; 2; -2; 1; 3; 4; -6 ]
let pos = [ for e in vals do if e > 0 then yield e ]
printfn “%A” pos
printfn “———————————”
[ for e in 1 .. 100 -> e * e ] |> printfn “%A”
printfn “———————————”
[ for a in 1 .. 100 do if a % 3 = 0 && a % 5 = 0 then yield a] |> printfn “%A”
printfn “———————————”
let vals3 = [ for x in 1 .. 3 do for y in 1 .. 10 -> x, y ]
printfn “%A” vals3
In F#, list comprehensions use for loops, if conditions and the yield keyword.
let vals = [ -1; 0; 2; -2; 1; 3; 4; -6 ]
let pos = [ for e in vals do if e > 0 then yield e ]
We have a list of values. A new list is constructed with a list comprehension. It contains only positive values.
[ for e in 1 .. 100 -> e * e ] |> printfn “%A”
We can use ranges in list comprehensions.
[ for a in 1 .. 100 do if a % 3 = 0 && a % 5 = 0 then yield a] |> printfn “%A”
Here we use two if conditions.
let vals3 = [ for x in 1 .. 3 do for y in 1 .. 10 -> x, y ]
Also, it is possible to use two for loops.
[(1, 1); (1, 2); (1, 3); (1, 4); (1, 5); (1, 6); (1, 7); (1, 8); (1, 9); (1, 10); (2, 1); (2, 2); (2, 3); (2, 4); (2, 5); (2, 6); (2, 7); (2, 8); (2, 9); (2, 10); (3, 1); (3, 2); (3, 3); (3, 4); (3, 5); (3, 6); (3, 7); (3, 8); (3, 9); (3, 10)]
In this article we have worked with lists in F#.
My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.