F# in Production: What I Know After Four Years
It’s F# Advent season again. This year, when I sat down to consider what my contribution might be, I realized many of the posts will rightfully be impressive technical demonstrations - the kind that showcase how much the language can do in the hands of skilled engineers. I’ve recently transitioned into more of a leadership and architectural role, and that shift made me reflect on what I could offer that would add value from where I’m standing now.
Before using F# in production, I’d spent years enjoying it on the side - building a ray tracer (following The Ray Tracer Challenge by Jamis Buck), solving Advent of Code, and writing small but useful internal tools like Slack bots and scripts. Those projects made it obvious to me what F# could do for a production enterprise system: the clarity, the modeling power, the lack of ceremony. Even though F# often gets discussed in scientific or academic contexts, I could see how well it would hold up in a bonafide SaaS environment.
But back then, I wasn’t the one making technology decisions. Any suggestion of F# was usually met with a good-natured “Oh, silly Uncle Ben,” or the familiar, “You can’t hire for that.” To be fair, it’s hard to convince people to bet on a language that isn’t widely adopted, and you shouldn’t let one enthusiastic senior engineer dictate a tech stack. These reactions were understandable for a technology that isn’t used by the masses (this is why we’re called F# evangelists) so there were no hard feelings.
Fast forward four years. I’ve had the privilege of making choices guided less by popular sentiment and more by the pragmatism of principles that are proven, even when they aren’t the prevailing trend. With that in mind, I’ve made this post simply a list of the things I’ve come to know as true while running a SaaS for four years on a backend written wholly in F#.
- Our team of three senior engineers simply couldn’t deliver at the level we do if F# weren’t the backbone of our system.
- None of our engineers started with F# experience - they came from JavaScript and Python. In short order, they were writing idiomatic, type-safe code because the language naturally encourages good habits. And they’ve come to genuinely enjoy working in it. “You can’t hire for F#” has simply not been our experience, regardless of what the talent pool statistics suggest.
- We refactor across 30+ serverless applications in a monorepo without hesitation because the compiler reliably tells us when something is wrong. That doesn’t mean we refactor recklessly - only that we can do it responsibly and with confidence. We don’t fear refactors, and we have never had to roll back because of them.
- Our entire reactive AI agent system runs on a single SNS topic. Discriminated unions make the message routing trivial: one union, pattern match everywhere. We kept the design true to our reactive, serverless architecture - the single topic was an intentional simplicity - and I can’t imagine building it in another language while still supporting it with a team this small.
- We serialize DUs over SNS between Lambdas. Having compile-time guarantees across distributed boundaries removes whole categories of runtime bugs.
- Vertical slice architecture fits F# well: functions instead of classes, modules to group feature endpoints, just enough structure to stay clear. No aspect of the language pulls you into unnecessary coupling or complexity.
- We also serialize our discriminated unions and send them over REST to our TypeScript and Swift applications. The rich domain model preserves intent across all platforms, reducing cognitive overhead because the same concepts are represented everywhere.
- As an accredited healthcare education platform, we work with domains that vary by state. Instead of forcing a single unified model to satisfy every requirement, we define our own internal standard with discriminated unions and map outward to each external permutation - each one modeled as its own DU. Exhaustive pattern matching ensures that every external standard is handled completely.
- Along the same lines, we submit continuing education credits to various states and institutions separately. In our serverless, event-driven architecture, each submission lives in its own function. Each one becomes a self-contained bounded context, documented by its own rich domain types and straightforward, expressive code - and kept safe from drift by F#’s very powerful compiler.
- Our scripts use the exact same domain types as production. There’s no drift between tooling and the system it supports.
- Dapper.FSharp lets us write SQL with type-checked guarantees. It’s a rare balance: relational modeling, normalized DB-first design, without an ORM’s complexity.
- Event sourcing increases in maintainability when each event is a DU case. State transitions are explicit rather than implied.
- Immutability by default removes a long list of subtle bugs.
- Adding a new accreditor or state we report to is usually adding a new DU case. Exhaustiveness checks reveal every place we need to update.
- Similarly, with our agentic AI, adding a new tool is also a matter of adding a new serverless function and a new DU case for the tool.
- With few engineers making features across the system, we learn the domain by reading types. The system teaches itself.
- Approval workflows - accredited course creation, compliance review, multi-party signoff - become manageable when represented as finite DU state machines.
- Maintaining web, iOS, and Android platforms with three engineers requires a backend that doesn’t rotate under our feet; F# helps it stay steady.
- Small, confident refactors keep technical debt from quietly accumulating. The compiler guides the restructuring.
- When regulatory requirements change, we update domain types and let the compiler show every downstream effect.
- Maintaining this system in Python or JavaScript, and yes, even C# would have been much more brittle; in F#, it stays resilient.
I could go on, but the pattern is simple: our operational capacity would not be what it is without F#. We release features steadily each week, process continuing education credits at roughly one per minute across both individual and organizational subscriptions.
These aren’t accomplishments to celebrate - they’re baseline expectations when your architecture doesn’t fight you. My hope is that this is a testimonial from the trenches. Sometimes the idealistic choice turns out to be the pragmatic one. Sometimes the industry’s narratives are little more than excuses - or, at the very least, blind spots. What I know for certain is that following principles rather than trends put us in a position to deliver at this scale with this team.