Most mobile app complexity is not in the UI.
The UI is the part people can see, so it gets most of the attention. It is also the part that looks close to done first. You can make an app look decent fairly quickly. Buttons work. Lists load. Navigation feels fine. Everyone gets tempted to think the hard part is behind them.
That is usually when the actual work starts.
The real problems live underneath
The hard parts are things users only notice when they fail.
Sync is one of them. If the app can show data that changes elsewhere, you need to decide what wins when local state and server state disagree. You need retries, conflict handling, and a clear idea of what happens when the app is offline for a while and then comes back.
Auth is another one. Signing in is not just a screen. It is token refresh, session expiry, edge cases around account switching, and the awkward moment when a user has technically been logged out but still expects the app to feel usable.
State gets messy fast too. Mobile apps have local state, cached state, server state, navigation state, and usually a few special cases nobody wrote down properly. The UI can look simple while the state machine underneath is already fragile.
Permissions are never just permissions
On paper, permissions are straightforward.
In practice, they are a mix of OS behavior, user trust, feature gating, and error recovery. A camera permission prompt is not just a prompt. It is a decision point in the experience. If you handle it badly, the user does not think “the app needs permission”. They think the app is broken or annoying.
Location, notifications, photos, Bluetooth, background activity: all of them carry product consequences, not just technical ones. The app has to behave well when permission is denied, partially granted, revoked later, or handled differently on a new OS version.
Offline support is where assumptions die
Offline support sounds like a feature. It is really a set of uncomfortable questions.
What should the user see when the network disappears? What actions are allowed locally? What gets queued? What gets discarded? What happens if the user edits the same thing twice before syncing? What should the app say when it cannot be sure what happened?
These are not UI questions. They are product and systems questions.
The UI just has to expose the consequences without making the user hate the app.
Release problems matter more on mobile
Mobile apps also have a special kind of pain that web apps do not always share.
Once you ship, you are living with the app store and the installed base. Old versions stay around. Some people update quickly, some do not, and some devices never behave like the ones you tested on. You do not control rollout in the same clean way you do on the web.
That makes configuration, feature flags, backwards compatibility, and staged rollout part of the product, not just deployment details. A bug in a mobile release is often not “fix it and everyone gets the change”. It is “fix it, wait for store review, hope the rollout goes well, and then keep the old behavior working for a while”.
Why this matters
I think this is why mobile work is easy to underestimate.
The UI can be tidy while the app is still hard to change. It can feel polished while the sync model is weak. It can look complete while auth is flaky, state is inconsistent, and release management is doing quiet damage in the background.
If you want to make a mobile app genuinely good, spend enough time on the parts users never see. That is usually where the app either becomes reliable or stays annoying.
