Why Your Tech Stack is an Implementation Detail
How do you actually start a project?
When you sit down with a blank canvas and high-level requirements, what is the very first thing you think about? If you’re tasked with designing a system, where does your mind go to make those initial, high-level decisions?
If you are like most engineers, your brain immediately starts scrolling through a catalog of technologies.
Should we use Go or Node? Postgres or Mongo? Is it time to spin up Kafka, or can we get away with Redis queues? Do we go microservices out of the gate?
We’ve been conditioned to think this way because the industry talks about engineering almost exclusively as a collection of tools.
But when you let the toolkit drive the design, you are letting external implementations dictate your architecture before you’ve even defined the constraints inherent in your problem.
The reality is that your design and your architecture are going to evolve. They have to evolve and iterate as requirements shift and realities change. You cannot predict every bottleneck or feature twist on day one. However, if your initial, fundamental primitives are chosen based on a very well-defined problem, you give the system a structural integrity that keeps your head out of the fire when things inevitably change.
You don’t build a system out of tools; you derive your tools from the essential characteristics of the problem.
And this isn’t just a narrow conversation about scale. Scale is the industry’s favorite buzzword, but performance under heavy load is only one variable. A system can collapse just as quickly under the weight of its own operational complexity, developer friction, unmaintainable code, or a complete mismatch with what the business actually needs to survive.
To anchor a system properly, your initial decision-making process has to pass through a strict hierarchy of intent: you define the What and the Why before you can ever accurately determine the How.
Most architectural quagmires, whether they are performance bottlenecks or teams moving at a snail’s pace don’t happen because someone picked a bad database. Often times its because a team jumped straight to the mechanisms.
Instead, you have to look at the brutal operational constraints of the domain itself. Is this an ingestion pipeline where dropping a single message means lost revenue, or is it okay if data is slightly stale? Is the execution pattern write-heavy and spikey, or read-heavy with strict low-latency requirements? Is the team composed of junior developers who need a low cognitive load, or systems engineers who can manage manual resource tuning?
Once you lock down those constraints, you still don’t pick a tool. You define the required system characteristics. Instead of saying, “We need Kafka,” the initial decision should be, “We need an immutable, append-only log that allows independent, decoupled consumers to process and replay data at their own pace.” Once that characteristic is established, evaluating the actual mechanism—whether it’s Kafka, Redpanda, or a simple cloud queue—becomes a straightforward checklist exercise.
The tool is just the detail that satisfies the primitive.
But designing a system on paper is only half the battle. Once you establish the characteristics your system needs to possess, your next critical decision isn’t technical at all it’s now organizational.
There is an inescapable rule of engineering known as
Conway’s Law - organizations design systems that mirror their own communication structures. If you have three separate, siloed teams working on a project, you will shatter the system into three separate, tightly coupled services that require massive coordination just to deploy a single feature.
Knowing this, a seasoned architect weaponizes the Inverse Conway Maneuver. You don’t structure your team to fit a corporate chart; you structure your team to match the desired architecture of the software. If you are looking for rapid feature velocity and tight domain alignment, you put a cross-functional, tightly-knit team together. Their daily high-bandwidth communication will naturally produce a clean, modular monolith. If you are building an ultra-scalable platform with strict boundaries between domains, you separate the engineering groups into completely autonomous units that only communicate via explicit, iron-clad API contracts. Because the human communication is decoupled, the code they write will naturally remain decoupled. You set up the human process first, and the architecture will naturally follow the path of least resistance.
When you finally arrive at the bottom of the pyramid and it’s time to choose languages, frameworks, and infrastructure, you look past the hype and evaluate them strictly by how they manage resources, execution, and visibility.
Programming languages, for instance, are just memory and concurrency models. Stop choosing a language because it’s popular on GitHub; evaluate it based on how it handles runtime execution. If a system demands absolute predictability, deterministic memory layout, and zero garbage collection pauses, you look toward low-level primitives like C or Zig, trading development speed for ultimate hardware control. If the problem is managing tens of thousands of concurrent network connections without blocking, you choose a runtime explicitly architected around lightweight processes or highly efficient async loops.
The same pragmatism applies to infrastructure. At scale or at rest, cleverness is a liability. A mature, boring, well-understood stack that doesn’t surprise you when incidents occur is worth its weight in gold. More importantly, your infrastructure must be a first-class citizen in telling you what it’s doing. You choose tools based on their ability to emit rich, high-cardinality telemetry. Instead of fragmented, siloed metrics, you want systems capable of producing Wide Events: single, massive contextual records for every transaction. When an unpredictable, emergent failure happens, you don’t want to guess based on an abstract dashboard graph; you want to query your telemetry and see the exact path a request took.
So The next time you start a project, catch yourself when your mind drifts to specific tools in the first five minutes. Step back and force your initial design decisions through this checklist instead:
Define the Domain and Constraints First: Clearly isolate the business problem before looking at technology. Determine what realities are non-negotiable: Is this an ultra-low-latency read environment, a high-throughput write-heavy data stream, or a highly confidential transactional system?
Trace the Data and Execution Flow: Map how a single byte travels from the user’s client down to physical storage. Identify exactly where data will queue, where execution paths block, and where resource contention will occur.
Establish Communication Patterns: Map out how your distinct system components will talk to one another. Do you require synchronous execution, or can you design for asynchronous, event-driven processing? Let these boundaries dictate your data contracts before choosing a transport mechanism.
Isolate Network and Reliability Requirements: Design for the bad days on day one. Evaluate how your primitives behave when the network splits, a disk fills up, or a downstream service disappears. Choose mechanisms based on how gracefully they fail, not how fast they run when everything is perfect.
Align Deployment and Hosting Realities: Understand where and how this system will actually run. If you are operating on a lean infrastructure or a highly distributed network, choose technologies that match that environment without introducing massive operational overhead.
Structure the Team to Match the Architecture: Assess your team’s composition, skill level, and communication lines. If you need a modular monolith to maximize feature velocity, don’t build a fractured system that requires five different engineering groups to sync up just to ship a feature. Match the org chart to the desired boundaries of the codebase.
Minimize Cognitive Load: The best tool is always the simplest one that satisfies your required system primitives. If a piece of infrastructure forces your team to maintain a massive, fragile mental model just to write a basic feature, it introduces organizational risk.
There is, however, a massive caveat to all of this:
Do not do this upfront in a full-scale, waterfall fashion.
Software is inherently incremental. You do not sit in an ivory tower for three months mapping out every data flow and designing perfect organizational structures before writing code. If you are building a prototype or an MVP to find product-market fit, you don’t need 90% of these characteristics. You need speed. You need to hack things together, use whatever tool is fastest in your hands, and validate the idea.
The real danger now isn’t that engineers are doing too much upfront planning, but that the modern toolset has created a brand new kind of structural illusion.
With the advent of LLMs and engineering agents, it has never been easier to “one-shot” a functioning prototype. You can type a prompt, and an AI will generate a codebase, scaffold a database, and spin up a working application in ten minutes. It feels like magic. But this speed introduces an incredibly insidious trap: the prototype accidentally becoming the product.
When an AI agent generates a development plan or hacks together an MVP without human architectural decisions, it defaults to the path of least resistance. It cobbles together popular frameworks and standard boilerplate. It doesn’t know your domain constraints. It doesn’t know your team’s cognitive load. It doesn’t understand your long-term reliability needs.
If you take that AI-generated prototype and just keep layering features on top of it without ever stepping back to define your primitives, you have accidentally entered the New Waterfall Model. You’ve locked yourself into an accidental architecture dictated by a stochastic model rather than an engineering mind.
AI can write code at blinding speeds, but it cannot make decisions for you. The development plans generated by agents are hypotheses, not decrees. Building sustainable systems still requires human touch, rigorous evaluation, and deliberate iteration.
You start messy and prototype fast. But the moment that prototype handles real traffic or faces shifting requirements, you stop hacking and you start architecting. You apply the hierarchy of intent incrementally, carving out well-defined boundaries as the problem itself becomes clearer. Architecture isn’t a phase you complete before you code; it’s a continuous, iterative practice that keeps your head out of the fire as the system evolves.
If you feel overwhelmed by the sheer number of tools out there, realize that you don’t need to master all of them. Master the art of defining the problem, mapping the characteristics, and structuring the human process. When you do that, the right tools have a funny way of choosing themselves.



