Look at the picture below. Look at how many more dots are on the right than on the left. Then look at how many more lines are on the right than on the left.

As you add pieces to an interconnected system, it gets more complex, and fast.

Conventional wisdom in the tech industry is that you manage complexity like weeds in a garden. You put aside a fixed amount of time (let’s say a couple days) at a fixed interval (let’s say every month) to do some documentation here, some code refactoring there.

Many don’t do that. Many want to, but are so busy churning out new features that they end up treating their product’s complexity the way one treats Boo, that Mario villain who slyly follows you while your back is turned and stops moving when you look at him. In the real world, many settle for “build really fast and sometimes acknowledge that complexity exists.”

Deep down, in places they don’t talk about at parties, every engineer knows these approaches are broken.

Software complexity is like a hydra, a monster from Greek myth that grows two new heads each time you cut one off. The more you build, the more it grows.

Making your team build faster to make up for the slowdowns that complexity can bring is the opposite of what you should do, and exactly what many teams do. In fact, the bigger the gap between the pace at which you build and the pace at which you address your complexity, the more trouble you’re creating for your future self.

The hydra’s heads generally attack from three places:

The languages and frameworks you use

The programming language I use the most is called TypeScript, which was built on a better-known language called JavaScript.

TypeScript is now both a “frontend” and “backend” language. This means, in a nutshell, that you can use it not just on devices to make app screens and webpages show up, but also on the faraway computers, called the servers, that handle the app’s data and send the right data to each user’s device.

Being able to use one language for most of your job is nice. Being able to use one language for all of your job is pretty much impossible. The vast majority of web developers need to be multilingual by default.

To be a frontend engineer, you can’t just know some flavor of JavaScript. You need HTML and CSS. HTML lets you decide what goes where — a heading here, an image there, a block of text yonder. CSS is how you style it with colors and fonts and such. JavaScript adds the logic and data. When I click on something, what happens? When I ask the server for some data and it arrives, how does it get handed off to the HTML?

Most backend engineers know more than one language too, in large part because so many more exist, and because they have such different strengths. Many were around long before the web was, like C++ and Java. Some are newer but still largely meant for other disciplines, like Python for data science. And completely separate from whichever of these language(s) you use, there are also the ones you specifically need for database querying, be it SQL or MongoDB or whatever else.

Especially for those languages that weren’t built for backend web development at all (and for some that were), tools called “frameworks” have sprung up to make backend web development easier. There’s Django and Flask for Python, NestJS and Express for Typescript, Rails for Ruby, and a lot more.

A lot of impressive frontend frameworks have sprung up, too, that make Javascript easier to use with HTML and CSS, and more generally, that make it easier to build big web apps. React, Vue, Angular, and Svelte are a few big ones. They can have major philosophical differences — React, for example, favors a “top down” approach where data is handled and dispersed from some central source, while Vue is friendlier to the idea of user actions and events causing data to “bubble up” in a tree of components.

Going further, each framework has an entire ecosystem around it. React, for example, has a spinoff called Preact that’s smaller and faster, and a related framework called React Native for building mobile apps. They also tend to have constellations of specific third-party libraries that work especially well with that framework.

Which brings us to…

The libraries and apps you use

This section is going to be a lot of lists. This is on purpose. It’s a shock-and-awe tactic.

There are a ton of things a codebase might (and should) want to “outsource” to an open-source library, if that library has already done the job well. Here’s a small subset of examples from the Javascript world alone:

When a library for something is particularly hard to build, you often have to integrate with another service entirely, sometimes paying for it, and usually getting some kind of web dashboard on the side. These also tend to cover pretty universal needs, like:

And then there are libraries from both categories — open-source and paid — that are much more domain-specific, depending on the needs of your company, like:

And then, of course, there are tools that don’t require a library that works with your codebase, but still involve adding some kind of product to your workflow that you have to get familiar with:

And finally, you have your apps. Every day, when I open my computer, there are at least ten apps from my launch bar that get opened without a second thought. I have to use them all pretty regularly. There’s:

As numerous as these tools are, they’re almost always well-documented — your end users are the developers using them, after all.

Inside companies, there’s less incentive for this. The code itself, and most of the nooks and crannies of how your product works, aren’t what you’re showing to the user. It’s the “happy path,” the core experience of your product. But the code itself, and its many edge cases, take up a huge percentage of your developers’ time.

This discrepancy leads to whole new cans of worms. The next section is the big kahuna — it produces the most hydra heads by far.

The product you’re building

If you’ve read the book “The Mythical Man Month” by Fred Brooks, you already have an idea of the gist of this section. (If you haven’t, and if you work in a trade where teams of people do knowledge work, give it a read.)

The premise, which I’ve seen validated too many times, is this: when you double the number of people working on a software project, you do not cut the time it takes to finish that project in half. Dream on.

With each person you add, there’s extra communication you need to do regarding how the system works (or should work), and extra planning regarding when things should get worked on relative to each other and by whom. This takes time.

Beyond writing the code that builds the thing, you need the following pieces in place, at a minimum:

Virtually no one overestimates how much time it will save to be methodical about these. On the contrary, most of us underestimate that all the time.

Is the fault in our tools?

Clearly there’s a dizzying array of tools and processes you now need to understand in order to make software that’s creative and profitable and better than alternatives. This, as mentioned at the beginning, is where real complexity comes from.

Some of this increasing complexity, it’s fair to say, is out of teams’ control. Software is eating the world, and that complexity has to live somewhere.

Many software ecosystems, especially Javascript, are understood by those within them to be bloated and crowded, if incredibly versatile. When presented with the statement “building Javascript apps is overly complex right now,” an astonishingly low percentage of developers — 34.4% — say they disagree. (Note that this percentage is also among people who like JavaScript enough to take a survey about it.)

That number is improving modestly over time, thanks to awesome work by awesome people building awesome tools. You might say “But wait! You said those tools can create even more opportunities for complexity!”

They can. But not because of the tools themselves. It’s our understanding of them and of how we use them.

The tools are incredible. Through them, a staggering amount of complexity has already been wrangled for us. Others have packaged up so much math and logic and physics and electrical engineering and computer science over the centuries, and especially over the last few decades, that you can now literally type some characters into some text files and pop out apps like magic.

These tools are not hammers or screwdrivers, and it’s a mistake to think we can just cobble them together in increasingly complex ways and leave it at that.

Brains process information like “how to ride a bike” or “how to build a chair” (this is called “implicit memory”) fundamentally differently from information like “the capital of North Dakota” or “the name of that script you use to fill your database with dummy data” (“explicit memory”).

Every product, app, and library you use as a developer is a little language unto itself, with new challenges for your explicit memory. New vocabularies of jargon. New lists of functions to remember (or at least remember that they exist). The things in each of these categories are also typically nested — built on top of other tools — and they evolve and get updated frequently in ways that hammers don’t.

The constant recall and lookup of these changing facts means there’s no real upper limit to the demands that they can impose on your brain’s cognitive load. Chronically bumping up against the limits of your cognitive load is a powerful source of stress, and a lot of companies have very smart people fine-tuning the amount you experience while using their apps to make sure this doesn’t happen.

Teams can do this for themselves, too. Much still rides on the way you manage, or fail to manage, the complexity that you build upon and create for yourself. What’s within a team’s control, then, is not the nature of software tools, but the nature of the product that it’s building, and the ways in which you marshal different resources to build it.

The solutions

Solutions? You still want solutions? Ugh. Come back later. Tired from explaining the problems.

Okay, obviously not. But it’s also tempting to skip solutions because they’re so infuriatingly, maddeningly simple. Most people could throw out uninformed suggestions that, if actually enacted, would beat the status quo.

Anyway, fine. Here.

That’s it. It’s really just up to teams whether they want to do it or not.

The “increasing” part is annoying, but important. A fixed amount won’t cut it. As you build, you need to allocate more time and resources to reducing what complexity you can, and documenting what you can’t.

Remember that the biggest productivity gains come from laying groundwork for others to walk on, whether that’s documenting complicated code, streamlining the ways developers test it, or anything in between. That’s why people build useful software in the first place.

Too many teams understand this perfectly when it comes to selling easy-to-use software to their customers, and not at all when it comes to making their own product easier, or sometimes even possible, for their own team members to work on effectively.

Many companies want to have their cake and eat it too. They want to build on top of, and make money from, the many ways in which others have wrangled complexity for them. They very rarely want to worry about wrangling it themselves. They want to build, build, build. They want to be feature factories. (Read this thread for a great deep dive into why that’s not the only approach startups should consider.)

Again, the more of a feature factory you are, and the faster your complexity grows without being managed, the more damaging it becomes. And the harder it is to stay in front of it. And the less feasible it is, after a while, to even get in front of it.

It’s a sneaky, vicious cycle, and the concrete symptoms are ugly. Projects start blowing through their time estimates. Unnecessary work is done. Necessary work is redone. Team communication suffers. Employees burn out.

Improving each individual employee’s ability to handle complexity, even if that’s a good goal to have, is not a substitute for the real solutions. Your product’s internal complexity will still catch up to people’s cognitive load, even if they’re very smart.

You have to strengthen your team’s collective ability to wrangle any part of the product’s complexity. For a product to be worked on by a team, where people come and go, and frequently communicate with each other, you have to make the product itself, and the many ways people work on it, inherently less complex and easier to understand.

The people who can do this legwork — document, understand, and wrangle complexity — are out there. Many people, as crazy as it sounds, actually thrive on it.

There are some roles for it already, with job titles like “Technical Product Manager,” and luckily they’re growing more common, but they’re not common enough. Instead, practically all of the complexity I introduced at each level of this post is currently distributed across a mishmash of engineers, product managers, and designers, rather than being owned in full-time roles.

The collective need for changing this fact can manifest itself in many places — retrospective meetings, Slack discussions, 1:1s with managers, and offhand comments during daily standups, to name a few.

When it does, don’t let it be swept under the rug. Don’t let people make excuses for it or kick the can down the road. Take note and take action. If you do so, you won’t just keep the creeping vines of complexity at bay. You’ll build better features, and you’ll build them more quickly, and you’ll build them with a much, much happier team.