Skip to content
All writing
6 min readMobile & architecture

The 5 product decisions that decide whether you scale cleanly

By Omer Khan

In short

Five early decisions are expensive to reverse: the data model, the boundary between your code and third-party services, authentication and tenancy, how you observe the system, and how you deploy and roll back. None needs a sophisticated solution on day one. Each needs a deliberate, cheap, good-enough version, because retrofitting them under real usage costs far more than getting them roughly right early.

Why these five

The usual advice for early products is to decide quickly and avoid building for scale you don't have. I agree with it, and it is the core of shipping an MVP fast. But it comes with a condition: a small number of decisions get more expensive to change with every user, every record and every released version of the app.

Those are the ones worth slowing down for. Not to build something elaborate, but to choose deliberately.

DecisionWhy it hardensThe cheap, good-enough version
Data modelEvery record written makes migration biggerName the core entities properly; stable IDs; timestamps
Third-party boundaryVendor calls spread through the codebaseOne module per vendor; your own types on the inside
Authentication and tenancyOwnership is baked into every row and queryA hosted auth provider; an owner on every record
ObservabilityYou can't see history you didn't recordError tracking, structured logs, a handful of events
Deploy and roll backOld app versions stay in useAutomated deploys, a staging environment, feature flags

1. The data model

Why it's hard to change later. Code can be replaced in an afternoon. Data can't. Once real records exist, every change to their shape is a migration that has to run against live data without losing anything. On mobile it's harder still: older versions of the app keep running on people's phones and keep expecting the old shape.

The cheap, good-enough version. Before writing code, write down the handful of core entities, what each one is called, and how they relate. Spend real time on the relationships: can a user belong to more than one organisation, can an order have more than one payment? Give every record a stable identifier that doesn't carry meaning, plus created and updated timestamps. Store facts rather than conclusions, so a status can be recalculated when the rules change. Use a relational database unless you have a specific reason not to; it keeps your options open.

Warning signs. One field doing two jobs. Columns named data or extra holding structured information that the code depends on. The same fact stored in two places that drift apart. Features being rejected because "the database can't represent that".

2. The boundary with third-party services

Why it's hard to change later. Renting solved problems such as payments, messaging, storage and AI models is the right call. The risk is in how you connect to them. If the vendor's SDK and data types are used directly across the codebase, the vendor's design becomes yours. Changing provider, or just absorbing a breaking change in their API, then touches everything.

The cheap, good-enough version. One module per vendor, and nothing else in the codebase talks to that vendor. Inside your own code, use your own types and names: your Payment, not the provider's object. Store the provider's identifiers next to your own records so the two can be reconciled. For mobile, route vendor calls through your server wherever you can. An SDK embedded in the app can only be changed by a release, and it puts keys on the device, which is the same reason AI calls belong on your server.

This is not an argument for abstracting everything. A thin wrapper is enough. Building a generic layer that could support any provider is the over-engineering version of the same decision.

Warning signs. Vendor type names appearing in screens and business logic. A provider outage taking down parts of the product that have nothing to do with it. Nobody able to say how long a switch of provider would take.

3. Authentication and tenancy

Why it's hard to change later. Who a user is, and which data belongs to whom, is assumed by every query in the system. If the product starts with "one user owns their own data" and later needs teams, shared workspaces or roles, that assumption has to be unpicked everywhere at once, with real customer data at stake. Changing how people sign in is also disruptive, since every existing account must be carried across.

The cheap, good-enough version. Use an established authentication provider rather than writing your own; this is one area where home-made is a liability. Then decide one thing explicitly: is the owner of data a person or an organisation? If there is any chance of teams or business customers, put an account or organisation record in from the start, even if every organisation has exactly one member, and stamp every owned record with it. Enforce access on the server, in one place, not screen by screen in the app.

That is far short of a full permissions system, which can wait until customers ask for it.

Warning signs. Access checks written separately in each endpoint. Customers sharing one login because the product has no concept of a team. A support request that needs a developer to run a query by hand. Any incident where one customer saw another's data.

4. How you observe the system

Why it's hard to change later. The tools themselves are easy to add. What you can't add is the past. Without records of what happened, the first serious bug is investigated by guesswork, and the first product question, such as where people give up during onboarding, has no answer until weeks after you start measuring.

The cheap, good-enough version. Three things, each a day's work or less:

  • Error and crash tracking on both the app and the server, with the release version attached.
  • Structured logs on the server with a request identifier, so one user's problem can be followed from start to finish.
  • A short list of product events, perhaps ten, covering the core flow: signed up, completed the key action, paid, came back.

Agree the event names before you instrument, and keep personal data out of logs.

Warning signs. You hear about failures from users before your own tools. Nobody can answer "how many people did this last week?" without a developer. Every bug report starts with "can you send a screenshot?". Hundreds of tracked events that no one looks at.

5. How you deploy and roll back

Why it's hard to change later. A team's release habits settle early. If releasing is manual and nerve-racking, releases get rarer and larger, which makes each one riskier. Mobile adds a constraint the web doesn't have: you can't recall a version that is already on someone's phone. A fix has to go through store review, and some users won't update for a long time.

The cheap, good-enough version. Automate the server deployment so it is one command or one merge, and make sure the previous version can be restored just as easily. Keep a staging environment that resembles production. Make database changes backwards compatible, so old and new code can run side by side. Version your API, and assume old app versions will keep calling it. Use the stores' gradual release options, put risky features behind a server-side flag, and build in a way to tell very old versions that they must update.

Warning signs. Releases happen only when one particular person is available. Nobody has ever practised a rollback. A server change breaks older app versions. Fixes are held back and batched because releasing is painful.

What to do with this

If you are starting, take half a day per decision, write down what you chose and why, and move on. If the product is already live, score yourself against the warning signs and start with whichever is getting worse fastest.

A closely related choice is how you build the app itself, covered in native vs cross-platform. The rest of the mobile architecture series goes deeper on each of these areas.

FAQ

Frequently asked questions

Doesn't thinking about scale early contradict shipping an MVP fast?
No. Building for scale means infrastructure for load you don't have, which is waste. These five decisions are about keeping change cheap. Each good-enough version takes hours or days, not weeks, and none of them involves extra servers, queues or services.
Which of the five matters most?
The data model. Code can be rewritten around good data, but poorly shaped data has to be migrated while the product is live and users depend on it. If you only have time to think carefully about one thing before building, make it the core entities and how they relate.
What if we have already got some of these wrong?
Most products have. Fix them in order of how fast the cost is growing: anything that gets worse with every new user or record first. Add observability early in that work, because you need to see the system clearly before you can change it safely.

Written by

Omer Khan

Omer Khan is the founder and director of Afiniti Global and founder of XO Ai. He works across product direction, practical AI advisory and software delivery, with a software background since 2013.

Need this built? AI app development.