F# and C#: Two Faces of .NET
F# and C# both target the .NET runtime, compile to the same Common Intermediate Language (CIL), and share the base class library and NuGet ecosystem, yet they differ sharply in default paradigm. F# is functional-first: values are immutable by default, type inference removes most explicit annotations, and code reads as a series of expressions. C# is object-oriented and imperative-first: variables are mutable by default and classes are the primary organizing unit, though modern C# has borrowed functional features such as LINQ, records, and pattern matching.
Cricket analogy: Compare Test cricket to T20 cricket: both are cricket played under the same ICC rules on the same size pitch, but Test cricket (F#) defaults to a patient, disciplined approach while T20 (C#) defaults to aggressive stroke-play, even though a batter like Virat Kohli can adapt technique to either format.
Type Systems and Null Safety
F# has a strong, inferred type system, and values of ordinary F# types cannot be null by default; instead, the idiomatic way to represent an optional value is Option<'T>, which forces callers to pattern-match on Some x or None before using the value. C# historically allowed null on any reference type, a common source of NullReferenceException at runtime. C# 8 introduced nullable reference types as an opt-in compiler feature, but the checks are advisory warnings rather than a runtime guarantee, so a null can still slip through if warnings are ignored or suppressed.
Cricket analogy: Think of DRS (Decision Review System): F#'s Option type is like a mandatory third-umpire review before any dismissal is finalized, forcing an explicit check, whereas pre-2018 C# null handling was like an umpire's raw on-field call with no review safety net, risking a wrong decision reaching the scoreboard.
Interop, Tooling, and When to Choose Each
Because F# and C# compile to the same CIL, projects of one language can reference projects of the other directly within a single .NET solution, with no interop shims required. Many teams write core business logic, domain models, and data pipelines in F# for correctness and testability, while using C# for ASP.NET Core controllers, EF Core migrations, or UI-heavy code where imperative style and a larger hiring pool matter. F# Interactive (FSI), a REPL bundled with the F# tooling, also enables rapid prototyping and data exploration that has no direct equivalent in typical C# workflows.
Cricket analogy: Like a franchise fielding a specialist death-bowler such as Jasprit Bumrah for the tricky last overs and all-rounders for the middle overs: a team mixes F# for gnarly, correctness-critical logic and C# for the broad, high-volume overs of everyday CRUD work, all within the same XI.
// F#: immutable discriminated union + exhaustive pattern matching, no nulls needed
type Shape =
| Circle of radius: float
| Rectangle of width: float * height: float
let area shape =
match shape with
| Circle r -> System.Math.PI * r * r
| Rectangle (w, h) -> w * h
// Equivalent C# requires a class hierarchy or a switch expression:
// double Area(Shape s) => s switch {
// Circle c => Math.PI * c.Radius * c.Radius,
// Rectangle r => r.Width * r.Height,
// _ => throw new ArgumentException()
// };Both F# and C# compile to the same Common Intermediate Language (CIL) and run on the same .NET runtime, so a single solution can freely mix .fs and .cs projects, referencing types across languages with no interop shims required.
F# discriminated unions and C# don't map perfectly: consuming an F# DU from C# exposes it as a class hierarchy with Tag properties and static factory methods rather than a native switch pattern, which can feel clunky until C# 8+ pattern matching syntax is used.
- F# and C# both compile to .NET IL and can be freely mixed within one solution.
- F# defaults to immutability and expression-based syntax; C# defaults to mutable, statement-based syntax.
- F#'s Option<'T> type forces explicit handling of missing values at compile time; C#'s nullable reference types are advisory warnings, not hard guarantees.
- F# pattern matching on discriminated unions is exhaustive-checked by the compiler; C# switch expressions only recently gained comparable pattern matching.
- Many teams use F# for domain logic, pipelines, and correctness-critical code, and C# for ASP.NET Core web layers and broader team collaboration.
- F# Interactive (FSI) enables REPL-style rapid prototyping that has no direct C# equivalent in typical workflows.
Practice what you learned
1. What does F# use by default for variable bindings, unlike C#?
2. Which .NET intermediate representation do both F# and C# compile to?
3. What F# feature forces callers to explicitly handle missing values?
4. In F#, matching on a discriminated union with a missing case will typically:
5. Which statement about F#/C# interop is true?
Was this page helpful?
You May Also Like
F# Best Practices
Guidelines for writing idiomatic, maintainable F# code covering function composition, project organization, and error handling.
F# Quick Reference
A condensed cheat sheet of core F# syntax, collection functions, and tooling for fast lookup while coding.
F# Interview Questions
Common F# interview topics and questions, from core language concepts to system design, with guidance on strong answers.
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