Worse is better: C# versus F#
Mark Seemann ยท rss ยท 2026-09-08 source โ Reader ๐ฅ๏ธ
practical ยท Argumentative opinion essay (contrarian technical advocacy), the second installment of the author's 'Worse is better' series โ The END is practical โ to change which .NET language the reader reaches for by default (choose F#). The BODY is a sequence of evaluative-theoretical judgments about relative language merit, organized as: tractability of the comparison, non-merit explanations for the popularity gap, a feature-superset argument, a single standout architectural feature, honest concessions of C#'s current advantages, and an explicit refusal to lean on the separate FP-vs-OOP argument. Unlike the predecessor JSON-vs-XML article, the author's own stated practice ('I would choose F# by default') matches his thesis rather than diverging from it.
Structure
| Part | Role |
|---|---|
| Series frame + thesis | Opens with the bolded hedge 'C# is a great programming language, but F# is better,' locates the piece in the 'Worse is better' series (linking back to the JSON-vs-XML installment and the VHS/Betamax analogy), and states the plan to argue F# is superior along most axes. |
| Popularity ranking + why this comparison is tractable | Establishes the .NET popularity ranking (C# > VB.NET > F#) and argues the comparison is unusually clean because ecosystem variables (hosting, build tools, data providers) are held constant across C# and F#, unlike cross-ecosystem language comparisons such as Java vs. Haskell. |
| Explaining the popularity gap without conceding merit | Offers non-merit explanations for C#'s greater popularity โ being the more established, mature alternative (echoing the JSON-vs-XML pattern) and the claim that imperative programming is easier to learn than declarative โ while explicitly flagging uncertainty about the cognitive-vs-pedagogical cause. |
| Feature parity and beyond (the Venn-diagram argument) | Explains F#'s design-forced feature parity with C# (required for .NET interoperability), then argues F# exceeds parity with a larger F#-only feature set than C#'s C#-only set, illustrated by an admittedly unmeasurable, not-to-scale Venn diagram. |
| A programming language for software architects | Singles out F#'s default disallowance of circular dependencies as F#'s standout feature, ties it to the general, coarse-grained, human-enforced discipline of module decomposition, and argues this makes F# priceless to architects. |
| What C# does better | Concedes real, current C# advantages โ programmer availability, GUI framework support (Razor), IDE tooling, and testing-tool ecosystem (cyclomatic complexity, coverage, mutation testing) โ while suggesting some are transient or overstated. |
| Bracketing off OOP vs FP | Explicitly declines to invoke the separate, stronger claim that functional programming is superior to object-oriented programming, keeping the F#-vs-C# case independent of that undefended debate. |
| Conclusion | Restates that the languages share most features, but F# retains capabilities โ led by language-level dependency management โ that won't transfer to C# even as C# catches up elsewhere, and teases a future 'Worse is better' article on async/await vs. monadic composition syntax. |
Key terms
- Worse is better Worse Is Better
Seemann's series category for technologies that became popular despite superior alternatives โ here casting F# as the superior-but-unpopular option and C# as the popular winner, applied for a second time within the series. - Functional-first
Seemann's description of F#: a multi-paradigm language biased toward functional programming, as distinct from C#, a multi-paradigm language biased toward object-oriented programming โ deliberately not claiming F# is 'purely' functional. - Feature parity
The property that F#, being newer than C# and needing to interoperate with it on .NET, was by design forced to match everything C# could do โ used to explain why F#'s historical position relative to C# was never evidence of technical inferiority. - Built-in software-architecture support
Seemann's framing for F#'s default disallowance of circular dependencies: a language feature that does at compile time what architects normally achieve only through module decomposition and constant human vigilance โ elevating a language feature to an architectural control.
Propositions
- explicitC# is a great programming language, but F# is better.conf: high
- explicitF# is superior to C# along most axes of comparison.conf: highsupportsWorse is better: JSON versus XML ยท p17 โ Second installment of the same series reinforcing the general 'technical superiority does not determine adoption' claim with a new, ecosystem-controlled example (F# vs. C# within .NET) rather than merely restating it.
- explicitComparing C# and F# isolates language differences from ecosystem effects, because both share the same .NET ecosystem (build tools, hosting, ADO.NET providers), unlike cross-ecosystem comparisons such as Java versus Haskell.conf: high
- explicitAmong the three Microsoft-supported .NET languages, F# is the least popular (ranking: C#, then VB.NET, then F#).conf: high
- explicitPopularity is not determined by a single factor.conf: high
- explicitC# is more popular than F# partly because most people find imperative programming easier to learn than declarative programming.conf: high
- explicitBecause F# (2005) is newer than C# and needed to interoperate with it, F# was by design forced to match C#'s feature set โ feature parity was a design constraint imposed by interoperability, not evidence F# started as the inferior language.conf: high
- explicitF# goes beyond feature parity, offering capabilities C# lacks โ computation expressions, strong type inference, DRY variable scoping via significant whitespace, structurally-equal collection types, and language-level dependency management โ while C#'s few unique features (e.g. unsafe) are comparatively minor.conf: high
- explicitThe set of features unique to F# ('F#-C#') is larger than the set of features unique to C# ('C#-F#').conf: high
- explicitF#'s default disallowance of circular dependencies is a language feature C# is unlikely ever to get, and it gives F# built-in software-architecture support.conf: highextendsPresentation: Clean Architecture for Serverless: Business Logic You Can Take Anywhere ยท p6 โ Both propositions agree that enforcing a dependency-direction rule mechanically beats relying on developer discipline; Seemann pushes the enforcement point one level down, from build-tool/module-boundary enforcement to language-compiler enforcement of acyclicity itself.
- explicitEnforcing acyclic dependency graphs is normally done at the coarse-grained module level through human convention and vigilance โ a discipline so ubiquitous it goes unnoticed, yet unnatural: junior developers don't understand it, and seniors must constantly police it to prevent code rot.conf: high
- explicitTo a software architect, F#'s language-level enforcement of acyclic dependencies is priceless.conf: high
- explicitIf asked to help build a greenfield .NET application, the author would choose F# by default.conf: high
- explicitDifficulty finding F# programmers โ the most common objection to choosing F# โ generally ought not be a real obstacle, though regional labor-market differences may matter.conf: high
- explicitC# retains concrete, current advantages over F#: some .NET GUI technology (ASP.NET Razor) doesn't support F#; Visual Studio's IDE support for F# is weaker; and F# tooling lags for cyclomatic complexity, code coverage trend analysis, and mutation testing.conf: high
- implicitSome of C#'s IDE-tooling advantages are arguably compensations for limitations in the C# language itself, not genuine independent strengths.conf: medium
- explicitAs C# continues to catch up to F# on language features (eventually including discriminated unions), the feature gap narrows, but F# retains capabilities โ such as language-level dependency management โ that don't translate to C# even then.conf: high
- explicitThe case for F#'s superiority over C# does not require, and is deliberately kept independent of, the separate (and stronger) claim that functional programming is superior to object-oriented programming.conf: high
- implicitcontestedA language's total feature-capability superset size functions, in this argument, as a proxy for its overall superiority โ even though the author admits the underlying comparison 'can't even be measured.'conf: mediumcontradictsApplying Best Simple System for Now for Software Design ยท p3 โ Seemann treats F#'s larger, admittedly unmeasured feature superset as a mark of superiority; BSSN holds that speculative extension points and unused capability make future change harder, not easier โ the same tension the previous 'worse is better' article exhibited with XML's richer standards ecosystem, and left unaddressed here too.
- implicitPopularity among .NET languages is a poor proxy for technical merit, since the officially-supported language the author judges best (F#) is also the least popular of the three.conf: high
Arguments
- F# was, by interoperability necessity, designed to match C#'s feature set, so its historical position was never evidence of inferiority (p7).
- F# exceeds parity: its unique feature set is larger than C#'s (computation expressions, strong type inference, DRY scoping, structural equality, dependency management) (p8, p9).
- F# has one killer architectural feature โ default acyclic-dependency enforcement โ that C# is unlikely ever to match (p10).
- The .NET ecosystem is held constant across both languages, isolating the comparison to language properties alone (p3).
- A larger unique-feature superset is itself evidence of a superior language โ a value judgment applied even though the author admits the feature-space comparison 'can't even be measured' (p19).
- The reader/architect values built-in, compiler-enforced constraints over convention-based, human-enforced discipline.
- Ecosystem being fixed genuinely isolates the variables that matter to 'superiority,' when interoperability, hiring, and tooling maturity (which the article itself later treats as real costs) are arguably part of what makes a language good to use in practice, not external to it.
- If capability-superset size determines superiority, and C# is steadily catching up feature-by-feature (per the author's decade-old prior analysis), superiority should be converging over time โ yet the article asserts one feature (acyclic dependencies) that will likely never converge, without explaining why this particular feature resists the pattern that applied to every other feature C# eventually absorbed.
- From 'F# has more unique features' to 'F# is the better default choice' skips over weighing those features against the tooling and hiring costs conceded later in the same article.
- Dependency management is a major, book-worthy concern for software architects (p11).
- Module decomposition is the normal, coarse-grained, human-vigilance-dependent way teams currently enforce acyclic dependency graphs, and this discipline is widely misunderstood by juniors and constantly policed by seniors (p11).
- F# enforces the same constraint at the language level instead (p10, p12).
- Compiler-enforced constraints are categorically more valuable than convention-enforced ones โ echoing (without citing) the general software-engineering principle that violating rules should fail to compile rather than rely on review discipline.
- Acyclic dependency graphs specifically are a good proxy for 'software architecture' as a whole, when architecture concerns are considerably broader (testability, deployment topology, team/data boundaries) than dependency direction alone.
- The value of preventing circular dependencies is high enough to be called 'priceless' without any attempt to quantify or bound that value.
- If language-enforced acyclicity is priceless, teams whose architectural pain is dominated by dependency-cycle problems specifically stand to gain the most from F# โ but teams whose architecture problems lie elsewhere may not see the same 'priceless' payoff the article implies applies broadly to 'software architects' as a class.
- Each cited C# advantage (Razor/GUI support, IDE tooling, cyclomatic-complexity/coverage/mutation-testing tooling, programmer availability) is real but can be worked around or may improve over time, as F#'s feature gap with C# has (p13, p14, p15).
- F#'s tooling and ecosystem deficits are transient costs, while F#'s core language-level advantages are permanent โ asserted by analogy to feature convergence, not demonstrated for tooling specifically.
- The value of F#'s permanent language-level advantages outweighs the current, real costs of weaker tooling, weaker mutation-testing support, and a smaller hiring pool, without an explicit weighing being shown.
- Each individual C# advantage is granted and then set aside ('I should look into that in the future') rather than integrated into a net verdict โ the same aggregation-without-weighing move flagged as a weakness in the JSON-vs-XML predecessor article.
Narratives
- VHS versus Betamax, invoked via the series link back to the opening 'Worse is better' article, as the emblem for the whole series.
โ Analogy that primes the reader to accept that the popular option (C#) can be the technically inferior one โ the same framing device used in the JSON-vs-XML predecessor. - The Venn diagram of C# and F# features โ explicitly 'not to scale' and depicting something the author admits 'can't even be measured' โ with the intersection drawn larger than either set difference, and the F#-only region drawn larger than the C#-only region.
โ Visual stand-in for the feature-superset argument (supports p8, p9), doing rhetorical work the text itself flags as unquantifiable. - Junior developers not understanding dependency management, and seniors having to stay constantly vigilant to prevent code rot, as the human cost of module-level dependency discipline.
โ Grounds the abstract architecture claim (that acyclic-dependency enforcement matters) in a concrete, familiar team pain point, before arguing F# solves it at compile time (supports p10, p11, p12). - The author's own stated professional practice: 'If someone asks for my help in developing a greenfield .NET application, I would choose F# by default.'
โ Personal-practice anecdote that grounds the abstract technical argument in the author's real decision-making โ notably, unlike the JSON-vs-XML predecessor (where the author admitted he'd pragmatically choose JSON despite arguing for XML), here practice matches the stated thesis rather than diverging from it.
Questions addressed
- โ
Is F# worse than C# because it's less popular?
No โ popularity isn't determined by technical merit; F# is argued to be superior despite being the least popular of the three officially-supported .NET languages. - โ
Doesn't F#'s later arrival (2005, after C#) mean it started out as the catching-up, lesser language?
No โ F#'s obligation to match C#'s features was an interoperability design constraint, not a sign it was technically behind; it was required to reach parity by design and then exceeded it. - โญ Why did C# become more popular than F# despite F#'s claimed technical merits?
Partially: maturity/establishment and 'imperative is easier to learn than declarative' are offered as explanations, but the author explicitly declines to determine whether the learning-ease claim reflects innate cognition or first-exposure artifact, leaving the actual causal mechanism open. - โ
Should you avoid F# because you might not find F# programmers?
Generally not a real obstacle, though regional labor-market differences may matter. - โ
Does the case for F# over C# depend on functional programming being superior to object-oriented programming?
No โ deliberately bracketed off as a separate, undefended claim reserved for a future article in the series. - โญ Will C# eventually close the entire gap with F#?
C# is portrayed as steadily catching up feature-by-feature, but the article also claims at least one 'killer feature' (default acyclic-dependency enforcement) C# probably never gets โ without explaining why this specific feature would resist the convergence pattern the article otherwise assumes.
Gaps
- illogicalThe central 'F# has a larger unique-feature set, therefore it's superior' argument rests on a Venn diagram the author himself calls 'not to scale' and describing something that 'can't even be measured' โ the same unweighed-capability move flagged as a central weakness in the predecessor JSON-vs-XML article, and in direct tension with the Best Simple System for Now view that more speculative capability and optionality make systems harder, not easier, to reason about and change.
Read next: Re-read 'Applying Best Simple System for Now' (extract-applying-best-simple-system-for-now) alongside this piece to test whether 'F# has more features' is evidence of superiority or simply of more surface area, by the same standard BSSN uses against speculative extensibility. - incompleteThe claim that C#'s compiler is 'unlikely to ever' gain default acyclic-dependency enforcement is asserted, not argued โ especially given the article's own repeated framing that C# has been steadily catching up to F# on feature after feature for over a decade. No reason is given for why this particular feature would be structurally unportable (e.g. because it would be a breaking change to a decade of existing C# codebases) versus merely additive, as most of the other converged features were.
Read next: Read Seemann's earlier 'C# will eventually get all F# features, right?' (2015, linked in this article) for his own longer analysis of which F# features are and aren't likely to migrate to C#, and check whether dependency-cycle prevention was already flagged there as structurally unportable. - incompleteThe 'what C# does better' section lists real, current C# advantages (Razor/GUI support, IDE tooling, cyclomatic-complexity/coverage/mutation-testing tool support, programmer availability) but never weighs them against F#'s advantages in any explicit cost-benefit โ each point is granted and then set aside ('I do occasionally find cyclomatic complexity useful, so it'd be nice to have tool support for that') rather than integrated into the overall superiority verdict, mirroring the same aggregation-without-weighing pattern flagged in the JSON-vs-XML predecessor article.
Read next: Look for a practitioner cost-benefit account of migrating a real C# codebase to F#, weighing tooling gaps (mutation testing, coverage, IDE refactoring support) against the architectural and expressivity gains claimed here. - incompleteThe article names 'declarative programming is harder to learn than imperative programming' as a likely driver of C#'s greater popularity, but explicitly declines to investigate whether this is inherent to human cognition or an artifact of how most programmers first encounter programming โ leaving the actual mechanism behind F#'s unpopularity, and thus the practical case for or against teaching F# (or FP) first, unresolved.
Read next: Read CS-education or cognitive-science literature on whether imperative-first instruction creates a lasting bias against declarative/functional thinking, versus functional programming being intrinsically harder to learn regardless of instruction order. - incompleteThe claimed F# tooling deficit for mutation testing rests on information the author flags as possibly outdated ('last time I worked with mutation testing in .NET I found no F# support, but it looks as though Stryker has at least rudimentary F# support... I should look into that in the future') โ the article's own concession that C# has a testing-tooling advantage is not verified as current at time of writing.
Read next: Check Stryker.NET's current F# support maturity directly rather than relying on this now-superseded claim, especially since this extraction may itself now be dated.
Related documents
- Applying Best Simple System for Now for Software Design
- JEP 540 Proposed to Target JDK 28 with a Simple JSON API
- Worse is better: JSON versus XML
- AI Is Changing Software Engineering. The Fundamentals Still Matter.
- The "funhouse mirror": How AI reflects the hidden truths of your software pipeline
- Presentation: Clean Architecture for Serverless: Business Logic You Can Take Anywhere
- Article: Modular Edge Computing at Multi-Tenant SaaS Scale on Cloudflare Workers
- DoorDash Uses Envoy and Valkey for a 1.5M RPS Proxy Cache with 99.99999% Availability
- Podcast: Increasing Users' Data Agency: From BlueSky's AT Protocol to the Local-First Software Movement
- Software Design in the Agentic Age: Place Your Bets