Introduction
Designing IT solutions is a challenging task. Not only do you need to take several factors into consideration, but you also need to understand the lifecycle of what you’re designing. Questions will inevitably pop into your head, like:
- How long does this solution need to last?
- Is it going to be perpetual?
- How do we handle high availability and disaster recovery?
- When will I know it’s complete?
That last point—when will I know it’s complete?—can be ambiguous. Does it mean the solution is fully fleshed out and working at the desired end state? Or does it mean it’s ready once a Minimum Viable Product (MVP) has been constructed?
If the solution is too simple, it may lack features or fail to scale. But if it’s too complex—over-engineered—it might introduce unexpected bugs and hurt not just our credibility, but also the brand we represent, especially in a corporate context. And beyond the bugs, what about the cost of building that solution? Our time? Money? Resources? All potentially wasted on something that doesn’t deliver a meaningful return on investment.
It’s a conundrum, no doubt. But there are frameworks and considerations that help you draw the line and confidently mark a solution as done based on criteria.
Understanding a solution
One of the first fundamental skills that you need to understand as a Consultant or anyone in a role that involves designing or architecting solutions—is the ability to understand the what and the why:
- What are we doing?
- What are we building?
- What is it?
- What will it be?
Once we understand that what, we move on to the why:
- Why are we doing this?
- Why are we building this?
- Why do we need to include x, y and z?
- Why is this or that a good idea?
All of those what and why questions can be summarised in a single word: requirements. Requirements are the driving force behind the rationale. They help shape what the solution will look like and give context to the decisions made along the way. They set the direction—establishing goals, coordinating efforts across teams, and managing expectations for stakeholders. From a commercial perspective, requirements are assessed and translated into elements such as definition of done and/or acceptance criteria. These elements are then used in commercial agreements like a Statement of Work (SoW) to provide clear and concise deliverables for all parties involved.
When we understand the requirements of what we’re setting out to do, we have structure. That structure becomes the foundation for a plan—where ideas take shape and strategy begins to form. As you become more proficient in your craft—whether it’s software, infrastructure, or something else—you’ll begin to recognise patterns. You’ll recall what worked well and what didn’t in previous experiences. Armed with that insight, you’ll feel more confident when similar situations arise, and better equipped to act early—before issues have a chance to fester.
The definition of complete
Now comes the tricky part of this process: What defines a solution as complete?
- Is it something that we can measure?
- Does it relate to its run cost?
- Can it handle the load we need it to?
- Will it accumulate technical debt?
We do have frameworks and tools to help answer these questions. In the previous section on understanding a solution, we mentioned the definition of done and acceptance criteria. Depending on how mature the environment and organisation are, these could be loosely defined or meticulously detailed. By using these elements, we can clearly describe what a “complete” solution looks like—aligned with what we’re trying to achieve now.
Even though we use the term “complete”, it doesn’t always mean the end state. More often, it marks a meaningful checkpoint. The future state will continue to evolve—just as the broader IT landscape constantly shifts.
A definition of done may reflect a simple, even traditional, solution. Solutions don’t need to be extravagant if they fulfill the basic requirements. There’s nothing wrong with doing things the old-fashioned way. Take mainframes, for example. You might think of them as legacy tech from the 1960s—but would it surprise you to know that many major corporations across sectors like finance, government, and healthcare still rely on mainframes today to process transactions? Mainframes continue to hold relevance because of their unique capabilities and the critical roles they play in these industries. Could they be replaced by modern stacks like serverless architectures? Probably. Is it a simple task? Absolutely not.
The mainframe example highlights two key principles:
- If it isn’t broken, don’t fix it — or in other words, if it works, don’t change it.
- Old technology doesn’t automatically mean obsolete — in fact, as older generations retire, we might even see a kind of renaissance for legacy technologies like mainframes.
At one point in history, mainframes were considered the north star of computing. Fast forward to today, and our modern-day north stars might be serverless platforms that run only your code—or perhaps AI-powered observability systems that proactively detect anomalies.
Technology doesn’t stand still. It evolves rapidly. Today, we’re seeing explosive growth in AI, particularly with agentic models that leverage Model Context Protocol (MCP) servers.
So when it comes to defining what makes a solution “complete,” remember these quick tidbits:
- It’s fit for purpose. It doesn’t need to last forever—just for today, tomorrow, and the near future. Solutions will always evolve.
- It will scale within reason. If you’re expecting millions of requests per second, architect accordingly. But if you’re only handling a few per second, you don’t need a massive data platform with all the bells and whistles that might cost thousands per month to run.
- It will work. This is the core of it all. “Working” might mean different things to different people, but ultimately, the solution behaves as intended and delivers on its purpose.
Over-engineering and its ramifications
So what happens when we go to the other extreme? Rather than stopping at our definition of done, we push way past it and engineer the hell out of a solution. It’s amazing! It’s something to behold!
…Or is it?
How far down the rabbit hole did you have to go to get there? And ultimately—what was the cost?
Over-engineering is contextual. When it does happen, it can manifest in different forms depending on the solution. One of the worst forms is trying to account for every possible future use case—a “just in case” mindset. Building a solution to handle everything that might happen in the future can stifle progress. It can halt or delay the project until there is mutual resolution amongst stakeholders, all while delaying progress to the end goal. In situations like this, I often use the saying: “the juice isn’t worth the squeeze.” In other words, to achieve X, we need to do Y—and Y just isn’t worth the investment of our time, resources, or mental energy.
Another form of over-engineering is adding unnecessary complexity. The maturity of a solution must be factored in when architecting. Introducing advanced complexity to a system that’s not ready for it can be wasteful, or even damaging. Take a startup, for example. Let’s say their core revenue-driving product is a piece of software. Right now, that software runs on a single PC under someone’s desk. Is it elegant? No. Does it work for now? Actually, yes—in a very non-ideal way.
If that same startup tries to launch into a cutting-edge, serverless cloud architecture with auto-scaling, CI/CD pipelines, and bleeding-edge automation—but has no current need for it—that effort would be a catastrophe. The resources required to build and support such a setup would likely never see a return, especially if the startup is still finding product-market fit or struggling with cash flow. In fact, they might run out of steam entirely and fold before realising their goals.
A solution shouldn’t be too top-heavy at the start. By that, I mean: don’t add features or components that don’t bring actual value in the near term. Later—once things stabilize and there’s a clear path forward—it might make sense to revisit and layer in those “nice to have” features. But early on, they often just weigh you down.
Return on Investment (ROI) and building momentum
After we get through the rigorous process of vetting the solution using the mechanisms already mentioned, we need to shift into a mindset that focuses on vision—the long-term outlook for the solution. As part of this vision, we must consider how we extract value over time and achieve a solid return on investment (ROI). But ROI isn’t always about money. There are other forms of currency we “spend” to make a solution functional and successful. These include:
- Time
- Effort
- On-going support
- Business integration
Let’s start with time. It’s one of our most precious resources—yet most of us (myself included) never seem to have enough of it. In the digital age, it’s easy to jump from watching Judge Judy on YouTube to checking sports results, reading the latest tariff news, playing a video game, or falling into a social media rabbit hole. There’s always something waiting to eat into our time.
So, when we invest time into a solution, it should give us time back. The return should come in the form of efficiency or automation. This is especially true when adopting DevOps methodologies, where the whole premise is to automate as much of your role as possible—freeing you up for higher-value tasks.
Another often-overlooked factor is the longevity of the solution and what ongoing support and maintenance will look like. Simple systems might be seen as ancient by today’s standards—but they can also be the easiest to maintain, debug, and enhance. A “good enough” solution today often beats a “perfect” one six months from now.
A solution that is well-vetted, tested, iterated upon, intuitive, and clearly defined delivers strong ROI—not just for development teams, but also for project managers, business stakeholders, and end users. When more people begin interacting with the solution, momentum builds. Feedback loops form, giving valuable insight into what to improve or prioritise next.
This is how momentum turns into progress. This is how “good enough” evolves into great.
Conclusion
The next time you’re designing a solution—or playing a role in shaping one—consider what’s practical to get it working now, versus what’s “perfect” but loaded with nice-to-have features that may not add much value.
Don’t get stuck in the weeds and chase those rabbits too deep into those holes! While it can be fun to experiment and theory-craft, a good engineer or architect knows when they’ve gone deep enough to achieve a solid result for a reasonable amount of effort. In the end, completing a solution up until a point isn’t settling. It’s being strategic.
Cover image by dix sept on Unsplash