In the fall of a company we'll call Fernwood — a composite, illustrative SaaS business, not a real company — a product manager files a ticket that reads, "Let customers pause their subscription instead of canceling it." Nobody on the team thinks twice about it. It sounds like a Tuesday-afternoon feature.
Fernwood has nine engineers. The billing logic lives in the same codebase as everything else — one Rails application, one Postgres database, one deploy pipeline. An engineer opens the subscriptions controller, adds a paused_at column, writes a scheduled job to skip billing for paused accounts, updates the account settings page, and ships it. The whole thing takes four days, including a Slack thread where someone asks whether paused accounts should still count toward usage limits (they decide no, and change one if statement). The feature ships on Thursday. Nobody outside engineering and product even needs to be told it happened, because the release process is one person clicking a button.
Rewind three years later — same idea, same company, now with 40 engineers organized into six product teams and a platform team. The subscriptions logic has been carved out into a billing service, because a growing customer base and a Series B forced Fernwood to take payment reliability seriously. A new PM files what looks like the identical ticket. This time, the conversation is different. The billing team owns the pause/cancel state machine, but the entitlements team owns what a paused account is allowed to access, and the notifications team owns the "your subscription is paused" email. The frontend team that renders the account settings page depends on an API contract that three other services also depend on, and nobody wants to change its shape without a deprecation plan. The data team flags that "paused" needs to show up correctly in monthly recurring revenue reporting, or the board deck will be wrong. What took four days at nine engineers now takes three weeks, two design reviews, and a rollout plan, not because anyone is incompetent, but because the feature now touches a system that other systems depend on.
Fast-forward again. Fernwood has 130 engineers, a compliance team, and enterprise customers with contractual uptime guarantees. The "add a pause option" ticket triggers a security review, because pausing a subscription intersects with data retention policy for enterprise contracts. It touches an event stream that six downstream consumers subscribe to, including a customer-facing webhook API that partners have built integrations against. Changing the billing state machine means coordinating a schema migration across a service that processes real financial transactions, with a rollback plan, a feature flag, and a customer communication plan for anyone whose invoice math might shift by a day. The same idea now takes a quarter, not because the architecture is bad, but because the company is doing something fundamentally different: it is making a promise-preserving change to a system that other people and other systems now depend on.
Nothing about Fernwood's architecture became incompetent between these three moments. The monolith at nine engineers wasn't naive; it was the correct tool for a team that needed to move fast and didn't yet know which parts of the product would matter. The billing service at 40 engineers wasn't overengineering; it was a reasonable response to a real reliability requirement. The elaborate change-management process at 130 engineers wasn't bureaucracy for its own sake; it was what happens when your software has become infrastructure other businesses rely on.
What changed was not the code. What changed was the company — its size, its promises, its blast radius, its stakeholders. This is the central problem this article is about: an architecture decision that is completely rational at one stage of a company's growth can become a constraint at a later stage, without anyone having made a mistake along the way.
Most conversations about startup architecture focus on the wrong question. Founders ask, "Which architecture is best?" as if there's a universal answer sitting in a technology stack — as if picking microservices, or Kubernetes, or event-driven design is a matter of intelligence rather than fit. The more useful question, and the one this article keeps returning to, is:
Which architecture gives this company enough freedom for the stage it is entering next — without spending resources solving problems it doesn't have yet?
That question doesn't have a universal answer either. But it has a discipline behind it, and that discipline is what the rest of this article tries to build.
Part I: Architecture Is a Set of Options, Not a Set of Technologies
It's tempting to think of architecture as a list of tools: this database, this cloud provider, this messaging system, this framework. That's the vocabulary, but it's not the substance. Architecture is better understood as a set of decisions about what is easy to change and what is hard to change — and, just as importantly, about who is affected when something does change.
Every meaningful architectural decision creates asymmetry. It makes some future actions cheap and other future actions expensive. It's rarely possible to make everything easy at once; the tradeoffs are real, and the skill is in choosing which asymmetries you can live with.
Consider what a single decision — say, whether two features share a database — actually determines:
- What is easy to change. If two features share tables, a schema change to one can be made in a single migration. If they're split across services, that same change needs coordination, versioning, and possibly a period where both the old and new shape must be supported simultaneously.
- What is difficult to change. A tightly coupled monolith makes it hard to let one team deploy independently of another. A widely-adopted internal API makes it hard to change that API's contract without breaking someone.
- What can scale independently. A single application scales as one unit — you scale the whole thing to handle load on any part of it. Separate services can each scale according to their own demand, at the cost of operating more independent systems.
- What must move together. In a monolith, a deploy touches everything, even the code that didn't change. In a well-decomposed system, only the affected pieces need to move — but only if the boundaries were drawn correctly, which is not guaranteed just because the services are technically separate.
- Who owns a component. Ownership is not just a management concept; it shapes velocity. A component with unclear or shared ownership tends to accumulate friction, because every change requires consensus rather than a decision.
- Where failures spread. A single process failing can take down everything in a monolith, or it can be contained to one narrow function, depending on how failure domains are drawn — and drawing them well is harder than simply splitting code into separate repositories.
- How much operational complexity the company accepts. Every additional moving part — a queue, a cache, a second database, a service mesh — is something someone has to monitor, patch, and understand at 2 a.m. when it breaks.
None of these are properties of a technology in isolation. They are properties of how a system is shaped relative to how a company works and what it needs to be true right now.
The Architecture Trade-Off Map
The table below isn't a scorecard to be optimized in every cell — that's not how architecture works, and any framework that claims otherwise is selling something. It's a way to make the trade implicit in a decision explicit, so it can be discussed on purpose rather than discovered by accident eighteen months later.
| Dimension | What it means in practice | What tends to improve it | What tends to cost it |
|---|---|---|---|
| Changeability | How cheaply can a given part of the system be modified without breaking something else | Clear boundaries, small blast radius, fewer hidden dependencies | Shared state, tight coupling, undocumented assumptions |
| Scalability | Can this component handle more load, more data, or more concurrent users without a full redesign | Statelessness, independent scaling units, well-chosen data partitioning | Shared bottlenecks, synchronous chains, single points of write contention |
| Reliability | Does a failure in one place stay contained, and can the system recover predictably | Isolated failure domains, retries, graceful degradation | More moving parts, more network calls, more places to fail |
| Team autonomy | Can a team ship without waiting on another team's release cycle or code review | Independent deploy paths, owned services, stable contracts | Shared databases, shared codebases without clear ownership |
| Operational complexity | How much do humans need to understand, monitor, and maintain to keep the system healthy | Fewer moving parts, strong tooling, consistent patterns | More services, more infrastructure, more configuration surface |
| External dependencies | How much of the system's behavior relies on something the company doesn't control | Well-chosen managed services, vendor diversity where it matters | Vendor lock-in, undocumented SLAs, single points of failure outside your control |
| Cost | What does the system cost to run and to change, in both infrastructure spend and engineering time | Right-sized infrastructure, efficient data access patterns | Over-provisioned services, duplicated infrastructure, idle complexity |
| Reversibility | If this decision turns out to be wrong, how expensive is it to undo | Encapsulation, abstraction layers, avoiding premature specialization | Deep coupling to a specific technology's semantics, data models baked into many consumers |
A founder reading this table shouldn't try to maximize every column. That's not possible, and companies that try tend to end up with systems that are simultaneously flexible and slow, or reliable and prohibitively expensive to operate. The useful exercise is to ask, for any major decision on the table: which two or three of these dimensions matter most for us in the next twelve to eighteen months, and which are we consciously willing to sacrifice?
Founder takeaway: Architecture isn't a technology choice. It's a decision about which future problems you're willing to have. The mistake isn't picking the "wrong" tradeoff — it's not knowing which tradeoff you picked.
Part II: Seven Architectural Decisions That Shape Everything Downstream
1. Monolith vs. Microservices — and the Question Nobody Asks First
The monolith-versus-microservices debate has been run into the ground, usually as a binary choice presented with a winner. It isn't binary, and there usually isn't a winner — there's a company at a particular stage, with a particular team shape and a particular set of unknowns, making a decision that will look different in hindsight than it does in the room.
Start with what a monolith actually buys a small team. A single codebase, single deploy, single database means that a change touching three "features" is really just a change touching three files, tested and shipped together, with a stack trace that shows you the entire call path when something breaks. For a team of five to ten engineers who don't yet know which parts of the product will need to scale independently — because they don't yet know which parts of the product customers will actually use — this is close to optimal. It maximizes changeability and minimizes operational complexity at the exact moment those two things matter most: before product-market fit is fully proven, when the cost of being wrong about scope is higher than the cost of being wrong about scale.
The instinct to reach for microservices early is usually driven by a reasonable-sounding fear: "we might need to scale this eventually, so let's build it the way a scaled company would." This fear misunderstands what microservices actually solve. Microservices are primarily an organizational scaling tool, not a technical scaling tool. A single well-designed monolith, properly indexed and cached, can serve enormous technical load — companies far larger than most startups will ever become have run monoliths at significant scale. What microservices actually solve is the problem of many teams needing to ship independently without blocking each other, and the problem of isolating a failure in one part of the system from taking down everything else. If you don't have many teams yet, you're paying an organizational-scaling cost to solve a problem you don't have.
That cost is not small. Distributed systems introduce entire categories of failure that don't exist inside a single process: network partitions, partial failures where service A succeeded but service B didn't, version skew between services that deploy on different schedules, and debugging sessions that involve tracing a request across five log files instead of one stack trace. None of this is exotic anymore — there is excellent tooling for distributed tracing, service meshes, and observability — but "there is good tooling" is not the same as "this is free." Someone has to choose the tooling, run it, and understand it when it's 2 a.m. and a request is timing out somewhere in a call graph nobody has fully mapped.
There's a useful middle position that's underused in these conversations: the modular monolith. This is a single deployable application internally organized into modules with enforced boundaries — modules communicate through defined interfaces rather than reaching into each other's database tables or internal state, even though they run in the same process and deploy together. It gives you much of the discipline that makes service extraction easier later — clear ownership, defined contracts, minimal cross-module reach-in — without paying the operational cost of running distributed infrastructure before you need it. When service boundaries do start to matter, a well-modularized monolith is a far better starting point for extraction than a tangled one, because the interfaces already exist; you're changing the deployment topology, not discovering the seams for the first time under pressure.
When do service boundaries start to actually matter? Not at a headcount number — there's no honest rule that says "extract services at 30 engineers." They start to matter when specific, observable pressures show up:
- Multiple teams are regularly blocked on each other's release cycles because they share a deploy pipeline.
- One part of the system has a genuinely different scaling profile than the rest — for instance, a search or recommendation feature that needs to scale independently of the core transactional workload.
- A component has a different reliability requirement than the rest of the system, and you don't want its failures to take down unrelated functionality.
- A part of the system needs a different technology (a different language, a different database model) because the workload genuinely doesn't fit the rest of the stack.
Absent these pressures, "we might need microservices someday" is not a justification for building them today. Every architecture decision made today is a bet that the operational cost is worth paying now, in exchange for avoiding a harder migration later. Sometimes that bet is right. Often, for an early company, it isn't — because the migration, when the pressure actually shows up, is a well-understood problem with a well-worn path, while premature distribution is an ongoing tax paid every day regardless of whether the underlying justification ever materializes.
Decision Ledger — Monolith, Modular Monolith, and Service Extraction
| Decision | What it optimizes | What it makes harder | What changes later | Warning signal |
|---|---|---|---|---|
| Single monolith, minimal internal structure | Speed of early iteration, minimal operational overhead | Isolating failures, enforcing ownership boundaries as team grows | Becomes harder to extract services cleanly if modules were never separated | Engineers routinely say "I'm afraid to touch that file" or a bug in one feature breaks an unrelated one |
| Modular monolith with enforced module boundaries | Speed with structural discipline; easier later extraction | Slightly more upfront design discipline; some duplication across modules by design | Individual modules become natural candidates for extraction when pressure appears | A module's interface is being bypassed regularly because "it's faster" |
| Extract one high-pressure service (e.g., billing, search) | Independent scaling and deployment for the extracted piece; contains blast radius | Adds distributed-systems complexity: network calls, versioning, partial failure handling | Sets a pattern other teams will want to repeat, sometimes prematurely | A team asks to extract a service mainly because "it feels more modern," not because of a specific pressure |
| Broad microservices decomposition | Team autonomy at scale, independent deploys across many teams | Operational complexity, cross-service debugging, coordination for any change that spans boundaries | Requires investment in platform tooling: service discovery, tracing, standardized deployment | Small product changes require touching five services and coordinating three teams to ship |
Founder takeaway: The question isn't "monolith or microservices." It's "what pressure, specifically, would justify the operational cost of a service boundary — and do we have that pressure yet, or are we anticipating it?" Anticipation is sometimes right. It should be a deliberate bet, not a default.
2. Database Choices — The Assumptions You Don't Know You're Making
Database decisions get framed as a technology comparison — SQL versus NoSQL, Postgres versus MongoDB, relational versus document-oriented. That framing misses what actually makes these decisions consequential. A database choice is really a bet about what your data will need to do, encoded early, before you fully know the answer.
Every database choice embeds assumptions about:
- Relationships. Does your core data have many interconnected relationships that need to be queried together (a customer has orders, which have line items, which reference products), or is your data naturally self-contained documents that rarely need to be joined against each other?
- Transactions. Do you need multiple writes to succeed or fail together as a unit — critical for anything involving money, inventory, or state machines — or is eventual consistency across independent writes acceptable?
- Reporting and analytics. Will the same data need to support both fast transactional lookups (show me this user's dashboard) and broad aggregate queries (show me revenue trends across all users)? These access patterns often want different underlying structures.
- Schema evolution. How often will the shape of your data change, and how expensive is it to change that shape once there's data in the system?
- Consistency requirements. Does a user need to see their own write immediately, everywhere, or is a brief delay acceptable in exchange for better scalability?
- Operational workload. Is your traffic read-heavy, write-heavy, bursty, or steady? Different databases are tuned for different shapes of load.
The trouble is that most of these questions don't have confident answers on day one. A startup at Stage A often doesn't know whether its data will end up relational or document-shaped, because the product itself hasn't stabilized. This is one of the better arguments for starting with a mature relational database like Postgres or MySQL even for data that might later look document-like: relational databases have become flexible enough — with native JSON column support in most modern engines — to handle semi-structured data reasonably well, while still preserving the option of strong relationships and transactions if the product turns out to need them. Starting with a rigid document store because "we might need to scale" often forecloses options you didn't know you needed, in exchange for a scaling property you may not need for years.
A Failure Timeline: How a Reasonable Choice Becomes a Constraint
Consider a fictional startup, Larkspur Analytics, that builds a dashboard product for e-commerce teams.
Year 1. Larkspur picks a single Postgres database. The product has one core entity — a "store" — with orders, products, and customers nested underneath it. The relational model fits perfectly: joins are cheap, transactions keep order totals consistent, and the team of six engineers ships fast because there's one obvious place for any new field to live.
Year 2. The product adds a reporting feature that needs to aggregate metrics across millions of order rows for dashboards refreshed every few minutes. The same database now serves both the transactional writes (a customer places an order) and the heavy analytical reads (a dashboard summarizes a year of orders). These two workloads compete for the same resources. The team adds read replicas and materialized views to keep the dashboard fast without slowing down the transactional path — a reasonable, well-understood pattern, but the first sign that "one database for everything" is starting to strain.
Year 3. Larkspur signs its first large enterprise customers, who want historical analytics going back years, sliced in ways the original schema never anticipated. The team builds a separate analytical data store — a columnar warehouse better suited to large aggregate queries — and pipes data into it from the operational database. This is not a mistake; it's the natural specialization of a system that has outgrown a single storage model for two very different jobs. But it introduces a new problem: two sources of truth, a pipeline that can lag or break, and reports that occasionally disagree with the live dashboard by a few minutes.
Growth stage. By the time Larkspur is operating at real scale, the original schema — designed around a single "store" as the unit of everything — is deeply embedded not just in the database, but in application code, in the reporting pipeline's transformation logic, in customer-facing API contracts, and in the assumptions baked into dozens of background jobs. A genuinely better data model for the modern product (say, one that treats a customer as able to operate multiple stores under one account, a feature large customers now expect) requires a migration that touches every one of those layers, not just a schema change.
This is the deeper point about database migrations: they are rarely difficult because the database technology is hard to change. They are difficult because business assumptions have become embedded across application code, queries, background jobs, analytics pipelines, and integrations that all assume the original shape of the data. A migration is not a data problem. It's an organizational archaeology problem — finding every place that quietly assumed the old model was permanent.
This doesn't argue for avoiding a first choice, or for over-engineering flexibility nobody will use. It argues for documenting the assumption at the time it's made (more on this in Part VI) so that when the pressure to change shows up, the team understands why the original decision made sense and what, specifically, has changed.
Founder takeaway: The riskiest part of a database decision isn't picking the wrong database. It's not noticing, three years later, how many places in the business have quietly assumed the original data model is permanent.
3. Synchronous vs. Asynchronous Architecture — Follow the Request
Most founders have an intuitive sense of what "the backend" does, but few have traced what actually happens between a customer clicking a button and something appearing on their screen. Doing that trace once is one of the most useful exercises a non-technical founder can do, because it reveals a decision that shapes reliability, cost, and user experience more than almost anything else: what has to happen right now, and what can safely happen later?
Start simple. A user clicks "Upload Invoice." What happens next?
Option one — everything synchronous. The click triggers a request that uploads the file, parses it, extracts line items, validates them against the company's chart of accounts, saves the result to the database, and returns a success message — all before the user's browser gets a response. This is the simplest architecture to reason about: one request, one response, a clear success or failure. It's also fragile in a specific way — if invoice parsing takes eight seconds because the file is large, the user's browser is sitting there waiting the whole time, and if any single step fails (the parsing library times out, a downstream validation service is slow), the entire request fails, even the parts that had nothing to do with the failure.
Option two — partially asynchronous. The click uploads the file and immediately returns "we're processing your invoice." Behind the scenes, the upload triggers a background job, placed on a queue, that does the parsing and validation independently. When it's done, the user is notified — through a websocket update, a polling check, or an email. This adds real complexity: now there's a queue to operate, a worker process to run, and a question of what the user sees in the meantime. But it also means a slow parsing step doesn't block the browser, a temporary failure in the validation service can be retried without the user noticing, and the upload step and the processing step can each scale independently — you can add more parsing workers without touching the upload path at all.
The tradeoff is not "asynchronous is more advanced, therefore better." Asynchronous architecture introduces genuine complexity that synchronous architecture doesn't have:
- Idempotency. If a background job is retried after a partial failure, does running it twice cause a duplicate invoice, a double charge, a duplicate email? Systems that retry need to be designed so that doing the same operation twice is safe — this is a real engineering discipline, not a checkbox.
- Eventual consistency. Between the moment the user clicks "upload" and the moment the invoice is actually processed, the system is in an intermediate state. The UI has to represent that honestly ("processing…") rather than pretending the work is already done.
- Failure visibility. A failed synchronous request tells the user immediately. A failed background job can fail silently unless someone builds monitoring, alerting, and a retry or dead-letter strategy specifically for it.
- Debugging complexity. Tracing what happened to a specific invoice now means following it across an upload handler, a queue, a worker, and a notification service, instead of reading one function top to bottom.
The founder-level question to ask about any feature is not "should this be async?" as a general philosophy, but: what part of this specific interaction absolutely has to be confirmed to the user before they can trust it happened, and what part can be resolved a few seconds — or minutes — later without anyone noticing the difference? A payment authorization has to happen synchronously; the user needs to know immediately whether their card was charged. Sending a receipt email does not; nobody notices if that arrives two seconds or twenty seconds later. Generating a PDF invoice can happen asynchronously. Validating that the invoice amount matches the order total, in a system where a mismatch triggers a fraud review, might need to be synchronous, because letting a bad transaction complete and cleaning it up later is more expensive than making the user wait an extra second.
Asynchronous architecture, done well, improves resilience — a spike in traffic gets absorbed by a queue rather than by overwhelmed application servers — and improves scalability, because different parts of a workflow can scale according to their own demand. It also, done poorly, turns a simple bug into a forensic investigation, because the state of "what happened" is now spread across multiple systems instead of living in one request's logs. Neither synchronous nor asynchronous architecture is the more mature choice. The mature choice is knowing, for each interaction, which one you actually need.
Founder takeaway: Every feature has a hidden question: what must be true immediately, and what can become true a moment later? Getting that question wrong in either direction — making everything synchronous, or reflexively making everything async — costs you either resilience or simplicity you didn't need to give up.
4. API Design and System Boundaries — Following the Blast Radius
An API is often described to non-technical stakeholders as "how two systems talk to each other," which is true but understates what's actually at stake. The real question an API design answers is: when this changes, who has to know, and how far does the disruption travel?
Call this the blast radius of a change: how many systems, teams, customers, and business processes are affected when one component's behavior shifts. Architecture is, in large part, the discipline of controlling blast radius — deciding, in advance, how far a ripple should be allowed to travel.
Take a change that sounds simple in a product meeting: "Let's change how subscriptions work — instead of monthly and annual plans, we want usage-based billing." Trace what that actually touches in a company with any operating history:
- Frontend. Every screen that displays a plan name, a price, or a billing cycle needs to understand the new model, including screens the team may have forgotten exist — an old admin panel, a mobile app on a slower release cycle, an embedded widget a partner uses.
- Backend billing logic. The core state machine that decides what a customer owes and when now has a fundamentally different shape — usage-based billing isn't a variation of subscription billing, it's a different problem (metering, aggregation windows, proration logic).
- Database. The schema that assumed "a customer has one plan with one price" now needs to represent variable, usage-derived charges, which likely means new tables, new migration paths for existing customers, and a plan for what happens to customers mid-cycle during the transition.
- Authentication and entitlements. If access to features is gated by plan tier, and plan tier is being redefined, the system that decides "can this user do X" needs to be updated in lockstep, or customers will find themselves with the wrong access during the transition.
- Notifications. Billing emails, upcoming-charge warnings, and dunning emails for failed payments all assume a predictable billing cycle. Usage-based billing changes when and how those are triggered.
- Analytics and reporting. Monthly recurring revenue, a number the board and investors look at, is calculated based on assumptions about predictable subscription income. Usage-based revenue is fundamentally less predictable, and every downstream report that assumed MRR as a stable metric now needs to be reconsidered.
- Customer support. Support tooling that shows a customer's plan and billing history needs to represent the new model, or support agents will be looking at a screen that doesn't match what the customer is actually being charged.
- External integrations. If any partner or customer has built something against a billing webhook or API — even something as simple as "notify me when a subscription renews" — that contract may no longer make sense under usage-based billing, and breaking it without warning breaks someone else's product.
None of these are exotic consequences. They're the ordinary result of one system boundary — "how billing works" — having many more dependents than the org chart makes obvious. Whether this change stays contained to one team for two weeks, or becomes a company-wide six-month program, is determined almost entirely by how well those boundaries were drawn in the first place.
Good API boundaries make blast radius small and predictable:
- Internal APIs — the contracts between services inside the company — can evolve faster, but still benefit from versioning discipline once more than one consumer depends on them, because "just update the caller" stops being an option once there are several callers on different release schedules.
- External APIs — anything a customer or partner integrates against — need genuine backward compatibility discipline. Once a contract is public, changing its shape without a deprecation window breaks someone else's product, sometimes silently, often at the worst possible time.
- Shared data models — the practice of multiple services or teams reading and writing the same underlying data structure directly, rather than through a defined interface — are one of the most common sources of unexpectedly large blast radius. A change that looks local ("I just added a field") can break three other teams' assumptions if they were reading that same table directly.
Founder takeaway: Before approving a "simple" product change, ask: how many systems does this actually touch, and how many of them are outside this team's direct control? The answer tells you whether you're looking at a two-week project or a coordination problem wearing a two-week project's clothes.
5. Cloud Architecture and the Managed-Services Paradox
The cloud conversation that matters for a startup is not "AWS versus Azure versus Google Cloud." That decision, for most companies, matters far less than founders assume, and the differences between the major providers have narrowed considerably. The conversation that actually matters is about managed services — and a paradox at the heart of using them.
Managed services exist to remove operational burden. Instead of running your own Postgres instance, patching it, backing it up, and handling failover, you use a managed database service and let the provider handle that. Instead of building your own authentication system, you use a managed identity provider. Instead of standing up your own search infrastructure, you use a managed search service. Each of these decisions, taken individually, is usually a very good trade for a startup: it converts a problem that requires specialized operational expertise into a problem that requires an API key and a monthly bill.
The paradox is this: each managed service that reduces operational work also becomes another architectural dependency — another piece of the system whose behavior, pricing, limits, and failure modes you don't control and must design around. This isn't an argument against managed services. It's an argument for noticing that "simplification" and "dependency" are the same decision viewed from two different angles, and that the balance between them shifts as the number of services grows.
A Dependency Map: How a SaaS Gradually Accumulates Coupling
Picture a hypothetical SaaS company's infrastructure evolving over a few years, each addition entirely reasonable on its own:
Year 1: [ App server ] → [ Managed database ]
Year 1: [ App server ] → [ Object storage (file uploads) ]
Year 2: [ App server ] → [ Managed authentication provider ]
Year 2: [ App server ] → [ Managed messaging/queue service ]
Year 3: [ App server ] → [ Managed monitoring & observability platform ]
Year 3: [ App server ] → [ Managed search service ]
Year 3: [ App server ] → [ Payment provider ]
Year 4: [ App server ] → [ Analytics platform ]
Year 4: [ App server ] → [ Managed CDN + edge functions ]
Every one of these was individually the right call — building your own authentication system instead of using a managed identity provider is rarely a good use of a startup's engineering time, and the same is true for payments, where the security and compliance burden of handling card data directly is a real argument for using a specialized provider. But look at the shape of the diagram after four years: the application now has eight or nine external dependencies, each with its own pricing model, its own rate limits, its own outage history, its own API that changes on its own schedule, and its own data export process if the company ever needs to leave.
At what point does this stop being simplification and start being architectural coupling that constrains the company? There's no universal number — a founder who's told "at seven managed services, be worried" is being given a false precision that doesn't hold up across different businesses. The useful diagnostic isn't a count. It's a set of questions:
- If this provider had a multi-hour outage tomorrow, what part of our product stops working, and would customers notice? If the answer is "the entire product goes down," that's a single point of failure worth understanding, even if it's a managed service you don't operate yourself.
- If this provider doubled its pricing, or changed its terms in a way we don't like, how hard would it be to leave? Some managed services are easy to swap (object storage is fairly commoditized). Others encode deep assumptions into your system (a managed database's specific consistency model, a search service's specific query language) that make leaving a multi-quarter project.
- Do we actually understand this service's failure modes, or have we only seen it succeed? A dependency you've never seen fail is not the same as a dependency that can't fail — it's a dependency whose failure mode you haven't been forced to learn yet.
- Is this service on the critical path for our core product, or for a peripheral feature? A managed analytics platform going down for a day is an inconvenience. A managed authentication provider going down for a day means nobody can log in.
Observability deserves particular attention here, because it's the thing that determines whether the answers to these questions are knowable at all. A system built entirely from managed services, without investment in logging, tracing, and monitoring that spans across them, becomes a system where an incident shows up as "something is slow" with no clear way to tell which of the nine dependencies is responsible. The operational simplicity that managed services promise is only real if the company also invests in visibility across the resulting dependency graph — otherwise, the complexity hasn't gone away, it's just become invisible until an incident forces it into view.
Founder takeaway: Every managed service is a good decision in isolation and a coupling decision in aggregate. The question worth asking regularly isn't "should we use managed services" — almost always yes — but "do we actually know what happens to our product if any one of these goes away for an afternoon?"
6. Architecture and Team Structure — The Same Codebase, Two Different Organizations
There's a well-known observation, often shortened to a buzzword, that organizations tend to design systems that mirror their own communication structure. Stripped of the buzzword, the mechanism is straightforward and worth walking through concretely, because it explains a pattern many founders have felt without naming: the same codebase can be perfectly healthy under one organizational structure and increasingly painful under another, without a single line of code changing.
Same Product, Different Organization
Organization A: one engineering team, eight people, one codebase, shared ownership of everything. When a bug shows up in the billing code, whoever's available fixes it. When someone wants to change how notifications work, they just change it — there's no other team to negotiate with, because there is no other team. Decisions happen in a standup or a Slack thread. The lack of formal ownership boundaries isn't a problem; it's efficient, because the coordination cost of "checking with the other team" doesn't exist when there's only one team.
Organization B: the same codebase, now five product teams and a platform team, thirty-five people. The billing code that anyone used to touch freely is now touched by three different product teams for three different reasons — one team owns pricing experiments, another owns invoicing, a third owns usage-based add-ons. None of them "owns" the billing module in a way that gives them confidence to change it without checking with the others, because a change one team makes for its own purposes can silently break an assumption another team was relying on. What used to be a five-minute change becomes a change that requires a Slack thread with three teams, a shared understanding of who's allowed to modify what, and — if the company hasn't addressed this — a growing reluctance from any individual team to touch shared code at all, because the risk of breaking someone else's work has become real.
Nothing about the code changed between these two organizations. What changed is that ownership, which was implicit and costless when there was one team, became a real architectural question when there were five. The natural response — and the one companies eventually reach for, sometimes too late — is to draw boundaries that match the organization: separate the billing module into something with a clear owning team, expose what other teams need through a defined interface rather than direct access, and let each team deploy its part independently.
This is where the practical mechanism behind the "communication structure" observation becomes visible: it's not that the company's org chart mystically shapes the code. It's that shared, ambiguous ownership of a system is cheap when coordination between the people involved is cheap, and expensive when coordination between the people involved is expensive. Coordination cost rises with team count, with organizational distance, and with how much the teams involved have diverging priorities. Architecture that draws clear boundaries — clear ownership, clear interfaces, independent deployment — is a way of making coordination cost explicit and bounded, rather than an invisible tax that shows up as "everything takes longer than it should" without anyone being able to say exactly why.
This has a genuinely important founder-level implication: architecture is partly a technical reflection of how the company works, which means an architecture decision made for a five-person team should be expected to need revisiting — not because it was wrong, but because the organization it was built for no longer exists. A founder who reorganizes engineering into product teams but leaves the underlying system as a single shared, unowned codebase is setting up exactly the friction described above. A founder who splits a system into services that don't match any real team boundary — extracting a "notifications service" that three different teams all still need to touch for their own reasons — creates a different, equally real friction: technical boundaries that don't correspond to organizational ones just move the coordination cost, they don't remove it.
Shared platform services deserve a specific mention here, because they're a common source of this mismatch. A platform team that owns "authentication" or "the design system" as a shared service, used by every product team, has to solve a genuinely hard problem: how does one team serve many demanding internal customers without becoming a permanent bottleneck? The answer usually involves clear, stable contracts and a self-service model — product teams can use the platform team's service without needing the platform team's direct involvement for every change — rather than a model where every product team's request goes into the platform team's backlog and waits.
Founder takeaway: If a part of the system feels like it takes disproportionately long to change, check whether it's owned by one team, several teams, or effectively no one. Ambiguous ownership is often mistaken for a technical problem when it's actually a structural one.
7. The Architecture Bottleneck That Doesn't Look Like an Architecture Problem
This is the section worth reading most carefully, because it's the one that prevents the most expensive mistake in this entire subject: treating every organizational pain point as an architecture problem, when some of them aren't. Architecture is genuinely responsible for a lot of friction that shows up in a growing company. It is not responsible for all of it, and the discipline of telling the difference is what separates a useful diagnosis from an expensive, misdirected rewrite.
Here are symptoms founders and product leaders actually observe, each paired with what might be causing it, what else might be causing it, and how to tell the difference.
Symptom: Every feature touches the same few modules. Possible architectural cause: Core business logic (often around a central entity like "account" or "user") was never separated into distinct concerns, so unrelated features all end up modifying the same files. Alternative explanation: The product genuinely has a small number of core concepts that most features legitimately relate to — this can be a sign of a coherent product, not a broken architecture. Evidence to collect: Look at whether the "same module" is being touched for unrelated reasons (a sign of poor separation) or related reasons (a sign the module is doing its job as a shared, coherent concept).
Symptom: Releases require too many people. Possible architectural cause: A monolithic deploy means unrelated changes ship together, so every release needs sign-off from everyone whose code happens to be in the batch. Alternative explanation: This might be a process problem — manual QA gates, unclear release ownership, or a compliance requirement — that would exist regardless of the underlying architecture. Evidence to collect: Ask what specifically requires each person's involvement. If it's "I need to confirm my unrelated feature didn't break," that's architectural coupling. If it's "we need sign-off per policy," that's process.
Symptom: One engineering team becomes a permanent bottleneck. Possible architectural cause: That team owns a shared component — often a core data model or a platform service — that every other team's work routes through. Alternative explanation: The team may simply be understaffed relative to demand, which is a resourcing problem, not a structural one. Evidence to collect: Check whether other teams could, in principle, make the needed change themselves if given access — if the honest answer is "no, only they understand that code," that's an architectural ownership problem. If the honest answer is "yes, but they're waiting in a queue," that's capacity.
Symptom: Small changes require broad regression testing. Possible architectural cause: Low isolation between components means a change in one place has unpredictable effects elsewhere, so the only safe response is to test everything. Alternative explanation: This can also be a symptom of insufficient automated test coverage generally, independent of how well-isolated the architecture is — a well-separated system with no tests still requires broad manual verification out of simple uncertainty. Evidence to collect: Look at whether past incidents were caused by unexpected cross-component effects (architecture) or by genuinely untested code paths within a single component (test coverage). This is a place where investment in test strategy and release confidence — the kind of work QA-focused engineering practices are built around — often matters as much as the underlying system boundaries; a well-isolated architecture with no regression safety net still forces broad, manual verification out of uncertainty.
Symptom: Engineers avoid certain areas of the system. Possible architectural cause: Genuinely fragile code with poor test coverage or unclear behavior, where changes have historically caused unexpected breakage. Alternative explanation: It could be a knowledge problem — only one person ever understood that area, and they've since left or moved teams, which is a documentation and knowledge-transfer issue rather than an architectural one. Evidence to collect: Ask engineers specifically what they're afraid of. "I don't understand what this affects" points to poor boundaries or missing documentation. "I know exactly what it affects and it's a lot" points to genuine blast radius.
Symptom: Scaling one component requires scaling everything. Possible architectural cause: Components that could have independent resource needs are bundled into a single deployable unit, so handling load on one forces over-provisioning of all of it. Alternative explanation: The actual load might not yet justify independent scaling — this can be a premature concern if current traffic doesn't come close to the limits of the current setup. Evidence to collect: Look at actual utilization data. If one component is consistently near capacity while others are idle, that's a real architectural signal. If everything has comfortable headroom, this is a future problem, not a current one.
Symptom: Production incidents affect unrelated functionality. Possible architectural cause: Shared infrastructure or shared failure domains mean a problem in one area cascades into others — a classic sign of insufficient isolation. Alternative explanation: It could be a monitoring and alerting gap — the "unrelated" functionality might not actually be affected, but the team lacks the visibility to tell what is and isn't impacted during an incident, so everything gets treated as suspect. Evidence to collect: During the next incident, trace the actual technical dependency chain, not just what looked affected from the outside. This is where good observability pays for itself directly.
Symptom: Infrastructure costs grow faster than usage. Possible architectural cause: Inefficient data access patterns, over-provisioned managed services, or a scaling model that doesn't match the actual load shape (e.g., paying for constant capacity to handle rare spikes). Alternative explanation: Pricing changes from a vendor, or a genuine change in what the product does (more data-intensive features) that legitimately costs more per user than before. Evidence to collect: Break down cost growth by component. If it's concentrated in one system whose usage hasn't grown proportionally, that's architectural. If cost is growing roughly in line with a new, genuinely more resource-intensive feature, that's expected.
Symptom: Teams repeatedly wait for another team. Possible architectural cause: Shared components without self-service interfaces mean every change requires the owning team's direct involvement. Alternative explanation: This is very often a prioritization and staffing problem — the owning team may have a perfectly good self-service interface but is simply understaffed relative to the requests coming in. Evidence to collect: Ask what's actually being waited on: a technical dependency (I can't deploy until they deploy) or a human dependency (I need them to review or build something for me). These call for very different fixes.
Symptom: New integrations take disproportionately long. Possible architectural cause: No clear extension point exists for adding a new integration, so each one requires bespoke work deep in the core system rather than plugging into a defined interface. Alternative explanation: This can also reflect a lack of reusable tooling or documentation for integration work, independent of whether the underlying architecture is actually well-suited to it. Evidence to collect: Look at what the last three integrations actually required. If each one needed changes to core business logic, that's architectural. If each one needed the same boilerplate that could have been turned into a reusable pattern, that's a tooling gap.
Symptom: Simple product experiments become expensive. Possible architectural cause: No feature-flagging or safe rollout mechanism exists, so testing an idea with a subset of users requires a full, risky deployment rather than a contained one. Alternative explanation: This can also be a cultural or process issue — a risk-averse release process that would resist quick experiments regardless of the underlying architecture's actual flexibility. Evidence to collect: Ask what specifically makes the experiment expensive: is it technically hard to isolate the change (architecture), or is it organizationally hard to get approval to ship anything small (process and culture)?
The discipline in this section is not glamorous, but it's the difference between a founder who diagnoses correctly and one who commissions an expensive rewrite that doesn't fix the actual problem. "Releases are slow," for instance, is a symptom with at least five plausible causes — architecture, poor deployment automation, unclear ownership, excessive manual approval steps, or insufficient automated testing — and the fix for each is completely different. Rewriting the architecture to solve a deployment-automation problem is an expensive way to discover that releases are still slow.
Founder takeaway: Before funding an architecture change, insist on evidence, not just a symptom. "It feels slow" is a starting point for an investigation, not a justification for a rewrite.
Part III: The Stage Transition Problem
The uncomfortable truth underneath everything in Part II is this: an architecture can be genuinely healthy today and still be wrong for the company's next stage — not because anyone made an error, but because the thing being optimized for has changed. A framework that helps here compares three illustrative company stages — not hard headcount rules, but rough shapes of organization that tend to correlate with different pressures.
A Maturity Matrix, Read Carefully
The temptation with a matrix like this is to read it as a rule — "ten engineers means a monolith, fifty means microservices." That reading is wrong and worth resisting explicitly, because the actual determinants of good architecture at any given size are workload shape, team structure, product complexity, failure tolerance, deployment patterns, and business requirements — not a number on a roster. A company at 15 engineers processing financial transactions for regulated customers has more in common, architecturally, with a 100-engineer company than with a 15-engineer consumer app that tolerates occasional downtime.
With that caveat firmly in place, here's how priorities tend to shift as organizations grow, understanding that "tend to" is doing real work in that sentence:
| Priority | Stage 1: Small product team (~5–10 engineers) | Stage 2: Growing engineering org (~30–50 engineers) | Stage 3: Large product org (100+ engineers) |
|---|---|---|---|
| Speed of iteration | Usually the top priority — the team is still finding product-market fit | Still important, but now bounded by the need to not break things other teams depend on | Important within a team's own scope; company-wide iteration speed depends more on platform maturity than individual team speed |
| Team autonomy | Not yet a distinct concern — one team, shared context | Becomes a real question as multiple teams need to ship without blocking each other | A core organizational design constraint; architecture is often explicitly shaped around preserving it |
| Reliability | "Good enough" is often genuinely good enough; downtime is costly but recoverable | Expectations rise, especially once paying customers depend on the product for their own operations | Often contractually and legally binding (SLAs, compliance requirements); failure has direct financial and legal consequences |
| Deployment independence | Irrelevant — there's one team and one deploy | Starts to matter once teams' release cadences genuinely conflict | Usually a hard requirement; a single shared deploy pipeline across 100+ engineers is rarely sustainable |
| Data scale | Data volume is usually well within what a single well-tuned database can handle | Specific access patterns (reporting, search, real-time features) may start to strain a single data store | Data architecture often needs deliberate specialization: separate systems for transactional, analytical, and search workloads |
| Operational maturity | Minimal — a small team can informally "just know" how the system behaves | Needs formal investment: monitoring, alerting, on-call processes, incident response | Requires dedicated platform and reliability functions; informal knowledge no longer scales to the system's size |
| Security and compliance | Baseline hygiene; formal compliance is rarely a blocker yet | Often becomes a real requirement once enterprise customers or specific industries are involved | Frequently a first-class architectural constraint shaping data flows, access boundaries, and audit requirements |
| Global traffic / customer expectations | Usually a single region is sufficient | May start to matter for latency-sensitive features or specific customer geographies | Multi-region architecture often becomes a genuine requirement, not an optimization |
Reading this table correctly means recognizing that a company can be a "Stage 3" business on some rows and a "Stage 1" business on others. A 25-engineer fintech company handling regulated payment data may need Stage 3-level reliability and compliance discipline while still operating with Stage 1-level deployment simplicity, because it hasn't yet split into multiple independent product teams. Applying a single stage's assumptions uniformly across all these dimensions is exactly the mistake this matrix is meant to prevent.
Founder takeaway: Don't ask "what stage are we at?" as a single number. Ask, row by row: on this specific dimension, what does our situation actually require right now — regardless of what our headcount would suggest?
Part IV: The Founder's Architecture Review
The following is a set of questions a founder or product leader can genuinely use without reading code. Each one is paired with why it matters, what a healthy answer sounds like, and what should trigger a deeper look.
1. What becomes harder every time we add a new feature? Why it matters: If the answer is "nothing in particular," the architecture is absorbing growth well. If the same category of difficulty keeps recurring, that's a structural signal, not a coincidence. Healthy answer: Difficulty is proportional to the feature's actual complexity, not to which part of the system it touches. Investigate further if: Engineers consistently say some category of feature is "always painful," regardless of how simple it sounds in the product spec.
2. Which component changes most frequently, and does it have the ownership clarity that frequency deserves? Why it matters: Frequently changed code without clear ownership is a recurring source of both bugs and coordination overhead. Healthy answer: The most frequently changed components have a clear owning team and well-understood interfaces. Investigate further if: The most frequently changed code is also the code with the least clear ownership — this combination compounds.
3. Which team approves the most changes outside their own roadmap? Why it matters: This reveals hidden bottleneck teams whose review load is a tax on everyone else's velocity, often invisible in a roadmap review. Healthy answer: Review load is roughly proportional to a team's own capacity and mission. Investigate further if: One team's calendar is dominated by reviewing other teams' changes to shared systems.
4. Which component has the largest blast radius if it changes or fails? Why it matters: This identifies your highest-leverage point of risk — the place where the company should invest most in careful design, testing, and change management. Healthy answer: The team can name it specifically and describe what's actually connected to it. Investigate further if: Nobody can confidently answer this question, or different people give different answers.
5. What happens if one of our key external dependencies becomes unavailable for a few hours? Why it matters: Every managed service and vendor is a dependency you don't control. Knowing the specific failure mode in advance is much cheaper than discovering it during an outage. Healthy answer: There's a specific, tested answer — degraded functionality, a fallback, or a known and accepted risk. Investigate further if: The honest answer is "we're not sure" for anything on the critical path.
6. Can we scale our busiest component independently of the rest of the system? Why it matters: If the answer is no, growth in one area forces the company to over-provision everything, which is both expensive and operationally fragile. Healthy answer: The busiest components can absorb more load without a full-system redesign. Investigate further if: Scaling for one feature's success requires touching infrastructure for unrelated features.
7. Which architectural decision would be hardest to reverse, and do we still believe in it? Why it matters: Not all decisions carry equal risk. The ones with low reversibility deserve more scrutiny before being made and more attention after. Healthy answer: The company can name its least-reversible decisions and can articulate why they still hold up. Investigate further if: A hard-to-reverse decision was made quickly, under pressure, without anyone flagging it as such at the time.
8. Where does the organization repeatedly wait for engineering, and is that a technical or a staffing constraint? Why it matters: These two causes look identical from the outside and require completely different responses. Healthy answer: Leadership can distinguish, with evidence, between "we need more people" and "the system itself is the bottleneck." Investigate further if: The company keeps hiring to solve a wait time that doesn't improve, which usually means the constraint isn't staffing.
9. Which technical constraint is starting to shape product strategy, rather than the other way around? Why it matters: This is a sign the architecture has quietly become the thing deciding what the company can offer, rather than a tool serving the product vision. Healthy answer: Technical constraints are known and factored into strategy deliberately, not discovered mid-negotiation with a customer. Investigate further if: A sales or product conversation gets derailed by "we can't actually do that" more than once for the same underlying reason.
10. Which part of the system is understood deeply by only one or two people? Why it matters: This is both an architectural and organizational risk — knowledge concentration slows everything down and creates a real business continuity risk. Healthy answer: Critical systems have at least a small group who understand them, with documentation that doesn't depend entirely on any one person's memory. Investigate further if: A specific person's vacation or departure would create a genuine crisis in a specific area.
11. Where have failures propagated further than expected? Why it matters: This is direct evidence about the real, as opposed to intended, failure domains in the system — often different from what an architecture diagram suggests. Healthy answer: Past incidents were contained roughly as expected, and any surprises were investigated and addressed. Investigate further if: A pattern emerges of "small" failures repeatedly having outsized, cross-system effects.
12. Which systems are becoming shared bottlenecks as we add teams? Why it matters: This is the earliest, cheapest point to notice a coordination bottleneck forming — before it becomes the reason releases slow down company-wide. Healthy answer: Shared systems have a plan for how they'll scale in ownership and usage, not just in technical capacity. Investigate further if: A system originally built for one team's use is now a dependency for several teams with no update to its ownership model.
13. What are we optimizing for right now — and does everyone building the system know that? Why it matters: Architecture decisions made without a shared understanding of current priorities tend to optimize for the wrong thing, or for whatever the engineer making the decision personally cared about that week. Healthy answer: Engineers can articulate the current priority (speed, reliability, cost, scale) in terms that match leadership's actual priorities. Investigate further if: Different teams give visibly different answers to what the company is currently optimizing for.
14. What future option are we intentionally giving up with this decision — and did we choose that, or did it just happen? Why it matters: Every architectural decision forecloses some future option. The question is whether that trade was made on purpose. Healthy answer: The team can name the option being given up and explain why it's an acceptable trade right now. Investigate further if: Nobody realized a decision was foreclosing a future option until that option was needed.
15. If we had to make this same decision again today, with what we now know, would we make it the same way? Why it matters: This is the most honest question in the review. It's not about blame — it's about whether the original reasoning still holds. Healthy answer: Yes, largely, with maybe minor adjustments — or a clear, specific articulation of what's changed and why the answer is now different. Investigate further if: The honest answer is "no, but we haven't done anything about it," which is a strong signal that a deliberate re-evaluation is overdue.
Part V: When Not to Change the Architecture
Every framework in this article risks creating an itch to act. That itch deserves active resistance, because architecture change is not free, and the fashionable answer is rarely the right one for a specific company at a specific moment.
Founders should be specifically skeptical of the instinct to:
- Rewrite a working monolith because a well-known company talks publicly about their microservices architecture, without checking whether that company's pressures (team count, traffic scale, organizational structure) resemble the startup's own.
- Split services because it feels like the "grown-up" thing to do, rather than because a specific, observed pressure from Part II, section 1 has actually shown up.
- Migrate databases in anticipation of scale that hasn't materialized yet, when the current database is handling current load comfortably.
- Adopt event-driven architecture wholesale, because asynchronous systems sound more resilient in the abstract, without accounting for the debugging and consistency complexity it introduces for workflows that didn't need it.
- Move to Kubernetes before the operational complexity of container orchestration is actually justified by a need it solves — for many startups, a simpler deployment platform handles their actual scale just as well with far less operational overhead.
- Change cloud providers based on marginal pricing differences, without weighing the migration cost against the actual expected savings.
- Introduce multiple regions before there's a specific customer requirement (data residency, latency for a specific geography) driving it, since multi-region architecture roughly multiplies operational complexity for every system it touches.
- Build an internal platform before there are enough internal teams and use cases to justify the investment — a platform built for a future that doesn't materialize is a permanent maintenance cost with no return.
- Replace working infrastructure simply because a newer approach is the current industry conversation.
The reason this matters is opportunity cost, and it's worth being concrete about what that cost actually is. Every architecture change consumes engineering capacity that could have gone toward the product. It consumes product capacity, because engineering leadership's attention is a scarce resource, and a major architecture initiative pulls it away from product decisions. It consumes testing capacity, because any significant architectural change needs to be verified thoroughly — this is precisely where release confidence and regression testing discipline earn their keep, since an architecture change that isn't matched by proportional testing investment is how a well-intentioned migration turns into a multi-week incident. It consumes operational attention, because new infrastructure needs new monitoring, new runbooks, and new on-call familiarity. And it consumes management attention, because coordinating a cross-cutting architecture change across multiple teams is itself a significant leadership undertaking.
A useful way to sort proposed architecture changes is a simple four-way framework:
| Category | Meaning | Example trigger |
|---|---|---|
| Change now | A specific, observed pressure is actively costing the business (money, reliability, or velocity) today | A shared database is measurably the bottleneck for a feature the company needs to ship this quarter |
| Prepare | No urgent pressure yet, but a specific, foreseeable trigger is close enough to warrant groundwork | A known enterprise deal in the pipeline will require SOC 2 compliance, so access-boundary work should start now |
| Monitor | A plausible future concern with no current evidence it's materializing | "We might need multi-region eventually" with no current customer requesting it |
| Leave alone | Working, low-risk, and not blocking anything the company currently needs | A monolith serving current load comfortably, with a team that isn't struggling with it |
Consider a counterexample to keep this honest: a company processing sensitive health data decides, at a fairly small size, to invest early in strict service boundaries and strong data isolation — well before the team-coordination pressures described in Part II, section 1 would normally justify it. This looks, on the surface, like premature complexity. It isn't, because the driving pressure isn't organizational scale; it's regulatory and reputational risk, where a data breach at a small company can be an existential event, not just an inconvenience. The framework in this article isn't "always wait as long as possible." It's "match the investment to a real, current or clearly foreseeable pressure" — and for that company, the pressure was real and current, just not the pressure this article has spent the most time discussing.
Founder takeaway: The default answer to "should we change the architecture?" should be "what specific evidence, right now, makes this urgent?" If the honest answer is "none yet, but it feels overdue," that's a "prepare" or "monitor" situation, not a "change now" situation.
Part VI: Lightweight Architecture Decision Records
Most of the pain described throughout this article — the migration that touches more than expected, the boundary that turns out to be in the wrong place, the "why did we ever build it this way" conversation — is made worse by one specific, avoidable failure: nobody wrote down why the original decision was made.
An Architecture Decision Record, done well, doesn't need to be bureaucratic. It needs to capture enough that someone two years from now — possibly someone who wasn't even at the company yet — can understand not just what was decided, but why, and what would change the answer. A good lightweight record contains:
- Context. What was the situation when this decision was made — team size, product stage, known constraints?
- Problem. What specific question was being answered?
- Options considered. What alternatives were on the table, even briefly?
- Decision. What was chosen.
- Trade-offs. What was gained, and what was knowingly given up.
- Assumptions. What has to remain true for this decision to still make sense.
- Consequences. What this decision makes easier and harder going forward.
- Signals that would invalidate this decision. What specific, observable change would mean it's time to revisit this.
Three short, realistic examples:
ADR: Single Postgres database for all application data Context: Six-engineer team, pre-product-market-fit, single core product surface. Problem: Choose a primary data store for the initial product. Options considered: A document database for schema flexibility; a relational database with JSON columns for flexibility where needed. Decision: Postgres, using relational tables for core entities and JSON columns for genuinely variable data. Trade-offs: Slightly more upfront schema design than a schemaless store; in exchange, strong transactional guarantees and the ability to run complex queries without a separate analytics pipeline. Assumptions: Data volume and query complexity remain within what a single well-indexed instance can serve; we don't yet need to separate transactional and analytical workloads. Consequences: Fast iteration now; a future migration to a specialized analytics store is likely once reporting needs grow, and that migration is expected and acceptable. Signals that would invalidate this: Reporting queries measurably degrading transactional performance; a customer requirement for real-time analytics at a scale the current setup can't serve.
ADR: Extract billing into a separate service Context: 35 engineers, five product teams, billing logic currently lives in the shared monolith and is touched by three different teams for different reasons. Problem: Billing changes are increasingly risky and slow because multiple teams modify shared billing code without a clear owner. Options considered: Keep billing in the monolith but assign clear code ownership and enforce module boundaries; extract billing into an owned service with a defined API. Decision: Extract into a separate, owned service. Trade-offs: Adds network calls and versioning discipline between billing and its consumers; in exchange, gives the billing team full autonomy to change internal implementation without coordinating with three other teams for every change. Assumptions: The billing team has the capacity to build and maintain the operational tooling a separate service requires (monitoring, on-call, deployment pipeline). Consequences: Slower for changes that genuinely need to span billing and another domain in a single atomic step; faster and safer for changes confined to billing itself. Signals that would invalidate this: If most future billing changes turn out to require simultaneous, tightly coordinated changes in three other services, the boundary was drawn in the wrong place.
ADR: Adopt a managed authentication provider instead of building in-house Context: Twelve engineers, first enterprise customers asking about single sign-on support. Problem: Need to support enterprise authentication requirements (SSO, basic access controls) without diverting significant engineering time from the core product. Options considered: Build a custom authentication system with SSO support; adopt a managed identity provider. Decision: Managed identity provider. Trade-offs: Ongoing per-user cost and a dependency on the provider's availability and roadmap; in exchange, enterprise-grade authentication features without building and maintaining them in-house. Assumptions: The provider's pricing model remains reasonable at our expected growth in users; the provider's feature set continues to cover our customers' authentication requirements. Consequences: Fast path to supporting enterprise sales requirements now; a future provider migration, if ever needed, will be a substantial project because authentication touches every part of the product. Signals that would invalidate this: Provider pricing scaling in a way that becomes a significant cost driver relative to revenue; a major customer requiring an authentication capability the provider doesn't support and has no roadmap to add.
The value of these records isn't the documentation for its own sake — it's what they prevent. Without them, every re-evaluation of an old decision starts from scratch, often re-litigated by people who don't have the original context and may not even know what alternatives were considered. With them, a re-evaluation starts from "here's what we knew, here's what we assumed, here's what's changed" — a conversation that takes an hour instead of a quarter.
Founder takeaway: The cost of writing a short decision record is minutes. The cost of not having one is a future team re-deriving, under pressure, reasoning that used to be obvious.
Part VII: The Final Founder Framework
Every architecture decision in this article — monolith or modular monolith, one database or several, synchronous or asynchronous, tightly or loosely coupled APIs, how many managed services to adopt, how boundaries map to teams, when to change and when to leave alone — comes back to the same underlying discipline. Good software architecture for a startup is not about picking the technically superior option. It's about a specific, repeatable act of honesty:
A good architecture does not eliminate constraints. It puts the right constraints in the right place at the right stage.
Every system has constraints. The question is never whether you'll have them — it's whether you chose them on purpose, in service of what the company actually needs right now, or whether they were chosen for you by a decision nobody examined closely enough at the time.
That's why this article keeps returning to the same three questions, in the same order, for any architecture decision a founder or engineering leader faces:
What are we optimizing for now? Not what's fashionable, not what a company at ten times your size is doing, not what sounds impressive in a hiring pitch — what does the business actually need to be true in the next six to eighteen months. Speed of iteration while finding product-market fit is a different optimization target than reliability while serving enterprise contracts with uptime guarantees, and an architecture built for one will genuinely underperform for the other.
What are we deliberately giving up? Every choice that optimizes for one dimension of the trade-off map from Part I costs something on another dimension. A monolith that optimizes for iteration speed gives up some independent scalability. A broad microservices architecture that optimizes for team autonomy gives up operational simplicity. Naming the cost explicitly, at the time the decision is made, is what turns a hidden trade-off into an informed bet.
What future option are we preserving? This is the question most often skipped, because it requires imagining a future the company hasn't reached yet. A modular monolith preserves the option of clean service extraction later. A well-documented data model assumption preserves the option of a less painful migration when the business outgrows it. A managed service adopted with a clear understanding of its failure modes preserves the option of switching providers without a crisis. Reversibility is not free — it usually costs some speed or simplicity today — but knowing which options you're preserving, and which you're giving up, is the difference between an architecture that ages well and one that quietly becomes the ceiling on what the company can do next.
None of this replaces engineering judgment, and none of it removes the genuine difficulty of building software while a company is changing shape underneath it. What it offers instead is a way of asking better questions before the expensive decision gets made — and a way of recognizing, a year later, whether the constraints the company is living with were chosen on purpose or simply inherited from a moment that has already passed.
The architecture that served Fernwood well at nine engineers wasn't wrong at forty, and the service boundaries that made sense at forty weren't wrong at a hundred and thirty. Each was the right constraint for its stage. The only real failure would have been treating any one of them as permanent.