Complexity Is a Code Smell:
1 Strategy for More Productive Teams

Complexity is unavoidable in modern frontends. However, some complexity is inevitable (e.g. the frontend has to do a lot of stuff), and some is unnecessary.

Before adding complexity, we need to carefully weight the trade-offs and make sure it's necessary. Our software choices have a large impact on our team culture, and too much complexity can grind your productivity to a halt.

When frontends become too complex, it becomes harder and slower for any given developer to take a new feature from idea to production. Every change ends up crossing between multiple teams, with all the inefficiencies and slowdowns that cross-team coordination brings with it.

Complex systems make individual developers slower.

At the individual level, developers need to understand how the changes they make affect the whole system. In complex systems, that's a huge amount of context required to maintain the code. Too much complexity can cause accidental breakage or, worse, a resistance to starting tasks because the codebase is overwhelming.

Hot take: letting codebase complexity spiral out of control is how we ended up with a whole industry that feels like it can only hire senior frontend developers.

Complex systems make teams slower.

At the team level, complexity creates silos. How many people truly understand the whole system? Are there people on your team who can't take a vacation because they're the only one that knows how a critical system works?

If people don't feel confident making changes without a sign-off from the person with all the complexity locked away in their brain, the team can only move as fast as that bottleneck allows.

Complex systems make entire companies slower.

At an company level, complexity creates bureaucracy. Cross-functional systems require multiple teams to coordinate, execute, review, and deliver together.

How often does the team feel stuck because they're not sure who makes the final decision on how to move forward?

Complex systems frequently require joint ownership, which creates a huge risk of ambiguity and all the slowdowns and tension that come along with it.

Complex systems lead to frustration and low morale.

Teams that feel like they can't ship things get frustrated, feel guilty (even though the slowness is out of their control), and eventually burn out and quit.

Complex software architecture directly contributes to turnover and low engagement.

Contain complexity through decoupling.

Simplicity doesn't mean removing systems; it means adding clarity between discrete parts of the system. It's challenging to hold a large amount of complexity in our heads, but we can easily manage a long series of individual, simple tasks.

Simplicity means resisting the urge to over-engineer a system and instead building solutions that unlock long-term, whole-team productivity.

This is at the root of the Jamstack architecture: we can accomplish incredible complex tasks using simple software architecture through decoupling systems and containing complexity in manageable chunks.

Clear boundaries between systems create clarity.

If two parts of a system only communicate via a well-defined API, we no longer need to think about anything outside the boundary when we work on the code. This means each engineer needs less context to make meaningful changes — they only need to know what the API contract looks like and make sure that doesn't break.

Optimize for deletion.

A great way to create clear boundaries is to think of each system as a discrete unit that can be fully replaced without any adjacent systems requiring updates. Optimizing for deletion makes it possible to create independent, simple systems that interact with each other to accomplish complex tasks.

We use systems like this all the time: when we call a REST API, we have no idea if the underlying service is written in Java, Go, Node, or some language we've never heard of. We don't know if it's serverless or running on Kubernetes or hosted on a single machine in the CEO's closet.

What we do know is that if we hit the /post/123 endpoint, we'll get back a JSON object with post data:

"id": 123,
"title": "Complexity Is a Code Smell",
"body": "Complexity is unavoidable in modern frontends. However, some..."

We know how to send a request to the API and how to expect results to come back — that's all we need to know to use the system!

Under the hood, the backend team could replace the entire codebase and — as long as the API contract stays the same — our frontend would never need to know or care. It can happily keep sending that query for as long as the API contract remains intact.

Decoupled systems make individual developers faster.

By making the boundaries clear, individual developers need significantly less context to make changes to a given system. This prevents knowledge silos and removes the barrier to entry for early and mid-career devs (at least on the "only a senior can understand our codebase" front). This also means that it's less daunting to start and more likely that an individual can handle a whole project on their own if necessary.

Decoupled systems make teams faster.

A decoupled system allows the team to share knowledge more easily and provide more redundancy and coverage. This means everyone can actually take a vacation without leaving the team stuck.

And, even if someone is the sole owner of a system, by putting clear boundaries around it the team can work on other parts of the product and not need to worry about accidental side effects that require pulling that person off vacation to fix.

Decoupled systems make companies faster.

Decoupled systems can be built and deployed independently. Teams will still collaborate and work cross-team, but their projects won't be tightly coupled.

Firm boundaries between systems allow for easier mocking and clearer discussion around requirements. This removes ambiguity and decreasing the number of things that require joint ownership.

Complexity is unavoidable — but we can keep it under control.

There's no getting around the fact that modern frontend development requires complexity. However, if we're intentional about how complexity is introduced, we can still provide clarity and simplicity in the software we write.

Being intentional with our tech choices makes all the difference between a team that feels bogged down and demoralized by complexity — or empowered and engaged by decoupled architecture.

Unlock the Full Potential of Scalable & Production Ready Jamstack Architecture for Your Team

Be the first to hear about updates to Enterprise Jamstack course

No spam, unsubscribe any time.
Jason Lengstorf, the author of, photographed while teaching a workshop
Written byJason Lengstorf

a principal developer experience engineer at Netlify and the host of Learn With Jason, a live-streamed video show where he pairs with people in the community to learn something new in 90 minutes.

Share this article with your co-workers
Interested in learning more about Jamstack?