Escaping the Gravity Well
Why the Best Software Solutions Are Often the Hardest to Defend: A reflection on architect-driven development and the hidden costs of following the "easy" path.
There's a moment in every complex software project where you face a choice that will define everything that follows. Do you reach for the familiar tool… the one with thousands of Stack Overflow answers and widespread industry adoption? Or do you pause, ignore the gravitational pull of conventional wisdom, and ask a more fundamental question: "What do the people who will inhabit this system actually need?"
I've been thinking about this choice lately, particularly after building a theater production management system that somehow avoided the state management nightmares that seem to plague most React applications. The secret wasn't superior technical knowledge or cutting-edge frameworks, it was approaching the problem as an architect rather than a builder… and being willing to climb uphill when the easier path would have been to coast downhill.
The Problem with "Hillyness"
Modern software development is full of these "down-hill" opportunities which I believe end up feeling a lot like gravity wells... easy to approach and slide into, but very difficult to get out of once they've got you. There is, I feel, too much reliance on technologies and development patterns that feel like the path of least resistance but which can ultimately trap you in local minima. React's Redux is a perfect example. When you need state management in React, Redux beckons like a well-lit highway: industry standard, extensively documented, and, perhaps most importantly, easy to defend in code reviews.
But here's the thing about gravity well tooling: it’s designed for the generic case, not the specific problems that arise from your particular use cases. Redux works beautifully for shopping carts and todo lists. It becomes a nightmare when you need real-time collaboration, complex undo/redo functionality, or domain-specific conflict resolution. By the time you realize you're in the wrong solution space, the mass of technical debt is enormous… and escape velocity possibly unreachable.
The uphill path… designing custom state architecture that perfectly fits your domain… not only requires substantially more initial effort, but also tends to be much harder to justify. "Why are we building our own state management system when Redux exists?" is a question that the DIY architect may face from both managers and peers. The justifications for doing so may sound like anything from bikeshedding (focusing on complex but ultimately inconsequential details as a form of group-safety procrastination) to just plain ego-driven over-engineering… until the custom built system has a chance to prove itself in deployment through low technical debt and effortless evolution.
Builder vs. Architect Mindset
This isn't about skill level or experience, it’s about fundamental orientation. Builders tend to think forward from tools toward solutions: "I have React and Redux, what can I build?" whereas architects think backward from inhabitants (that’s users to you and me) toward tools: "People need to collaborate on scripts in real-time, what would make that effortless?"
The builder mindset optimizes for delivery velocity and risk mitigation: use proven patterns, follow best practices, ship features. This approach works well for many projects and is likely necessary for most commercial development, but when you're solving complex, domain-specific problems, the builder mindset can lead you into the kinds of increasingly elaborate workarounds that often become necessary when you start with fundamentally mismatched tools.
The architect mindset, on the other hand, optimizes for inhabitability… how people will actually live and work within this system. This approach requires deeper domain knowledge and longer-term thinking. It's willing to invest in greater upfront complexity to achieve ultimate simplicity and to build custom solutions that map perfectly to user mental models, even when those solutions are harder to explain… and justify… at first glance.
Enter the “I Didn't Know It Wasn’t Possible" Factor
When it comes to crafting complex, systems, there’s tremendous power skill and experience, but sometimes you can also benefit from approaching problems without all of the preconceived notions about what's "possible" or “reasonable" that extensive experience would provide. When I started building our Cuebe’s queue system for collaborative script editing, I didn't know that "real-time collaboration is hard" or that "undo/redo requires complex state management." I just knew that users needed to edit scripts together and undo changes easily. It took a few hours of listening to ThePrimeagen rant about how to do proper state management to figure out that I’d “accidentally” achieved something that most developers struggle with.
It may sound silly to say it openly, but the truth is this ignorance was liberating. Instead of reaching for operational transform libraries or complex conflict resolution systems, I designed a simple operation queue where each user action became a discrete, reversible operation. The result handles collaboration, undo/redo, and audit trails naturally—not because I'm brilliant, but because I wasn't constrained by knowledge of what "couldn't" be done simply. Well, maybe I’m a little bit brilliant. We’ll wait for Primeagen to be the judge of that.
In any case, it seems the software industry has a peculiar relationship with complexity. We've created elaborate frameworks to handle problems that often don't exist, then trained developers to reach for these frameworks reflexively. Sometimes it’s worth remembering that the simplest solution is the one that you try because you just didn’t know it was supposed to be complex.
Domain-First Design
If I have to be honest, the most successful architectural decisions I've made in my now 25 years of programming didn’t come from being a master with the tools, they started with a deep understanding of the problem domain. Theater production, for example, is a complex, time-sensitive, collaborative art form with specific workflow patterns that don't map well to generic software solutions. And so approaching these problems from the perspective of someone who would ultimately be using the software I was building gave me the answers, often before I even knew the questions.
When you understand that lighting designers and sound engineers need to work simultaneously on the same script but only need to see the parts of it that are relevant to their departments, that changes need to be distributed in real-time, that pausing a show is far more complex than running it, and that simple mistakes during live performance can cause show-stopping cascade failures, suddenly the technical decisions become clear. You need operation-based editing, precise timing systems, and rock-solid reliability. Not because these are trendy technical patterns, but because the people running the show demand them.
This domain-first approach led to solutions that feel inevitable in retrospect but were non-obvious initially. When you optimize for inhabitability, you start making different tradeoffs. You're willing to write more initial code for cleaner daily workflows. You design systems that can evolve as requirements change. You prioritize debuggability and maintainability over cleverness or speed of deployment… and by doing so, you actually accelerate deployment because well-crafted code that is type-safe and follows DRY and SoC principles is far easier to extend and support than quickly produced spaghetti code can ever be.
The Long Game
In all too many cases, developers must build and deploy quickly and deal with technical debt down the road... but the architects gambit is to invest in complexity upfront for simplicity downstream. To do so requires not only a certain degree of freedom from the sorts of pressures and constraints typically imposed on working development teams but also the individual conviction to consider, build and maintain custom solutions despite peer pressure toward standard patterns. It requires a dogged determination to stick to long-term thinking in an industry all-too-obsessed with story points, sprints and deliverables.
But when it works… when maintenance becomes straightforward, users don't fight your system and new features seem to compose themselves naturally, even joyfully… you realize that what seemed like the hardest path to defend was actually the easiest path to live with.
Avoiding The Gravity Well
How do you stay out of the gravity well? It's easier than you might think. The next time you face a complex technical decision, consider the gravitational forces at play and ask yourself (and your team) if the "obvious" solution is really so obvious, or is it just well-marketed and eagerly supported by tool-makers and builders who can’t, won’t or don’t want to buck the trends? Take the time to consider whether you are solving your specific problem when you reach for a commonly accepted tool, library, or framework, or if you might be just applying standard fixes made for the generic cases those frameworks target? The answers might surprise you.
It’s not about being an iconoclast, it’s about spending time understanding the people who will inhabit your system and whose hobbies, professions and possibly even lives depend in it working properly. Think about their daily workflows, their pain points, and their mental models and keep in mind the possibility that, sometimes, the best technical solution is the one that matches human reality, even if it means building something custom, something that didn't exist before, something that's hard to defend until it proves itself indispensable.
The uphill path is harder to walk but often leads to places that are worth inhabiting.