The architecture decisions that damage startups aren't the dramatic ones. Nobody's company failed because they picked Postgres over MySQL.
The damaging ones share a shape: they look completely reasonable when made, they cause no pain for a year, and then they become the reason a six-week feature takes six months. By the time the cost is visible, reversing the decision means touching everything.
These are the five we see most often, drawn from architecture audits for Indian startups. For each: what it looks like, what it actually costs, and what to do instead.
1. Microservices before you have a team to run them
What it looks like: the team splits the system into eight services before launch. It's what the engineering blogs describe, it feels professional, and it promises independent scaling.
Why it's appealing: microservices genuinely solve a real problem — letting many teams ship independently without coordinating every release.
Why it's wrong for you: that problem is organisational, not technical. Microservices are a solution to having too many engineers to coordinate. With five engineers, you don't have that problem. You've adopted the costs and none of the benefit.
What it actually costs:
- Every feature crossing a service boundary needs coordinated releases — the exact thing microservices were supposed to prevent
- A local development setup that takes a day to get running, which quietly slows every new hire forever
- Distributed debugging: a failure means correlating logs across services, so you now need tracing infrastructure to answer questions a stack trace used to answer for free
- Data consistency handled in application code, because you can't use a transaction across services
- Multiplied infrastructure cost — a small managed database per service adds up fast
We've seen four-engineer teams spending roughly a third of their capacity on the overhead of their own architecture.
What to do instead: build a well-structured monolith. Separate modules, clear internal boundaries, no reaching into another module's tables. When a specific part genuinely needs independent scaling or a different release cadence, extract that one thing.
The signal to split isn't traffic. It's when two groups of engineers keep blocking each other. Until then, boundaries inside one deployable are free to move — and you will move them, because your first guess about where they belong is usually wrong.
2. The tenancy decision you can't undo
What it looks like: you're building B2B software. First customer signs. You build for them, storing their data with no notion of which organisation a row belongs to. Second customer arrives and you spin up a separate deployment. Reasonable — it's isolated, it works.
Why it's wrong: by customer fifteen you're running fifteen deployments, fifteen databases, and fifteen schema versions that have drifted. Every release is fifteen migrations, some of which fail. Every bug report starts with "which instance?" You cannot build anything that spans customers — no cross-tenant analytics, no shared admin, no benchmarking feature.
What it actually costs: this is the most expensive item on this list, because retrofitting tenancy touches every table, every query, and every line of application code. It's a multi-month project during which you can ship almost nothing else, and it has to be done while every existing customer stays live.
What to do instead: decide tenancy on day one, before the first migration.
For most B2B SaaS, the answer is a shared database with a tenant_id on every table, enforced at a layer nobody can bypass — row-level security in the database, or a repository layer where it's impossible to construct an unscoped query. Enforced, not remembered. A convention that relies on developers adding WHERE tenant_id = ? will eventually leak one customer's data to another, and that's a company-ending kind of bug.
Genuinely isolated deployments are the right answer sometimes — a regulated customer contractually requiring it, or one paying enough to fund the operational burden. Choose it deliberately, not by accident, and put a hard limit on how many you'll run.
Also add the tenant to every log line and metric from day one. Debugging "it's slow for one customer" without that is miserable.
3. Cloud-native by default, before the unit economics support it
What it looks like: managed everything. Serverless functions, managed queues, managed cache, managed search, a managed database per service. Auto-scaling, no servers to patch. It's the modern default and it's genuinely pleasant.
Why it's wrong at small scale: request-priced managed services are excellent at large or spiky scale. At small, steady scale they're the most expensive way to run a modest workload, because you pay per request, per invocation, per GB moved, per service — and each service carries a fixed operational and cognitive overhead.
What it actually costs: we audited an IoT platform paying ₹30,000 a month for a workload that now runs comfortably on about ₹10,000 a year. A 97% reduction, no features lost. The architecture had been sized for a scale the business hadn't reached and might never reach.
The secondary cost is worse than the bill: unpredictability. A bug causing a retry loop turns into a five-figure surprise, and at seed stage that's a real problem.
What to do instead: for a small, steady workload, a properly sized VPS running your application, database, cache and queue is often 80–90% cheaper than the managed equivalent, and simpler to reason about. It is not less reliable — a well-configured server with backups and monitoring is very reliable.
Adopt managed services when you have a specific reason: genuinely spiky traffic, a real need for multi-region, or an operational burden you can measure. "It's what serious companies use" isn't a reason. The serious companies using it have traffic patterns and headcount you don't.
Whatever you choose, model your unit economics before you build. Cost per active user per month, at 10× today's volume. If nobody can answer that, the bill will find you.
4. Authorisation bolted on afterwards
What it looks like: you build authentication properly — login, sessions, password reset. Authorisation starts simple, because early on there's only one kind of user. Then you add if user.is_admin in a few places. Then a manager role. Then "managers can see their own team's data." Then a customer asks for read-only access for their auditor.
Why it's wrong: permission logic ends up scattered across hundreds of endpoints as ad-hoc conditionals. There's no single place that answers "who can see this?" so nobody can audit it, and every new role means finding and updating every check. Some get missed. Those are your security incidents.
What it actually costs: it blocks enterprise deals. Serious B2B customers ask for custom roles, SSO, and audit logs of who accessed what. If permissions are scattered conditionals, each request is months of work. We've watched startups lose deals because "can we restrict this team to only their region's data?" was a quarter of engineering work.
What to do instead: make authorisation an explicit layer from the start, even when there's only one role.
Define permissions as data, not code — a role has a set of permissions, a user has roles. Check permissions through one function every endpoint calls. Even if that function initially returns true for everyone, the shape is right, and adding real logic later is a change in one place rather than three hundred.
Two rules worth adopting immediately: deny by default — a new endpoint with no permission declared should refuse, not allow — and log every authorisation decision on sensitive resources. You'll need that audit trail for your first serious customer, and it's much easier to add now than to reconstruct later.
5. No seam between your code and your vendors
What it looks like: you integrate a payment gateway. Their SDK is well documented, so you call it directly from your order controller. Same with SMS, KYC, storage, email. Vendor calls sprinkled naturally through the business logic.
Why it's wrong: you will change vendors. Rates change, a provider declines your category, an SDK gets deprecated, you need a second provider for redundancy, or support becomes unbearable. In India this is close to certain across a company's life.
What it actually costs: the difference between a two-day change and a two-month one. When gateway calls live in forty files, switching means finding all forty, understanding each context, and re-testing everything — while payments keep running.
It also blocks a valuable pattern: running two providers simultaneously and routing between them for redundancy or better success rates. That's standard practice at scale and impossible without a seam.
What to do instead: define the interface your application needs — PaymentProvider with charge, refund, getStatus — in your own domain language, and put the vendor SDK behind an adapter implementing it. Your business logic talks to your interface and never imports the vendor SDK.
This isn't heavyweight abstraction. It's one small file per vendor, written once when you first integrate. The cost is an hour. The benefit arrives the day you need to switch.
Do it for anything you don't control: payments, SMS, email, KYC, storage, maps, and any AI provider. Especially AI providers — that market is moving fast and you'll change models more than once.
What these have in common
Every one is cheap to get right at the start and expensive to fix later. None causes visible pain in the first year. All of them are decisions people make by default rather than deliberately.
The practical version:
- Monolith with clean internal modules. Split when teams block each other, not when traffic grows.
- Decide tenancy before the first migration, and enforce it where it can't be bypassed.
- Model unit economics before choosing infrastructure. Right-size for today with a known path to scale.
- Authorisation as a real layer from day one, even with one role.
- An adapter around every external vendor.
Roughly a week of extra thought in the first month. Each one, skipped, costs months later.
Getting an outside opinion
If you're a year or two in and shipping is slowing down without anyone being able to say exactly why, it's usually one or two of these. They're diagnosable in a couple of weeks, and the fix is nearly always cheaper the sooner it starts.
We do fixed-scope architecture audits: two weeks, a written report with specific findings, prioritised by what's costing you most. No obligation to engage further.
Bengaluru-based, working with clients across India and globally.
Get in touch · See our services · WhatsApp: +91 9677749648
