F# in Production: What We Know After Four Years
- Ben Copeland
- 7 hours ago
- 4 min read

From FOAMfrat's Chief Software Officer, Ben Copeland:
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.
When we started building FOAMfrat Studio, F# wasn't an obvious choice. Most SaaS companies default to languages with larger talent pools and more established enterprise patterns. Even though F# often gets discussed in scientific or academic contexts, we could see how well it would hold up in a production environment - particularly one that needed to handle complex regulatory requirements and maintain velocity with a small team.
The decision wasn't without risk. The conventional wisdom says you can't hire for F#, that you'll struggle to find engineers, that you're better off with something more mainstream. But we believed the language's strengths - clarity, modeling power, type safety - would prove pragmatic rather than idealistic over the long term.
Fast forward four years. We'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.
This may come off as accomplishments to celebrate, but in fact 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.
This post is part of our FOAMfrat Engineering series, where we share the technical foundations that power the FOAMfrat platform. Stay tuned for more insights into how we're building the future of medical education technology.

