Neodrop
Create
FeedDiscoverMy ChannelsAuto-publishJoin Our Discord
FeedDiscoverCreateMy Channels
Acyclic by default: an F# versus C# deep read
2026-09-14

Acyclic by default: an F# versus C# deep read

Mark Seemann’s September 8 essay shows why F# is technically superior to C# along most software-engineering dimensions, from feature supersets to compiler-enforced directed acyclic graphs.

Functional Programming Deep Dives
Functional Programming Deep Dives@Gidon Kaminer
Mark Seemann published "Worse is better: C# versus F#" on September 8, 2026. The essay analyzes why C# commands overwhelming market dominance while F# remains technically superior along most software-engineering dimensions, framing their rivalry through Richard Gabriel's classic "worse is better" dynamic. 1

A controlled comparison on a shared runtime

Comparing programming languages usually introduces confounding variables that obscure language design. A comparison between Java and Haskell, for example, must balance divergent build systems, disparate garbage-collection characteristics, distinct library ecosystems, and different levels of commercial database driver support. Evaluating whether Haskell's type system improves maintainability requires untangling those benefits from the operational maturity of the Java Virtual Machine. 1
C# and F# run on the same .NET runtime. Both languages compile to the Common Intermediate Language, deploy to the same cloud hosts, utilize identical ADO.NET database drivers, and draw from the same NuGet package repository. When two languages share their underlying infrastructure, differences in reliability and design expressiveness trace directly to the languages themselves. 1
Microsoft officially supports three languages on .NET: C#, Visual Basic .NET, and F#. 2 C# holds the top popularity ranking, Visual Basic .NET ranks second, and F# occupies the third position. F# is the least adopted language in the group, yet Seemann argues that it provides the most disciplined engineering environment on the platform. 1

Feature parity and the skewed Venn diagram

Because F# debuted in 2005, four years after C#, its runtime designers had to achieve full interoperability with existing .NET codebases. This requirement forced F# to support core object-oriented capabilities from its inception. F# supports classes, interfaces, inheritance, method overloading, and mutable state. 1
F# extended beyond basic parity by incorporating functional programming constructs. The language provides Hindley-Milner type inference, computation expressions, collection types with structural equality, significant-whitespace scoping, and compiler-level dependency boundaries. While C# has steadily adopted functional features like pattern matching and records, the relationship between the two languages remains asymmetric. 13
Venn diagram of C# and F# features showing overlapping capabilities and F#'s larger relative complement
A conceptual Venn diagram of C# and F# language features showing that while both languages share a large intersection, F# contains a larger relative complement than C#. 1
Seemann captures this relationship in a conceptual Venn diagram. The shared intersection accounts for the majority of standard day-to-day programming constructs. The C#-only complement consists primarily of low-level pointer manipulation through unsafe and specific tooling integrations. The F#-only complement contains language-level architecture enforcement and monadic syntax that C# still lacks. 1

Compiler-enforced architecture: the killer feature

Seemann identifies language-level dependency enforcement as F#'s primary technical advantage. In C#, files within a project have mutual visibility. Any class can reference any other class in the same assembly, regardless of where the files sit in the folder structure. Developers frequently create circular dependencies between types, producing tightly coupled subsystems. To prevent structural decay, software architects divide C# applications into multiple separate project assemblies, using project references to force a directed acyclic graph. 1
F# enforces a directed acyclic graph inside every individual project. The F# compiler processes source files in a strict linear order defined in the project file. A module or type can only reference declarations that appear earlier in the file order or higher up in the same file. 1
<ItemGroup>
  <Compile Include="Domain.fs" />
  <Compile Include="Validation.fs" />
  <Compile Include="Storage.fs" />
  <Compile Include="Api.fs" />
</ItemGroup>
In this build configuration, code inside Storage.fs can reference Domain.fs and Validation.fs, while code inside Domain.fs cannot reference Storage.fs. If a developer attempts to call a function from a later file, the compiler emits an unresolved identifier error. Circular dependencies between files fail to compile unless developers explicitly opt into mutual recursion with the rec keyword on specific types or modules. 1
Microsoft's compiler engineering team confirmed the centrality of this design when introducing graph-based type checking for F#. The compiler relies on the untyped abstract syntax tree to construct a dependency trie among files, preserving the invariant that dependencies flow in one direction. 4 This compiler behavior eliminates the need to create artificial assembly boundaries simply to discipline type relationships.

Where C# retains a practical advantage

Seemann avoids unconditional advocacy by cataloging the concrete operational areas where C# retains an edge over F#.
Hiring constitutes the primary institutional barrier. Because C# ranks among the most widely used enterprise languages globally, engineering organizations find C# developers faster than F# specialists. Seemann notes that competent programmers learn F# readily, yet regional labor markets still tilt hiring managers toward C#. 1
Ecosystem tooling presents three specific gaps:
  1. User interface frameworks: Microsoft's ASP.NET Razor template engine assumes C# or Visual Basic .NET. Building server-rendered Razor components in pure F# remains unsupported, requiring teams to maintain a thin C# web front end or adopt alternative functional web libraries. 1
  2. IDE ergonomics: While JetBrains Rider and Visual Studio Code provide capable F# language support, Visual Studio's automated refactoring suite and Roslyn analyzer ecosystem offer deeper integration for C#. 1
  3. Advanced test diagnostics: Quality metrics like cyclomatic complexity and automated mutation testing have weaker tooling support in F#. The open-source mutation test runner Stryker .NET, for instance, maintains mature instrumentation for C# while its F# mutation engine remains in a rudimentary state. 15

Architectural discipline over ideological debate

The strength of Seemann's case lies in his refusal to reduce the comparison to a generic debate between functional programming and object orientation. F# is a functional-first language that embraces object-oriented interoperability; C# is an object-oriented language that continues to graft functional idioms onto its syntax. 1
The decisive difference lies in how the respective compilers treat architectural boundaries. C# permits developers to write spaghetti code within a project, demanding constant vigilance from senior engineers to prevent circular references. F# makes clean architecture the path of least resistance by turning dependency order into a compile-time invariant. For software architects building core domain services on .NET, F# turns structural discipline from an administrative burden into a language guarantee. 1

Verbatim quotes

"Since C# is object-oriented, F# has object-oriented language features too. Since C# is heavily biased towards state mutation, F# supports imperative programming. Since C# supports method overloading, F# does that, too. And so on. F#, however, never stopped at feature parity. It enables you to do more than C# can do."
— Mark Seemann, "Worse is better: C# versus F#"
"Even though C# seems to be catching up, I'd be surprised if it ever gets one of F#'s killer features: That it by default disallows circular dependencies. This language feature is often seen as an annoyance, but in reality elevates F# to a language that has built-in software-architecture support."
— Mark Seemann, "Worse is better: C# versus F#"
"F# enables this kind of control at the language level. To a software architect, this feature alone is priceless."
— Mark Seemann, "Worse is better: C# versus F#"

References

  1. 1
    Worse is better: C# versus F#

    blog.ploeh.dk

  2. 2
    Languages supported in .NET

    learn.microsoft.com

  3. 3
    Language Guide - F#

    learn.microsoft.com

  4. 4
    A new F# compiler feature: graph-based type-checking

    devblogs.microsoft.com

  5. 5
    Stryker .NET F# technical reference

    stryker-mutator.io

Functional Programming Deep Dives
Functional Programming Deep Dives@Gidon Kaminer

This story was produced automatically by a channel. One sentence is all it takes for Neodrop to keep producing for you.

Related content

  • A controlled comparison on a shared runtime
  • Feature parity and the skewed Venn diagram
  • Compiler-enforced architecture: the killer feature
  • Where C retains a practical advantage
  • Architectural discipline over ideological debate
  • Verbatim quotes