Is the FP Juice Worth the Squeeze?

Knowing I ought to spend more time seriously researching things that matter in my career and having the discipline to do so amid life’s distractions are two different things. Writing this blog is one way I am keeping my promise to myself. I have read some stuff over the last few years, written some over the last few months, and now I think it’s time to check myself.

I am not a Scala developer, and it’s been some time since I could really count myself as a Java developer. I work for a company that provides digital commerce solutions and professional services. There are things we know we should do, things our customers wish we would do faster, and things that would be exciting to do now. They are rarely the same, and they often compete against each other for attention, so we must prioritize. I think transitioning from Java to Scala would be beneficial, particularly on the product side of our business, but I have to qualify that before I can push that agenda seriously. So I am reexamining past articles from that point of view. 

New to this series?

Catch up on earlier posts to follow along with the Functional Programming Isn’t Just for Academics series:

Each post in this series explores how teams use Scala to build applications that stay clean, testable, and easy to scale.

Does adopting functional programming materially reduce enterprise risk and improve delivery predictability… enough to justify the adoption cost for a mostly-Java organization, and enough to survive contact with the messy reality of digital commerce? Documentation, QA, team dynamics, operational predictability, and (on the softer side) the maintenance of trust and value to our customers and partners are some of the more material aspects of my job.

For this context, I’m not using FP to mean monads, category theory, or a rewrite into a purely functional language. I’m using it to mean a handful of disciplines that change where complexity lives:

  • Prefer immutability so “facts” don’t drift after they’re recorded.

  • Represent absence explicitly instead of letting null smuggle ambiguity through the system.

  • Represent failures explicitly instead of letting exceptions and side-effects hide them.

  • Keep a “pure-ish” core of logic that transforms values, and isolate side effects at the edges.

  • Use closed sets of domain shapes (ADTs) where “status flags” are currently doing the work of modeling truth.

Alas, adopting FP is a maybe for us. The era of LLM-assisted SDLC is upon us, so I need to factor that in too. 

Knowledge Management & Documentation

On paper, documentation is overhead. In practice, it’s throughput. Keeping up with customer requirements in this area is non-trivial, and doing so internally is too often disregarded in favor of something more pressing. This impacts our ability to efficiently communicate with our customers and prospects. It actively thwarts our ability to onboard new team members and shift them between projects, and limits how many things we can do well in parallel. When documentation lags reality, we pay three times. We pay when customers misunderstand what the platform does. We pay when prospects can’t evaluate our offerings without meetings. We pay again every time our engineers “go spelunking,” through Jira and a dozen other systems to rediscover tribal rules that were never written down.

Functional techniques don’t magically produce documentation. But they can change the character of what must be documented by making rules explicit and local. When a domain model is honest, the code becomes a form of documentation that can’t lie. When states and their required data are distinct, constraints are visible in the type signature and enforced in construction. If failures are values, the set of ways a workflow can fail becomes visible and enumerable. If decision logic is written as pattern matching over a closed set of cases, the decision surface is concentrated rather than scattered. This argument for FP is not particularly strong on its own. I think most would agree that LLMs read code better than they write it. They do a great job explaining what something does, how it does it is somewhat less impressive, and why… well, maybe ok for small systems with copious comments, but generally it’s a fail. I wonder if Scala (and other FP languages) are easier for LLMs to read and if that impacts the quality of the docs they generate from it. It’s something I am eager to test.

Either way, the adoption cost here is primarily cultural. Teams have to agree (or be strongly persuaded) that “making rules explicit” is worth the short-term friction. Writing in Scala can help, but the bigger change is FP discipline within the development team and the LLMs they use, so your mileage may vary.

Team Balance

Team balance is my least technical constraint but the most brutal; who knows what, who is good at what, how much of what we are capable of in a slice of time versus what we plan (or need) to get done to deliver the feature or enhancement. An unbalanced team creates capability gridlock across the organization, and rebalancing it is costly.

When done well and broadly, FP techniques should help reduce the cognitive load required to understand and extend coded systems. That is, if and only if the development team practices FP well and broadly enough. The adoption cost is: training, slower initial velocity, and the risk of introducing a new language into an already busy organization. The cost of training those who have never practiced FP to master it is extremely high, not to mention risky. Hiring or renting Scala talent ranges between 30% and 70% higher than Java talent. For me, this is the biggest barrier to internal adoption. I’m not terribly comfortable speculating on it, but I expect the cost is dramatically reduced for a team of agentic developers who know everything there is to know about Scala and FP, or at least it’s no extra cost after you “hire” them. 

Quality Assurance & Standard Compliance

Our testing surface is huge, and we must ensure we test as much as what matters accurately and efficiently. We cannot test everything, and even if we could, and all of that testing were automated, we must be able to do it in a timely & predictable fashion between sprints and releases. Compromises must be made. 

I believe FP techniques reduce testing costs by shrinking the test surface in several ways. When core logic is pure-ish, you can test output as a function of input, precisely and mathematically. Prove for all x there is no y such that 0=f(x,y)... that’s powerful… especially when the compiler is the one doing the proof. Now, that doesn’t solve everything, but most mistakes really originate somewhere between, “this is what I meant when I said that, but this is what you heard before translating it to another language.” That just doesn’t happen in mathematics. If our expressions are mathematical, code may be tested structurally and completely. In a world where LLMs do all of the coding, do you really care in which language it is written? But do you care how exhaustively and reliably you can test its output against your requirements, and do you care any less if your code is not written by LLMs? Try behavior-driven design and test-driven development using Scala 3 and compare it to whatever you are using today. What are the costs of testing as a function of coverage?

Standard compliance fits here too because compliance is usually a matter of invariants: what must be present, what must be recorded. Those are exactly the kinds of truths that are cheap to violate in a loosely typed, flag-driven model and expensive to violate in production.

The adoption cost is twofold: engineers have to embrace the idioms, and the team has to invest in boundaries that keep the core honest. Scala may reduce friction in expressing these models, but the real investment is in designing the system so that correctness is a property of construction. I believe we can lower the cost of that investment by arming our current team with LLMs and canned prompts, requiring that FP principals are maximized in the solution… in Java for now, maybe Scala in the future.

Unplanned Load & Escalation Risk

This is worth calling out on its own, even though it is the other side of the coin described above. Theoretically, disciplined functional programming reduces the number of ways a system can misrepresent itself and/or obfuscate pathways that allow potential errors. That’s a big statement. I can’t tell you if that is true or to what degree. But if invalid input is quarantined at the boundary so fewer bad states enter the core; if honest internal state modeling reduces contradictions from being manufactured downstream; if typed failures lead to fewer ambiguous and/or poorly handled exceptions; and if decisions produce traces allowing incidents to be resolved (and patched) faster; then I'm interested, even if the evidence is anecdotal. I haven't been experimenting long enough to validate this for myself, but there are people I trust who run systems composed of mixed microservices, Java and Scala, who believe the Scala services are cheaper to maintain and cause fewer surprises over time. I think that is a worthwhile and very “doable” experiment for most systems composed of distributed services, like ours. 

Trust, Integration, and Data Contract Risk

Our customers integrate with ERPs, WMSs, CRMs, payment providers, shipping carriers, and bespoke internal services. Those systems are not going to become consistent just because we wish it. They may send data we don’t expect. They may change contracts without warning. They may retry or not. They may fail, or fail to be available, entirely. Or we may fail to catch them. Our customers really don’t care what causes the bug or whose fault it might be; they really don’t even care about the bug, just the impact on them and their business. Trust is what we gain when we do things well and lose when we don’t, full stop. There is a cost for adopting FP and migrating to Scala. Can I pay that cost from our trust budget?

Investment Allocation Risk

Every software organization is balancing three things: shipping value, paying down debt, and investing in capability. This is where functional programming is easy to both oversell and undervalue. The cost of shipping MVPs that calcify into permanent constraints vs. the cost of “over-engineering” that delays value. What is the cost of doing neither well: always firefighting, never paying debt, never investing, always behind. This is where “technical debt” stops being a metaphor and becomes an operating model. 

FP won’t stop us from shipping MVPs. It won’t prevent us from over-engineering. Those are product and leadership issues. What I believe it will do is reduce the long-term interest rate on complexity in the most stateful, most rule-heavy parts of our platform. It should make certain kinds of refactors less terrifying, because changes become explicit and guided rather than implicit and scattered. I think it will improve the ROI of both debt paydown and innovation, because less of our investment is lost to regression and tribal knowledge. 

The whole FP paradigm appeals to me for all of these reasons, among others. Its mathematical nature may be a barrier to human adoption but a facilitator for leveraging machine intelligence. There are real cost-benefit ratios that should ultimately help you decide if the juice is worth the squeeze in your organization; I think it is in mine. I also think the marginal cost of adopting FP is decreasing as LLM and similar adoption increases so if it isn't now, it may be tomorrow. It’s hard to quantify and make that argument to my CFO, but I believe it’s worth trying, and I plan to continue.

This is Part 11 in an ongoing series. If you found this useful, Part 10 breaks down how to model your data so impossible states can't be constructed in the first place. Read "When MVPs Grow Teeth"

 
Next
Next

How to Choose the Right Offshore Software Development Company