ZetCode

F# Ranges

last modified May 17, 2025

In this article, we explore ranges in F# and how they simplify the generation of ordered sequences. Ranges provide an elegant way to create lists of values without manual repetition, making them especially useful in loops and collection operations.

A range in F# defines a sequence of values between a specified start and end point. They can be used with integers, characters, and other incrementable types. The syntax allows for concise and readable code, improving efficiency when handling sequences. F# offers both inclusive and exclusive range operators, along with optional step values to define custom increments.

An inclusive range generates values between two points, including the final value. For example, [1 .. 5] produces [1; 2; 3; 4; 5], while ['a' .. 'e'] yields ['a'; 'b'; 'c'; 'd'; 'e']. This approach ensures predictable iteration without requiring explicit value assignments. To specify a custom step value, a third parameter can be introduced, such as [2 .. 2 .. 10], which generates [2; 4; 6; 8; 10].

Exclusive ranges omit the last value in a sequence. When using seq { 1 .. 4 }, the resulting sequence contains [1; 2; 3], excluding 4. This behavior is useful when ensuring that certain boundary conditions are maintained.

Ranges integrate naturally with loops, allowing for structured iteration over predefined values. For example, using a for loop with for i in 1 .. 5 do printfn "Iteration %d" i prints numbers from 1 to 5, reducing the need for manually managing loop counters.

Beyond iteration, ranges enhance collection processing by enabling transformations and filtering. A list comprehension, such as [ for i in 1 .. 5 -> i * i ], applies a transformation to each element, yielding [1; 4; 9; 16; 25]. This approach keeps code concise while leveraging functional programming principles.

Since F# ranges are generated lazily when used with sequences (seq expressions), they provide memory-efficient handling of large datasets. Unlike directly materializing lists, lazy evaluation ensures that values are computed only when needed, optimizing performance. When processing extensive sequences, alternatives like Seq.init or Seq.unfold may offer further efficiency gains.

F# basic integer range

The simplest form of range generates integers between two values.

basic.fsx
let numbers = [1..5]
printfn "Inclusive range: %A" numbers

let exclusive = [1..4..15]
printfn "With step 4: %A" exclusive

[1..5] |> List.iter (printfn "Number: %d")

We create three different integer ranges and demonstrate their usage.

let numbers = [1..5]

Creates an inclusive range from 1 to 5 (1, 2, 3, 4, 5).

let exclusive = [1..4..15]

Creates a range from 1 to 15 with step 4 (1, 5, 9, 13).

λ dotnet fsi basic.fsx
Inclusive range: [1; 2; 3; 4; 5]
With step 4: [1; 5; 9; 13]
Number: 1
Number: 2
Number: 3
Number: 4
Number: 5

F# descending range

Ranges can count downward by using a negative step.

descending.fsx
let countdown = [10..-1..5]
printfn "Countdown: %A" countdown

let bigStepDown = [20..-3..5]
printfn "Big steps down: %A" bigStepDown

[5..-1..1] |> List.iter (printfn "%d")

The example demonstrates descending ranges with different step values.

let countdown = [10..-1..5]

Creates a range from 10 down to 5 (10, 9, 8, 7, 6, 5).

λ dotnet fsi descending.fsx
Countdown: [10; 9; 8; 7; 6; 5]
Big steps down: [20; 17; 14; 11; 8; 5]
5
4
3
2
1

F# character range

Ranges work with characters as well as numbers.

characters.fsx
let alphabet = ['a'..'z']
printfn "Alphabet: %A" (alphabet |> List.take 5)

let vowels = ['a'..'e'..'z']
printfn "Every 5th letter: %A" vowels

['A'..'Z'] |> List.iter (printf "%c ")
printfn ""

Shows how to create ranges of characters.

let alphabet = ['a'..'z']

Generates all lowercase letters from 'a' to 'z'.

λ dotnet fsi characters.fsx
Alphabet: ['a'; 'b'; 'c'; 'd'; 'e']
Every 5th letter: ['a'; 'f'; 'k'; 'p'; 'u'; 'z']
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 

F# range with sequences

Ranges can be used with F# sequences for lazy evaluation.

sequences.fsx
let bigRange = seq {1..1000000}
printfn "First 5: %A" (bigRange |> Seq.take 5)

let infinite = seq {0..}
printfn "Infinite first 10: %A" (infinite |> Seq.take 10)

seq {10..-1..1} |> Seq.iter (printfn "Countdown: %d")

Demonstrates using ranges with sequences for memory efficiency.

let bigRange = seq {1..1000000}

Creates a sequence range that doesn't allocate all elements immediately.

let infinite = seq {0..}

Creates an infinite sequence starting at 0 (be careful with these!).

λ dotnet fsi sequences.fsx
First 5: seq [1; 2; 3; 4; ...]
Infinite first 10: seq [0; 1; 2; 3; ...]
Countdown: 10
Countdown: 9
Countdown: 8
Countdown: 7
Countdown: 6
Countdown: 5
Countdown: 4
Countdown: 3
Countdown: 2
Countdown: 1

F# range in for loops

Ranges are commonly used with for loops.

loops.fsx
for i in 1..5 do
    printfn "Loop iteration %d" i

printfn "---"

for i in 10..-2..0 do
    printfn "Countdown: %d" i

printfn "---"

for c in 'a'..'f' do
    printf "%c " c
printfn ""

The example demonstrates using ranges in for loops.

for i in 1..5 do

Standard for loop with inclusive range from 1 to 5.

λ dotnet fsi loops.fsx
Loop iteration 1
Loop iteration 2
Loop iteration 3
Loop iteration 4
Loop iteration 5
---
Countdown: 10
Countdown: 8
Countdown: 6
Countdown: 4
Countdown: 2
Countdown: 0
---
a b c d e f 

F# range with floating point

Floating-point ranges require explicit step values.

floating.fsx
let floatRange = [0.0..0.5..2.0]
printfn "Floating range: %A" floatRange

let descendingFloats = [5.0.. -1.0 ..0.0]
printfn "Descending floats: %A" descendingFloats

[0.0..0.1..1.0] |> List.iter (printfn "%.1f")

Shows how to create ranges with floating-point numbers.

let floatRange = [0.0..0.5..2.0]

Floating-point ranges must specify a step value.

λ dotnet fsi floating.fsx
Floating range: [0.0; 0.5; 1.0; 1.5; 2.0]
Descending floats: [5.0; 4.0; 3.0; 2.0; 1.0; 0.0]
0.0
0.1
0.2
0.3
0.4
0.5
0.6
0.7
0.8
0.9
1.0

F# custom range for DateTime

Ranges are not built-in for DateTime, but you can create a custom range using a sequence expression and a step value. This is useful for generating a series of dates.

date_time.fsx
open System

let dateRange (start: DateTime) (finish: DateTime) (step: TimeSpan) =
    seq {
        let mutable current = start
        while current <= finish do
            yield current
            current <- current + step
    }

let startDate = DateTime(2024, 5, 1)
let endDate = DateTime(2024, 5, 5)
let step = TimeSpan(1, 0, 0, 0) // 1 day

for d in dateRange startDate endDate step do
    printfn "%A" d

This example defines a dateRange function that yields dates from the start to the end date, stepping by the specified interval. Here, it prints all dates from May 1, 2024 to May 5, 2024.

In this article we've explored the versatility of ranges in F#. They provide a concise syntax for generating sequences of values, particularly useful with loops and collection operations.

Author

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.