devShakib

Choosing a Stack You Won't Resent in Eighteen Months

How to choose a tech stack you won't regret: optimize for iteration speed, boring technology, hiring pools, and reversible vs one way door decisions for startups.

A client once asked me to review a stack decision before they committed. Three founders, no product yet, zero users. They had a whiteboard covered in Kafka, a service mesh, event sourcing, and a plan to run everything on Kubernetes across two regions. I asked one question: "What does the app actually do?" They spent four minutes answering, and none of the answer needed any of the whiteboard.

That team wasn't stupid. They were optimizing for a company that didn't exist yet, using a mental model borrowed from engineering blogs written by companies with a thousand engineers and a hundred million users. The stack you pick in month one has almost nothing to do with the problem those blogs solve. It has everything to do with how fast you, personally, can change your mind when the market tells you you're wrong. Which it will, roughly weekly.

I've made this mistake myself. Early in my career I built a "properly architected" backend for a product nobody had asked for yet — clean layers, a message queue, the works — and then watched the actual product requirement change so hard that all of that architecture became scaffolding around the wrong building. I've since flipped my default. The question I ask now isn't "is this the right design?" It's "how much does the wrong design cost me when I find out?"

This is a guide to choosing a technology stack you won't resent eighteen months from now — not by predicting the future, but by keeping your one-way doors few and your reversible doors clean. It's written for solo founders, early-stage startup teams, and technical CTOs making 0-to-1 architecture decisions under real uncertainty.

The real question is not "what scales" but "what lets me change my mind cheaply"

"Will it scale?" is the wrong first question, because at the pre-product-market-fit stage, scale is a problem you are lucky to earn. Most startups die of irrelevance, not load. You will rewrite your data model six times before you ever have enough traffic to stress a single Postgres box.

The right first question is: when I learn the thing I built is wrong, how expensive is it to throw away and rebuild? That cost is the real currency of a young startup. Call it the cost of changing your mind.

A stack optimizes for iteration speed when:

Notice none of that is about requests per second. It's about the radius of a change — how much of the system a single decision reversal disturbs. On a recent 0-to-1 build at Shpper, our whole backend was Firestore plus a handful of security rules. When we discovered our core assumption about how users grouped their data was backwards, the fix was an afternoon: rewrite a few queries, adjust the rules, migrate a few hundred documents with a script, done before lunch was cold. If that same feature had lived in three microservices with their own databases and a message bus, "we got the model wrong" becomes a two-week migration, a planning doc, and a standup where someone says the word "coordination" four times. Same wrong answer, wildly different bill.

A concrete way to measure the radius of a change

Whenever I'm tempted by a fancier architecture, I do a thought experiment I call the pivot dry-run. I pick the single most likely way the product could be wrong — the assumption I'm least sure of — and I trace what it would take to undo it in the stack I'm considering. For a consumer app I recently prototyped, the risky assumption was "users think in projects." I asked: if it turns out they think in tags, not projects, what changes?

Same pivot, and the boring stack won by a factor I could feel. That gap is the cost of changing your mind, made visible before I paid it. Run this dry-run on your two or three riskiest assumptions and you'll usually find the architecture that looked "more serious" is just more expensive to be wrong in.

Boring technology as a competitive advantage for a tiny team

There's a well-known idea in engineering circles: you get a small budget of "innovation tokens," and you should spend them on the thing that is actually your product, not on infrastructure. I believe this more the smaller the team is.

For a solo founder or a team of three, boring technology is not a compromise. It's leverage. Boring means:

Every exotic choice is a support contract you signed with yourself, payable in the exact moments you can least afford it — usually the night before a demo. I have debugged a bespoke event bus at 2 a.m. in Dubai while a client in another timezone waited. I have never once debugged Postgres at 2 a.m., because whatever I was doing wrong, someone had already written it up in 2013.

Boring also compounds. Postgres, a well-understood web framework, a managed host, a queue — these are skills that transfer between your projects and hires. The clever custom framework you built transfers nowhere. When you leave, it dies with you.

"Boring" is relative to your team, not to the internet

One caveat: boring means boring to you and the people you can hire, not boring in the abstract. For me, Flutter and Firebase are boring in the best sense — I've shipped them for years, I know their sharp edges, I can estimate work in them accurately. For a team of Rails veterans, Rails is the boring choice and Flutter is the risky one. Pick the technology where your team's intuition is already calibrated. Calibrated intuition is the thing you can't buy or fake.

The trap here is confusing popular with boring-for-you. A framework can have a million GitHub stars and still be exciting-and-dangerous for your specific team if nobody on it has shipped production code with it. Boring is measured in your team's scar tissue, not the technology's Hacker News rank. When I say "use the boring option," I mean: use the one where, when it breaks at midnight, someone on the team already knows the shape of the fix.

Where the exciting choice actually pays off (and where it bankrupts you)

I'm not arguing for never choosing something new. I'm arguing for spending the innovation token where it buys you a real edge, and hoarding it everywhere else.

The exciting choice pays off when the novelty is your product:

The exciting choice bankrupts you when it's plumbing nobody will ever thank you for:

Here's the test I use. For each shiny thing on the whiteboard, ask: if this worked perfectly and invisibly, would a customer notice? If the answer is no, it's plumbing, and plumbing should be the most boring, most managed thing you can buy. Save the courage for the pipe that has your logo on it.

There's a second, sneakier failure mode worth naming: resume-driven development. It's real, and it's expensive. The urge to put a trendy database or an orchestration framework on your architecture diagram because it looks good in a talk is not the same as needing it. The tell is that you can't finish the sentence "we chose this because our users..." without trailing off into a hypothetical. If the justification is about your career and not the product, that's the token being spent on the wrong pipe.

Hiring gravity: the stack decision that decides who you can recruit

This is the part first-time founders miss entirely, and it's the one that hurts eighteen months out. Your stack is a hiring filter you set on day one, before you've met a single candidate.

Every technology has a gravity — a size and shape of the talent pool around it, and a price. When you pick a stack, you're pre-selecting who will ever be able to join you, how long it takes to find them, and what they cost.

A few honest realities:

I run a small team, and I feel this constantly. Every non-standard decision I make is a tax on the next person I hire. Sometimes that tax is worth it. But I want to pay it consciously, not discover it when a great candidate bounces off our stack because nothing on it transfers to their next job — and yes, good engineers think about that. The best people you want to hire are precisely the ones who ask "will this experience be worth anything at my next job?" A stack that answers "no" filters out exactly the talent you were hoping to attract.

A quick way to sanity-check hiring gravity

Before committing, imagine you need to hire two engineers in your city (or remote in your timezone) within sixty days on a startup budget. For each core technology, ask:

If any core piece of your stack answers "very few / a month / no," you've found a place where you're trading hiring reality for something you'd better be sure is worth it. There's a corollary that saves you real money: the onboarding time of a stack is a recurring cost, not a one-off. You pay it again for every hire, every contractor, and every time someone comes back to code they wrote six months ago and no longer remembers. Boring stacks amortize that cost across the whole industry; bespoke ones make you pay it alone, forever.

The migration tax you're secretly signing up for

Nobody plans to migrate. Everybody migrates. The question is only whether you priced it in.

The migration tax is the accumulated cost of the decisions that are hard to reverse. It shows up as:

I keep Firebase costs at literally zero on real projects, and I'm a genuine fan — but I stay honest about the tax. Firestore's query model shapes how you write features. Lean into it too hard and "just move to Postgres later" becomes a rewrite, not a swap. So I keep the coupling shallow: business logic doesn't hardcode vendor quirks everywhere, and the read/write surface stays behind a thin repository layer I could re-point.

You don't avoid the migration tax by predicting the future. You avoid the expensive version of it by keeping the one-way doors few and the reversible doors clean.

// Not this: vendor SDK bleeds into every widget and service.final snap = await FirebaseFirestore.instance    .collection('orders')    .where('status', isEqualTo: 'open')    .get();// This: one boring seam you could re-point at another backend.abstract class OrderRepository {  Future<List<Order>> openOrders();}class FirestoreOrderRepository implements OrderRepository {  FirestoreOrderRepository(this._db);  final FirebaseFirestore _db;  @override  Future<List<Order>> openOrders() async {    final snap = await _db        .collection('orders')        .where('status', isEqualTo: 'open')        .get();    return snap.docs.map(Order.fromDoc).toList();  }}

That's maybe fifteen extra lines. It's the difference between a swap and a rewrite. I don't do this for every call — only for the surfaces I genuinely might want to move. The trick is knowing which ones those are, which brings us to the actual framework.

The seam is cheap insurance, but don't over-insure

A word of caution, because I've seen this abstraction habit metastasize. The repository seam above is worth it for the data access boundary and the external provider boundary — the two places where vendor lock-in genuinely hurts. It is not worth wrapping every library you touch in your own interface "just in case." That way lies a codebase where you've reinvented every dependency behind a façade nobody understands, which is its own bespoke abstraction and its own migration tax. The rule of thumb: put a seam where the thing on the other side is (a) hard to move and (b) plausibly something you'd want to move. If it fails either test, call the SDK directly and move on. Premature abstraction is just as expensive as premature scaling — it's the same mistake wearing a cleaner shirt.

A decision framework: reversible vs. one-way-door choices

Amazon has a useful idea here: some decisions are two-way doors (walk through, don't like it, walk back) and some are one-way doors (you're not coming back easily). Founders get this exactly backwards. They agonize over reversible choices and sleepwalk through the permanent ones.

Sort every stack decision into three buckets:

| Bucket | Examples | How much to deliberate |

| --- | --- | --- |

| Reversible (two-way door) | UI framework, styling approach, folder structure, most libraries, hosting provider | Decide fast. Pick the boring default. Move on. |

| Sticky (heavy but movable) | Primary database, auth system, API shape | Deliberate seriously. Keep the coupling shallow. |

| One-way door (near-permanent) | Core data model, your primary programming language, single vs. multi-tenant, whether you're event-sourced | Slow down. This is where your innovation token risk lives. |

The rule that follows:

Most stack regret I've seen comes from inverting this: bikeshedding CSS for a week, then picking a data model in an afternoon that dictated the next two years.

How to tell which door you're standing in

The buckets sound obvious until you're staring at a specific choice and can't decide which one it is. Two questions usually settle it. First: if this turns out wrong, does the fix touch code or data? Code-only reversals are cheap — you rewrite, you redeploy, users never know. Data reversals are expensive because you have to migrate live state without losing or corrupting it, which means they're at least sticky and often one-way. Second: does anything downstream depend on this being stable? Your programming language is a one-way door not because the language is hard to change but because every hire, every library, and every line you'll ever write depends on it. The stickiness of a decision is proportional to how much other stuff has come to rest on top of it. Choose the load-bearing pieces slowly; treat everything else as swappable and stop treating swappable choices as sacred.

Case study: three stacks I'd pick for three startups today

Same person, same year, three very different bets. The stack should look different every time, because the constraints do. The point of these examples isn't "copy my choices" — it's to show how the same principles produce completely different answers once you take the constraints seriously.

1. Solo founder, consumer mobile app, pre-revenue

Constraint: I am the entire team, and I need to ship, learn, and pivot on weekends.

The whole thing runs on $0 of infrastructure until there's traction worth paying for. If it dies, I lost time, not money. The unlock here is that every hour I don't spend on infrastructure is an hour on the actual product, and at a team size of one that trade is the whole ballgame.

2. Seed-stage B2B SaaS, small team, first paying customers

Constraint: real customers, real data, need to hire two engineers this year without gambling on a thin talent pool.

Here the stack optimizes for hireability and data integrity, because a B2B product lives or dies on trust and on my ability to grow the team. The Firestore-first approach from case one would actively hurt me here: relational integrity and complex queries are the daily job of a B2B backend, and forcing them through a document model is fighting the tool.

3. AI-native product where the model is the point

Constraint: the intelligence is the product, and the novel part actually earns its complexity.

Notice the pattern: exactly one exciting choice, everything else boring, and the exciting choice sits behind a seam. That last point matters more in AI than anywhere else, because the provider landscape reshuffles constantly — the interface that lets you swap models in a day is the difference between riding that churn and being trapped by whichever vendor you married in month one.

Key takeaways

Your first stack is a bet on your own iteration speed and hiring reality at your current stage — not an audition for a scale you haven't earned. Choose the boring, calibrated, reversible option by default, spend your courage on the one pipe that carries your logo, and keep the one-way doors few enough that being wrong stays cheap.