How F# Code Runs
Prerequisites: Your first program
F# is a compiled language:
- 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.
- The .NET runtime loads the IL and its JIT compiler turns it into native machine code as the program runs.
dotnet fsidoes 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.