Skip to content
Mutable State and Loops
step 1/7

Reading — step 1 of 7

Learn

~3 min readHigher-Order, Modules, Mutable

F# is functional first but has full support for imperative code. Mutable bindings, refs, arrays, and loops are all available — use sparingly, where they fit. Microsoft's F# docs explicitly call F# 'functional first, but multi-paradigm.'

Mutable bindings

let mutable counter = 0
counter <- counter + 1                  // assignment with <-
printfn "%d" counter                    // 1
  • let mutable declares a mutable binding
  • <- assigns a new value (NOT =)

Without mutable, let counter = 0 is immutable; later assignment is a type error.

ref cells

The older imperative style:

let counter = ref 0
incr counter                            // counter := !counter + 1
printfn "%d" !counter                   // 1 — dereference with !
  • ref initial creates a mutable cell
  • !r dereferences
  • r := value assigns
  • incr r / decr r for int refs

In modern F#, let mutable is usually preferred over ref for simple mutation. ref cells are useful when you need to pass a mutable cell around as a value.

Arrays — fixed-size mutable

let arr = [| 1; 2; 3; 4; 5 |]           // | brackets, semicolons inside
arr.[0]                                  // 1 — read with .[i]
arr.[2] <- 99                            // assign
arr.Length                               // 5

Arrays are MUTABLE and FIXED-SIZE — different from lists (immutable, linked). The Array module mirrors List:

Array.map (fun x -> x * 2) [|1; 2; 3|]
Array.filter (fun x -> x > 2) [|1; 2; 3; 4|]
Array.sum [|1; 2; 3|]
Array.length arr

When to use arrays vs lists

  • List — immutable, easy to prepend/recurse
  • Array — fixed size, fast random access (O(1) by index), in-place updates
  • ResizeArray — mutable growable list (.NET List<T>)
  • Seq — lazy sequence (similar to IEnumerable)

Most F# code uses lists. Arrays for performance-critical numeric work.

for and while loops

for i in 1..5 do
    printfn "%d" i

for i = 1 to 5 do                       // alternative syntax
    printfn "%d" i

for i = 5 downto 1 do                   // descending
    printfn "%d" i

let mutable n = 0
while n < 5 do
    printfn "%d" n
    n <- n + 1

Loops execute their bodies for side effects. The body must have type unit.

for-in over collections

for x in [1..5] do
    printfn "%d" x

for x in [|1; 2; 3|] do
    printfn "%d" x

for (k, v) in Map.ofList ["a", 1; "b", 2] do
    printfn "%s=%d" k v

Works on any IEnumerable<T>. Pattern matching in the loop binding (like the (k, v) destructuring) works too.

Mutable record fields

type Counter = { mutable Count: int }

let c = { Count = 0 }
c.Count <- c.Count + 1                  // mutate the field

The mutable keyword allows in-place modification with <-. Other fields stay immutable.

Dictionary and ResizeArray

For mutable .NET-style collections:

open System.Collections.Generic

let dict = Dictionary<string, int>()
dict.["alice"] <- 30
dict.["bob"] <- 25
dict.ContainsKey("alice")               // true

let list = ResizeArray<int>()
list.Add(1)
list.Add(2)
list.[0]                                 // 1
list.RemoveAt(0)

Dictionary<TKey, TValue> and ResizeArray<T> are .NET BCL types. F# bindings use <- for assignment.

When to mix functional and imperative

Yes:

  • Performance-critical inner loops
  • Hash tables for fast lookup
  • .NET interop (the BCL is mostly imperative)
  • Implementing stateful APIs

No:

  • Default coding style — stay functional where you can
  • Public API surface
  • Where pattern matching + recursion is clearer

Common mistakes

  • = vs <-= is comparison; <- is assignment. Mixing them is a common newcomer error.
  • Forgetting mutable — without it, let x = 0; x <- 1 fails to compile.
  • ! vs ref! dereferences ref cells. let mutable doesn't need !.
  • Array [| |] vs list [ ] — different brackets. Mix the syntax and the compiler complains.
  • arr.[i] vs arr[i] — F# uses dotted indexing arr.[i]. Bracket-less is being added but arr.[i] is the safe form.

Discussion

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

Sign in to post a comment or reply.

Loading…