Learn about pattern matching in F#. This tutorial explains match expressions and their usage in F# programming.
last modified May 17, 2025
This article explores how to effectively use match expressions in F# to enhance branching logic and data handling.
A match expression enables structured branching by comparing an expression against a predefined set of patterns. Each possible outcome is called an arm, allowing developers to process data efficiently. Match expressions can be used to transform input, decompose complex data structures, or extract specific elements with ease.
F# match expressions rely on the match, with, and when keywords. The match keyword initiates the pattern-matching process, with defines the possible arms, and when introduces additional conditional filters to refine matches. This syntax provides a concise and expressive way to handle conditional logic without excessive nested statements.
The basic patterns are constants, integers, strings, or enumerations.
main.fsx
let word = “falcon”
let langs = [ “Slovak” “German” “Hungarian” “Russian” “French” ]
for lang in langs do
let res =
match lang with
| "Slovak" -> "sokol"
| "German" -> "Falke"
| "Hungarian" -> "sólyom"
| "Russian" -> "сокол"
| "French" -> "faucon"
| _ -> "unknown"
printfn $"{word} in {lang} is {res}"
The program prints a translation of the word falcon in a few languages.
let res = match lang with | “Slovak” -> “sokol” | “German” -> “Falke” | “Hungarian” -> “sólyom” | “Russian” -> “сокол” | “French” -> “faucon” | _ -> “unknown”
The match expression returns a value. Each arm in a match expression is started with |. The string pattern follows the | character. The return value is specified after ->. The wildcard match _ returns a value for an option that is not recognized.
λ dotnet fsi main.fsx falcon in Slovak is sokol falcon in German is Falke falcon in Hungarian is sólyom falcon in Russian is сокол falcon in French is faucon
In the next example, we match enums.
main.fsx
open System
type Day = | Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday
let days = [ Monday; Tuesday; Wednesday; Thursday; Friday; Saturday; Sunday ]
let rnd = new Random()
let res = days |> Seq.sortBy (fun _ -> rnd.Next()) |> Seq.take 3
for e in res do
match e with
| Monday -> printfn "%s" "monday"
| Tuesday -> printfn "%s" "tuesday"
| Wednesday -> printfn "%s" "wednesay"
| Thursday -> printfn "%s" "thursday"
| Friday -> printfn "%s" "friday"
| Saturday -> printfn "%s" "saturday"
| Sunday -> printfn "%s" "sunday"
We define a Day enumeration. We randomly pick three Day enumerations.
match e with | Monday -> printfn “%s” “monday” | Tuesday -> printfn “%s” “tuesday” | Wednesday -> printfn “%s” “wednesay” | Thursday -> printfn “%s” “thursday” | Friday -> printfn “%s” “friday” | Saturday -> printfn “%s” “saturday” | Sunday -> printfn “%s” “sunday”
In this match expression, we match against enumerations. Here we do not return value; we print messages.
λ dotnet fsi main.fsx monday friday tuesday
Guards are conditions that must be fulfilled inside the arm. Guards are created with when.
main.fsx
let vals = [ 1; -3; 5; 6; 0; 4; -9; 11; 22; -7 ]
for wal in vals do
match wal with
| n when n < 0 -> printfn "%d is negative" n
| n when n > 0 -> printfn "%d is positive" n
| _ -> printfn "zero"
With pattern match, we categorize values into negative, positive, and zero values.
| _ -> printfn “zero”
With this we create an exhaustive matching.
λ dotnet fsi main.fsx 1 is positive -3 is negative 5 is positive 6 is positive zero 4 is positive -9 is negative 11 is positive 22 is positive -7 is negative
Multiple options can be combined with |.
main.fsx
let grades = [“A”; “B”; “C”; “D”; “E”; “F”; “FX”]
for grade in grades do
match grade with
| "A" | "B" | "C" | "D" | "E" | "F" -> printfn "%s" "passed"
| _ -> printfn "%s" "failed"
We categorize grades into two groups: passed and failed. The passed arm combines all matching values with |.
λ dotnet fsi main.fsx passed passed passed passed passed passed failed
In the next example, we match records.
main.fsx
type User = { FirstName: string LastName: string Occupation: string }
let users = [ { FirstName = “John” LastName = “Doe” Occupation = “gardener” } { FirstName = “Jane” LastName = “Doe” Occupation = “teacher” } { FirstName = “Roger” LastName = “Roe” Occupation = “driver” } ]
for user in users do match user with | { LastName = “Doe” } -> printfn “%A” user | _ -> ()
We have a few users, which are created with records. With record pattern matching, we select all Does.
match user with | { LastName = “Doe” } -> printfn “%A” user | _ -> ()
We specify an anonymous record with an attribute as a pattern.
λ dotnet fsi main.fsx { FirstName = “John” LastName = “Doe” Occupation = “gardener” } { FirstName = “Jane” LastName = “Doe” Occupation = “teacher” }
With :?, we can match types.
main.fsx
open System.Collections
type User = { FirstName: string LastName: string Occupation: string }
let vals = new ArrayList() vals.Add(1.2) vals.Add(22) vals.Add(true) vals.Add(“falcon”)
vals.Add( { FirstName = “John” LastName = “Doe” Occupation = “gardener” } )
for wal in vals do match wal with | :? int -> printfn “an integer” | :? float -> printfn “a float” | :? bool -> printfn “a boolean” | :? User -> printfn “a User” | _ -> ()
We have a list with different data types. We go through the list and print the type for each of the elements.
λ dotnet fsi main.fsx a float an integer a boolean a User
When a pattern match is defined in a function, it can be simplified with the function keyword.
main.fsx
open System
type Choices = | A | B | C
let getVal = function | 1 -> A | 2 -> B | _ -> C
let chx = [ for _ in 1..7 do yield getVal (Random().Next(1, 4)) ]
printfn “%A” chx
In the example, we builda list of choices randomly.
let getVal = function | 1 -> A | 2 -> B | _ -> C
This is the simplified pattern match syntax inside a function.
λ dotnet fsi main.fsx [B; C; A; C; A; B; B]
In the next example, we match list patterns.
main.fsx
let vals = [ [ 1; 2; 3 ] [ 1; 2 ] [ 3; 4 ] [ 8; 8 ] [ 0 ] ]
let twoels (sub: int list) = match sub with | [ x; y ] -> printfn “%A” [ x; y ] | _ -> ()
for sub in vals do twoels sub
The program prints all lists that contain two elements.
λ dotnet fsi main.fsx [1; 2] [3; 4] [8; 8]
A match pattern can be used in a list comprehension.
main.fsx
let res = [ for e in 1..100 do match e with | e when e % 3 = 0 -> yield “fizz” | e when e % 5 = 0 -> yield “buzz” | e when e % 15 = 0 -> yield “fizzbuzz” | _ -> yield (string e) ]
printfn “%A” res
We solve the fizz-buzz challenge with a list comprehension. All values are stored in a list.
λ dotnet fsi main.fsx [“1”; “2”; “fizz”; “4”; “buzz”; “fizz”; “7”; “8”; “fizz”; “buzz”; “11”; “fizz”; “13”; “14”; “fizz”; “16”; “17”; “fizz”; “19”; “buzz”; “fizz”; “22”; “23”; “fizz”; “buzz”; “26”; “fizz”; “28”; “29”; “fizz”; “31”; “32”; “fizz”; “34”; “buzz”; “fizz”; “37”; “38”; “fizz”; “buzz”; “41”; “fizz”; “43”; “44”; “fizz”; “46”; “47”; “fizz”; “49”; “buzz”; “fizz”; “52”; “53”; “fizz”; “buzz”; “56”; “fizz”; “58”; “59”; “fizz”; “61”; “62”; “fizz”; “64”; “buzz”; “fizz”; “67”; “68”; “fizz”; “buzz”; “71”; “fizz”; “73”; “74”; “fizz”; “76”; “77”; “fizz”; “79”; “buzz”; “fizz”; “82”; “83”; “fizz”; “buzz”; “86”; “fizz”; “88”; “89”; “fizz”; “91”; “92”; “fizz”; “94”; “buzz”; “fizz”; “97”; “98”; “fizz”; “buzz”]
With active patterns we can define named partitions of input data and use these names in a pattern matching expression.
main.fsx
let (|Even|Odd|) input = if input % 2 = 0 then Even else Odd
let testnum input = match input with | Even -> printfn “%d is even” input | Odd -> printfn “%d is odd” input
testnum 3 testnum 8 testnum 11
In the active pattern syntax, we define Even and Odd names. These names are later used in the match expression.
let (|Even|Odd|) input = if input % 2 = 0 then Even else Odd
We define the Even and Odd active patterns.
let testnum input = match input with | Even -> printfn “%d is even” input | Odd -> printfn “%d is odd” input
These names are used in the match expression.
λ dotnet fsi main.fsx 3 is odd 8 is even 11 is odd
We can use regular expression in match patterns.
main.fsx
open System.Text.RegularExpressions
let (|RegEx|_|) p i = let m = Regex.Match(i, p)
if m.Success then
Some m.Groups
else
None
let checkrgx (msg) = match msg with | RegEx @"\d+" g -> printfn “Digit: %A” g | RegEx @"\w+" g -> printfn “Word : %A” g | _ -> printfn “Not recognized”
checkrgx “an old falcon” checkrgx “1984” checkrgx “3 hawks”
In the example, we use active patterns and regular expression in pattern matching.
λ dotnet fsi main.fsx Word : seq [an] Digit: seq [1984] Digit: seq [3]
Match expressions can be used to handle exceptions.
main.fsx
open System
printf “Enter a number: "
let value = Console.ReadLine()
let n = match Int32.TryParse value with | true, num -> num | _ -> failwithf “’%s’ is not an integer” value
let f = function | value when value > 0 -> printfn “positive value” | value when value = 0 -> printfn “zero” | value when value < 0 -> printfn “negative value” | _ -> ()
f n
We expect an integer value from the user. We try to parse the input value; if it is not an integer, we fail with an error message.
let n = match Int32.TryParse value with | true, num -> num | _ -> failwithf “’%s’ is not an integer” value
TryParse returns true if the parsing was successfull. Therefore, we have the true boolean pattern in the first arm. For the rest we fail with failwithf.
Option types are used to represent values that may or may not be present. Pattern matching is a safe and idiomatic way to handle Some and None cases in F#.
main.fsx
let describeOption opt = match opt with | Some v -> printfn “The value is %d” v | None -> printfn “No value found”
describeOption (Some 10) describeOption None
In this example, the function describeOption prints the value if it is present, or a message if it is not. Pattern matching on option types makes the code concise and safe.
λ dotnet fsi main.fsx The value is 10 No value found
In this article we have worked with match expressions 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.