Your First F# Program
The traditional way to start an F# project is dotnet new console -lang "F#" -o HelloFSharp, which scaffolds a minimal console application containing a single file, Program.fs, along with a .fsproj project file that tells the .NET build system which source files to compile and in what order. Unlike many languages, F# compiles files top-to-bottom and depends on their order in the .fsproj, so understanding that file matters even for a simple 'Hello, World!' program.
Cricket analogy: The .fsproj dictating file compile order is like a batting order sheet handed to the umpire before the match — who comes to the crease, and in what sequence, is fixed before play starts.
Understanding Program.fs
Inside Program.fs, the generated template defines a main function decorated with the [<EntryPoint>] attribute, which tells the compiler this is where program execution begins — much like Main in C# or main in C. The function must accept a string array of command-line arguments and return an int exit code (0 conventionally means success); inside it, printfn "Hello, World!" writes text to the console, where printfn is F#'s type-safe formatted-print function, using placeholders like %s for strings, %d for integers, and %f for floats, all checked by the compiler at compile time.
Cricket analogy: [<EntryPoint>] marking where execution starts is like the toss deciding which team bats first — a single, unambiguous designated starting point for the whole match.
Running with dotnet run vs F# Interactive
You can run the program two ways: dotnet run from the project folder compiles the whole project and executes the resulting binary, which is the standard workflow for a real application; alternatively, for quick experiments you can skip the project entirely and write a .fsx script file, then execute it directly with dotnet fsi hello.fsx, which is faster for throwaway exploration since there's no project file or build step involved, just the F# Interactive engine interpreting the script.
Cricket analogy: Running dotnet fsi hello.fsx for quick experiments versus dotnet run for the real project is like a net session for trying a new shot versus stepping out to bat in an actual first-class match.
Building a Slightly Bigger Example
A slightly richer first program demonstrates more of F#'s character than a bare 'Hello, World!': defining a small helper function above main, calling it with a list of names, and printing a formatted greeting for each one shows function definition, list iteration with List.iter, and printfn's multiple format specifiers working together in a single small file — a realistic taste of how F# code is typically structured even in much larger programs.
Cricket analogy: Iterating over a list of names with List.iter to greet each one is like a captain going down the entire batting lineup individually to give each player specific instructions before the innings.
// Program.fs
let greet name =
sprintf "Hello, %s! Welcome to F#." name
[<EntryPoint>]
let main argv =
let names = ["Alice"; "Bob"; "Priya"]
names
|> List.iter (fun n -> printfn "%s" (greet n))
printfn "Program ran with %d argument(s)." argv.Length
0 // exit code: 0 means successA common beginner mistake is forgetting [<EntryPoint>] on main, or defining main above other functions it calls — remember, F# compiles top-to-bottom, so a function must be defined before anything that calls it, and main must appear last in the entry-point file.
printfn (with a trailing newline) and sprintf (which returns a formatted string instead of printing it) share the same %s/%d/%f-style format specifiers, and both are checked by the compiler — passing an int where %s expects a string is a compile error, not a runtime crash.
- dotnet new console -lang "F#" -o HelloFSharp scaffolds a minimal project with a Program.fs file and a .fsproj.
- F# compiles files top-to-bottom in the order listed in the .fsproj; a function must be defined before it's called.
- [<EntryPoint>] marks the main function as the program's starting point; it must take string[] and return int.
- printfn is F#'s type-safe formatted print function, using specifiers like %s, %d, and %f checked at compile time.
- dotnet run builds and executes a full project; dotnet fsi script.fsx runs a script instantly without a project.
- Returning 0 from main conventionally signals success; nonzero values signal an error to the calling shell.
Practice what you learned
1. What attribute marks the entry point function in an F# console program?
2. What must the main function in F# return?
3. Which command scaffolds a new F# console project?
4. What's the fastest way to run a throwaway .fsx script without creating a project?
5. In what order does F# compile source files listed in a .fsproj?
Was this page helpful?
You May Also Like
Installing .NET and F#
Step-by-step guide to installing the .NET SDK, setting up F# tooling, and verifying your environment with fsi and dotnet new.
F# Syntax Basics
A tour of F#'s significant whitespace, function definitions, the pipe and composition operators, and basic types.
Values and Immutability
How F#'s let bindings are immutable by default, how to opt into mutability, and how shadowing differs from mutation.
What Is F#?
An overview of F# as a functional-first, statically typed language on .NET, its origins, and why developers choose it.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics