C# is a great programming language, but F# is better.

This article is part of a series called Worse is better, in which I muse on technologies and techniques that became popular despite superior alternatives. Think VHS versus Betamax.

In this article, I will argue that F# is superior to C# along most axes of comparison.

Popularity ranking #

It's easier to compare C# and F# than it is to compare, say, Java to Haskell. When comparing most languages, you'll realistically need to take into account not only syntax and expressibility, but also the entire surrounding ecosystem: Build tools, hosting options, frameworks and libraries, knowledge bases, etc. While I admit that I haven't rigorously looked into this, I'd be surprised if no supported Oracle data provider exists for Java, but is there one for Haskell? Even if one exists, how good is it? Is it supported? By whom?

It's easier to compare C# and F# because many such variables are fixed and the same. Both languages run on .NET. If you want to host a web service, you'll have the choice of the same web servers. If you want to connect to an Oracle database, you can use the same ADO.NET provider. Thus, when comparing C# with F#, it's possible to ignore most of the surrounding ecosystem, since it remains constant.

Three .NET languages enjoy support from Microsoft. While it can, in general, be difficult to compare the popularity of programming languages, there's little doubt that the three languages rank in popularity as follows:

  1. C#
  2. Visual Basic .NET
  3. F#

Indeed, F# is not even the second-most popular .NET language; it's the least popular. Could it really be the best of the bunch?

Feature parity and beyond #

If there's a lesson to be learned from this article series, it's that popularity isn't determined by a single factor. In the previous article, I argued that XML is superior to JSON even though JSON is seen as the more modern, lightweight alternative. Ironically, C# is more popular partially because it's the more established, mature alternative.

In all fairness, I also think that C# is more popular than F# because, for some reason, most people find imperative programming easier to learn than declarative programming. I haven't yet been able to determine whether this is something inherent in human cognition, or whether it's an artefact of how most people have their first encounter with programming. I could write much more about this, but it would be a digression.

Although F# is from 2005, it's newer than C#. This means that by design it has always had to match everything that C# could do. Otherwise, the two languages wouldn't be able to interoperate.

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. I've already been over this ground ten years ago, so I will not repeat the entire list of what F# does better. Indeed, as C# catches up to F#, the gap becomes narrower.

Even so, I think one may reasonably illustrate the situation in a Venn diagram:

Venn diagram of C# and F#. The intersection is larger than any of the set differences, but the F# relative complement is greater than the C# relative complement.

As is usually the case when I draw a Venn diagram, it's not to scale, and in fact, what it depicts can't even be measured. Rather, I'm trying to convey a conceptual relationship. In this diagram, each set represents a set of features. The intersection is larger than any of the set differences, indicating that most features are shared by both languages. In fact, had the diagram been 'to scale', the intersection would be so much greater than the relative complements that the latter would be hardly visible. That is, if we assume that it somehow makes sense to talk about features as something measurable.

The point of the skewed figure is that the set difference F#-C# is 'greater than' C#-F#. In the latter, we find 'small' features such as unsafe as well as some tools that I discuss below. In the larger F#-C# set difference, we not only find some of those language features that C# is soon to get, but also computation expressions, strong type inference, DRY variable scoping, collection types with structural equality, and language-level dependency management.

A programming language for software architects #

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. Thick books have been written on dependency management. A major reason software architects decompose applications into modules is to enforce dependency graphs: At the module level, cyclic dependencies are disallowed. If library A depends on library B, you can't (easily) reference library A from library B.

This style of architecture is so ubiquitous that we don't even notice it, but if you think about it, it's quite unnatural. Junior developers don't understand dependency management, and seniors have to be constantly vigilant to prevent code rot. Decomposing a code base into modules is a coarse-grained attempt at keeping chaos at bay.

F# enables this kind of control at the language level. To a software architect, this feature alone is priceless.

What C# does better #

If someone asks for my help in developing a greenfield .NET application, I would choose F# by default. That said, I'm aware of forces that push toward C#. The most common concern is about being able to find F# programmers. In general, that ought not be a problem, although I'm aware that regional differences may have an effect.

Besides the question of finding qualified programmers, C# has an edge over F# in various smaller areas.

First, and perhaps easiest to spot, a few .NET GUI technologies assume C#: ASP.NET Razor assumes C# or VB.NET, but as far as I know, doesn't work with F#. This is, in a way, a genuine limitation, but can be overcome in various ways: Have a thin UI layer in C#, or use an alternative rendering framework that works with F#.

Second, many C# developers will be underwhelmed by the IDE support for F#. A few of Visual Studio's code editing features I do genuinely miss for F#, but one could also make the argument that some IDE features for C# are only workarounds for limitations of the language.

Next, there are a few other pieces that I miss when working in F#. One is that last time I checked, there's no easy way to calculate cyclomatic complexity for F# code bases. While this is not a big deal, I do occasionally find cyclomatic complexity useful, so it'd be nice to have tool support for that.

The same goes for code coverage, and while code coverage is a useless target measure, it may still be occasionally useful for trend analysis and sanity checks.

Perhaps the most uncomfortable lacuna is the state of mutation testing for F#. 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.

To flip a previous argument on its head: Just as C# is catching up to F# when it comes to most language features, it's conceivable that the tool-support situation could improve for F#.

Object-orientation and functional programming #

Notice that none of the above arguments hinge on comparing object-oriented programming (OOP) with functional programming (FP). F# is a 'functional-first' language, meaning that it's really a multi-paradigmatic language biased toward FP. Conversely, C# is a multi-paradigmatic language heavily biased toward OOP.

I could, and probably will, write an entire article in this worse-is-better series arguing that FP is superior to OOP, but I don't think that I need to make that argument here in order to argue that F# is superior to C#.

Conclusion #

C# and F# share most language features. What you can do in C# you can do in F#, with a few inconsequential exceptions. Even when C# gets discriminated unions, on the other hand, there are things you can do in F# that don't translate to C#. One is that the language offers built-in dependency management, effectively letting the compiler help you to avoid spaghetti code. And then there are all the little things.

What I didn't discuss at all is the expressivity offered by computation expressions. I kept it out of this article because I think it deserves an article of its own.

Next: Worse is better: async/await versus syntactic sugar for monadic composition.



Wish to comment?

You can add a comment to this post by sending me a pull request. Alternatively, you can discuss this post on Twitter or somewhere else with a permalink. Ping me with the link, and I may respond.

Published

Tuesday, 08 September 2026 06:26:00 UTC

Tags



"Our team wholeheartedly endorses Mark. His expert service provides tremendous value."
Hire me!
Published: Tuesday, 08 September 2026 06:26:00 UTC