100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

Building a Console App in F#

A hands-on walkthrough of scaffolding, structuring, and handling input/output and exit codes in an F# console application.

PracticeBeginner8 min readJul 10, 2026
Analogies

Setting Up an F# Console Project

The dotnet new console -lang F# template scaffolds a runnable F# console app with a Program.fs entry point containing an [<EntryPoint>] main function that must return an int. The .fsproj lists compile items in build order, and dotnet run compiles and executes the app in one step during development, making the inner feedback loop fast while you iterate on logic.

🏏

Cricket analogy: Like the toss at the start of a match that sets everything in motion: dotnet new console -lang F# is that opening toss, instantly putting a runnable scaffold, an XI already on the field, in place before a ball is bowled.

Reading Input and Structuring Logic

Console apps typically read input with System.Console.ReadLine() and must parse it defensively, since raw input is always a string. Combine this with pattern matching against Int32.TryParse results, wrapped in Option or Result, to handle invalid input gracefully rather than letting a parse failure crash the app with an unhandled exception. This keeps the program flow explicit at every branch instead of relying on try/catch as the primary control mechanism.

🏏

Cricket analogy: Like a third umpire double-checking a boundary rope call frame by frame before confirming a six: Int32.TryParse is that careful double-check on user input before trusting it as a valid number, rather than assuming every throw is clean.

Handling Errors and Exit Codes

The [<EntryPoint>] function must return an int used as the process exit code, where 0 conventionally signals success and non-zero signals failure, which matters for scripting and CI pipelines that inspect the exit code to decide whether to continue. Wrap risky I/O such as file reads in try...with at the boundary of the app, while keeping core logic pure and exception-free so failures are easy to trace to a specific I/O operation rather than an arbitrary function deep in the call stack.

🏏

Cricket analogy: Like the scoreboard operator posting a final result code, win, loss, tie, or no-result, that everyone downstream, broadcasters and statisticians, reads to know what happened: a console app's exit code is that same unambiguous final signal for scripts.

fsharp
open System

let tryReadInt (prompt: string) =
    printf "%s" prompt
    match Int32.TryParse(Console.ReadLine()) with
    | true, value -> Some value
    | false, _ -> None

[<EntryPoint>]
let main argv =
    match tryReadInt "Enter your age: " with
    | Some age when age >= 0 ->
        printfn "In 10 years you'll be %d" (age + 10)
        0 // success exit code
    | Some _ ->
        eprintfn "Age cannot be negative."
        1 // failure exit code
    | None ->
        eprintfn "That wasn't a valid number."
        1

dotnet new console -lang F# creates a project with a single Program.fs containing [<EntryPoint>] let main argv = ... 0. You can add more .fs files, but remember to list them in dependency order in the .fsproj's <Compile Include="..." /> items.

Forgetting to return an int from main, or returning it from only some code paths, is a common beginner mistake: every branch of your top-level match or if/else in main must produce an int, or the compiler will reject the entry point.

  • dotnet new console -lang F# scaffolds a runnable project with Program.fs and a [<EntryPoint>] main function.
  • The .fsproj lists compiled files in dependency order; new files must be added there to be built.
  • Console.ReadLine() always returns a string; parse defensively with Int32.TryParse rather than assuming valid input.
  • Model parse failures with Option or Result and pattern-match on them instead of letting exceptions crash the app.
  • main must return an int exit code: 0 conventionally means success, non-zero means failure.
  • Keep try...with blocks at I/O boundaries (file/network access) and keep core logic pure where possible.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#FStudyNotes#BuildingAConsoleAppInF#Building#Console#App#Setting#StudyNotes#SkillVeris#ExamPrep