One product, three platforms, no third team
Shipping on web, iPhone and Android with a small team. The differences that matter are the seams, not the screens.
If you are a small team shipping a product on the web, iPhone and Android, the tempting plan is three native applications built by three teams who coordinate well. At small scale this does not work, and the failure is not primarily about cost. It is that three codebases produce three slightly different products, and the differences accumulate in the places you are least able to see.
The alternative is not a compromise. It is a different decomposition of the problem.
The seams, not the screens
Rendering is the part people worry about and the part that matters least. A list is a list. Modern cross-platform rendering is good enough that most users cannot identify what a screen was built with, and the ones who can mostly do not care.
What genuinely differs between platforms is the seams — the points where your product meets the operating system:
- Identity and sign-in. Platform sign-in conventions differ, and some are effectively mandatory depending on what else you offer.
- Payments. The rules about what must go through platform billing, and what may not, are specific, consequential and occasionally counter-intuitive.
- Notifications. Permission models, delivery guarantees and the user's expectations around them are all different.
- Files, camera and storage. Permission prompts, lifecycles and failure modes vary meaningfully.
- Background behaviour. What your app is allowed to do when nobody is looking at it is one of the sharpest differences between platforms.
Structure the codebase around that observation. A shared core carrying the product's actual logic, and deliberately thin platform edges where the seams live. When something is genuinely platform-specific it goes in the edge, visibly, rather than being smeared through the core as a conditional.
Decide early what must be native
The useful exercise is to list everything in the product and mark the small number of things where a native implementation materially changes the experience. For most products it is a short list — usually something involving the camera, something involving performance under sustained input, and something involving a platform integration users expect.
Build those natively. Share everything else. The mistake is making this decision implicitly, feature by feature, under deadline pressure — which reliably produces a codebase where nobody can say which parts are shared and which are not.
One product, three platforms, no third team. The constraint is not the number of platforms. It is how many genuinely different products you are willing to maintain.
App review is a product constraint
Treating store review as paperwork at the end is the most common way a shipping date slips without warning.
Review is a constraint on the product, the same way a browser's capabilities are. It affects what you can charge for and how. It affects what you must disclose. It affects what an account can do, what a guest can do, and whether an account can be deleted from inside the app. These are design decisions, and they are considerably cheaper to make at the start than in response to a rejection.
The practical version: before building a feature that touches payments, accounts or user content, read the current guidelines for that specific area. Not the whole document, and not from memory — they change.
Platform parity is not a goal
It is tempting to insist every feature exists everywhere simultaneously. This is expensive and usually unnecessary.
Users do not experience your product across three platforms at once. They experience it on the one they are holding. What they notice is whether the thing they came to do works well there — not whether some capability exists on a device they do not own.
That frees you to ship where it makes sense first, and to let a platform lag deliberately when the cost of parity is high and the demand is low. The discipline is making that a decision you record, rather than a drift you discover later.
The actual discipline is subtraction
Shipping one product across three platforms with a small team is mostly an exercise in refusing things:
- Refusing platform-specific features that do not earn their maintenance cost.
- Refusing to fork the core for a problem that belongs in an edge.
- Refusing parity for its own sake.
- Refusing to treat review as a formality.
None of that is technically difficult. It is organisationally difficult, because every individual exception is reasonable and the cost only shows up in aggregate, months later, as a codebase that three people understand in three different ways.
The teams that ship well across platforms are not the ones with the cleverest abstraction. They are the ones with the shortest list of things that are allowed to be different.