Skip to content
AgileLogics
4 min read

How F# Code Runs

Prerequisites: Your first program

F# is a compiled language:

  1. The F# compiler checks your code, infers the types, and compiles it to IL (Intermediate Language) — the same bytecode C# and Visual Basic compile to.
  2. The .NET runtime loads the IL and its JIT compiler turns it into native machine code as the program runs.
  3. dotnet fsi does both steps in memory, so a script feels like an interpreted language but runs at compiled speed.

Type errors happen before running

printfn "start"
let total = 10 + "5"
printfn "never reached"
main.fsx(2,18): error FS0001: The type 'string' does not match the type 'int'

Nothing printed — not even "start" — because the script failed to compile. The message gives the file, line, and column (2,18), and an error code (FS0001) you can search for. F# never converts between types silently, not even int to string.

Runtime errors (exceptions)

printfn "start"
let numbers = [| 1; 2; 3 |]
printfn "%d" numbers[5]

The output starts like this (the stack trace is shortened here):

start
System.IndexOutOfRangeException: Index was outside the bounds of the array.
   at <StartupCode$FSI_0001>.$FSI_0001.main@() in C:\demo\main.fsx:line 1
   ...
Stopped due to error

This time "start" printed, then the out-of-range index raised an exception that stopped the script. The stack trace after the message lists the chain of calls that led to the error — much more useful once your code is split into many functions.

Indentation matters

F# uses indentation instead of braces. The body of a function, if, or loop must be indented further than the line that starts it, and lines in the same block must line up. Use spaces — tabs are not allowed.

Order matters

The compiler reads top to bottom: you can only use a value or function after it's defined. In a project, the order of files in the .fsproj matters too. This sounds limiting, but it makes dependencies easy to follow — you always know that code only depends on what's above it.