Software Architecture·6 min read

Architecture decisions that look fine at launch and expensive a year later

Defox AI Team·

Some technical choices are cheap to get right on day one and expensive to fix once real usage arrives. The tricky part is that these decisions rarely look risky at the time — they look like sensible, pragmatic shortcuts, because at launch scale, they genuinely are fine. The problem shows up only after the product has customers, data, and history that make the "quick fix" much harder to justify undoing.

Global network
Architecture decisions scale with your growth.

Here's a handful of the ones we see most often, and what to do about each instead.

1. Storing everything as loosely-typed JSON blobs

Early on, a flexible JSON column feels like a smart way to avoid rigid schema decisions before you know exactly what data you'll need to store. It ships fast and adapts easily to changing requirements in the first few weeks.

A year later, that same flexibility means you have no database-level guarantees about what's actually in that column, no easy way to query or index specific fields at scale, and a growing pile of inconsistent shapes — because different code paths wrote slightly different structures into the same blob over time, and nothing ever enforced consistency.

**The better default:** use a real schema for anything you know you'll query, filter, or report on. Reserve JSON columns for genuinely unstructured, rarely-queried metadata — not as a general substitute for schema design.

2. Hardcoding configuration that will obviously need to change

Rate limits, feature flags, pricing tiers, email templates — early on it's faster to hardcode these directly in application code than to build a configuration system for values that "probably won't change much."

They always change more than expected, and every change to a hardcoded value means a code deploy, a code review, and engineering time spent on something that should be a business decision, not an engineering task. We've seen teams burn a full sprint, a year in, building the configuration layer they should have built from the start — purely to stop needing an engineer every time marketing wants to adjust a discount threshold.

**The better default:** anything a non-engineer might reasonably want to change — pricing, limits, copy, feature toggles — belongs in a simple config table or environment-driven system from day one. This costs almost nothing extra upfront and saves recurring engineering time indefinitely.

3. Synchronous processing for anything that will eventually need to scale

A signup flow that synchronously sends a welcome email, creates a CRM record, and triggers three other side effects, all before returning a response to the user — this works fine when it takes 200 milliseconds and nothing ever fails. It becomes a serious problem once one of those three side effects starts occasionally timing out, and now your core signup flow — the most important conversion point in your product — is failing because a third-party CRM integration had a bad five minutes.

**The better default:** anything that isn't strictly required to answer the current request should go through a queue or background job, not a synchronous call chain. This is more setup work initially — you need a job runner and some retry logic — but it decouples your critical path from the reliability of every downstream service you integrate with.

4. No clear ownership boundary between features

In the early stages of a product, it's common (and often correct) for code to be loosely organized — a handful of files, minimal separation, everything reachable from everywhere. This is fine when one or two people hold the whole system in their heads.

A year later, with more features and more contributors, that same looseness means every change risks breaking something unrelated, because nothing enforces which parts of the code are allowed to depend on which other parts. Teams end up afraid to touch core files because nobody's sure what else relies on them.

**The better default:** you don't need microservices or a formal modular architecture from day one, but establishing even loose boundaries — this folder owns billing logic, this one owns user data, cross-boundary calls go through a defined interface — pays off enormously once more than two or three people are working in the codebase simultaneously.

5. Skipping database indexes because the table is small

An unindexed column is invisible at 500 rows. Every query is fast, because everything fits comfortably in memory and the query planner barely has to work. Nobody notices the missing index because there's nothing yet for it to matter against.

At 500,000 rows, queries against that same column start taking seconds instead of milliseconds, and by the time it's noticed, it's usually because a customer is actively complaining about a slow feature in production — which is a much worse time to be adding an index than when the table was created.

**The better default:** index the columns you know you'll query by (foreign keys, anything in a `WHERE` clause, anything you'll sort by) at the time you create the table. Adding an index later on a large, live table is riskier and slower than adding it upfront on an empty one.

The pattern behind all five

Every one of these decisions has the same shape: it's genuinely fine at the scale and complexity you have on day one, and it becomes expensive specifically because of growth — more data, more contributors, more third-party dependencies, more business requirements that didn't exist at launch. None of them are mistakes in isolation. They become mistakes only in hindsight, once the product has grown past the scale where the shortcut was reasonable.

The fix isn't "always build for scale you don't have yet" — that's its own well-known trap, and over-engineering for imagined future scale is its own way to waste time and budget. The fix is knowing which of your early decisions are cheap to change later and which ones aren't, and spending your caution on the second category specifically. Schema flexibility, ownership boundaries, and indexing strategy are expensive-to-reverse. Which UI library you picked or how you named your API routes are not. Most engineering judgment on a young product comes down to correctly sorting decisions into those two buckets.

*Not sure which of your early technical decisions are quietly setting you up for a hard year-two fix? [Book a call](#) — a second set of eyes on this early is a lot cheaper than the alternative.*

Let's Talk

Have a project in mind already?

Skip the waiting room — let's talk about what you're building.