Complexity in Software: Find Your Pocket

Description: Complexity in software is not all equal. Focus on simplifying the parts that will be changed often and by many. Take on some complexity to make those extensions simpler.


In music, especially jazz or funk, musicians talk about "the pocket"—a space where rhythm and feel lock in. It's not necessarily simple, but it’s tight. The rhythm section lays down something reliable, steady, and rich in groove. That groove creates a pocket—a place where improvisation happens smoothly, predictably, and musically.

Software is no different.

Not all complexity is equal. And not all complexity is bad. In fact, the best systems—the ones that feel intuitive to use and evolve gracefully over time—are often built on a bedrock of deep, carefully contained complexity. They "groove" because they push the mess down into stable layers and leave clean, expressive surfaces for change. They create a pocket where future development can flow.

As John Ousterhout argues in A Philosophy of Software Design, complexity is the enemy of software, but it must be managed—not eliminated. The key is reducing accidental complexity and concentrating essential complexity in places that rarely change.


Complexity Isn’t the Enemy—Misplaced Complexity Is

A common trap in software design is trying to make everything simple. But oversimplifying the internals to keep the codebase "clean" often leads to cluttered interfaces or fragile extension points.

Great systems don’t avoid complexity—they manage it. They move it out of the way.

They bury intricate logic behind stable contracts. That complexity might involve dependency juggling, caching, or parsing—but if it’s contained and well-tested, it can enable a beautifully simple developer experience at the surface.

This reflects Ousterhout’s concept of deep modules: modules that hide significant complexity behind a small, simple interface. These modules lower the cognitive load for the majority of developers and make the rest of the system easier to change.


The Rhythm Section and the Pocket

Think of your system like a band:

  • The rhythm section—the complex, low-level infrastructure—is written once, tuned carefully, and rarely changed. It anchors the system.
  • The pocket—the interfaces and extension points—is where most of the future development happens. It needs to feel smooth, consistent, and expressive.

When you build systems this way, future developers (or future-you) can drop into the codebase and groove with it. Adding features or debugging becomes natural and rhythmic—not chaotic.


Design for the Groove

To keep your system in a groove, you may need to:

  • Hide complexity behind clear abstractions. Internal machinery can be complex if it makes the higher-level interface easy to use.
  • Isolate edge cases. Don’t let rare conditions muddy up the main flow. Push them into lower layers or helpers.
  • Optimize for change. Invest in making high-traffic areas of the code easy to read, modify, and extend—even if it means writing more complex plumbing underneath.

Ousterhout emphasizes information hiding as a strategy for fighting complexity. The idea: isolate design decisions and implementation details behind simple interfaces, so most of the system can operate without knowing about them.


Real-World Examples: DSLs and Reverse Proxies

Let’s look at two examples—one in library design and one in software architecture—that show how it’s worth introducing complexity early to make future changes clean and easy.

🧱 Library Design: Embedding a DSL

Suppose you're building a library for configuring a rules engine. One option is to expose a verbose, imperative API with many method calls and branching logic. Another is to build a domain-specific language (DSL) that lets developers express rules declaratively.

Creating the DSL adds up-front complexity:

  • Designing grammar and syntax
  • Building a parser or interpreter
  • Handling validation, typing, and evaluation

But once it’s in place, using it becomes elegant and fast.

This DSL becomes the pocket—an interface where developers work day-to-day.

The parsing and execution logic becomes the rhythm section: deep, complex, and stable.

You’ve traded complexity in the internals for simplicity where it matters most. You’ve made your own life—or your users' lives—easier.


🌐 Architecture Design: Reverse Proxy as Groove Keeper

In a service-oriented architecture, routing, versioning, authentication, and observability can pollute each service with duplicated infrastructure logic.

Instead, you can introduce a reverse proxy layer—a centralized gateway like Envoy or NGINX—to absorb this cross-cutting complexity:

  • Route requests to services based on URL paths, headers, or versions
  • Handle TLS termination and auth
  • Add observability and retries

This reverse proxy might involve a complex configuration, scripts, and deployment logic. But once in place, it keeps individual services clean. They just expose simple HTTP endpoints.

Here, the reverse proxy is the rhythm section: complex but static.

The services live in the pocket, free to evolve with clarity.


Ask Yourself

Before making a design decision, ask:

  • Where will changes happen most?
  • What needs to be easy to understand and modify?
  • Can I introduce complexity elsewhere to simplify these hotspots?

If you move the heavy lifting into well-contained rhythm sections, the rest of the system will play smoothly. Future changes won’t be battles. They’ll be riffs in a familiar groove.


Conclusion: Find Your Pocket

Great software, like great music, relies on structure and feel. You don't avoid complexity. You compose with it.

Put your deep, hard-to-change complexity in the foundation—like a solid rhythm section—and shape your abstractions so that future changes sit naturally in the pocket.

When you do, development stops feeling like hacking through a jungle. It starts to feel like playing in a band that’s locked in.