Version number / build number
Two numbers per release: the version humans read (1.4.2) and the build number the store uses to tell your uploads apart.
See it
What it is
Every release carries two numbers doing two different jobs. The version is marketing-facing and human-readable ('1.4.2'), shown on your store page and in your release notes. The build number is bookkeeping: a counter that has to go up on every single upload so the store can tell two attempts at the same version apart. On iOS these are CFBundleShortVersionString and CFBundleVersion; on Android they are versionName and versionCode.
The rule that bites people is real, but it is not the same rule on both platforms. On iOS, CFBundleVersion has to increase within a given marketing version: upload 1.4.2 build 31, spot a typo, re-upload as build 31, and App Store Connect rejects it as a duplicate. Start a new marketing version and the build counter is allowed to restart at 1, which is why a lot of teams keep per-train numbering. On Android, versionCode is a single plain integer that must increase across the whole life of the app, no restarts, no reuse, and a rollout locks in everything at or below it forever. Android's versionName is display text only and the store does not order releases by it. Wire the increment into CI (a commit count, a timestamp, or the CI run number) so no human has to remember.
Second trap: you cannot go backwards on the number the store actually orders by. Ship iOS 2.0.0 by accident and 1.9.x is dead to you, because App Store Connect will not take a lower marketing version. Ship Play versionCode 400 by accident and 399 is gone the same way. Same reason a hotfix after 1.4.2 has to be 1.4.3, not 1.4.2 again with a fresh build.
Ask AI for it
Set up two-part app versioning for my iOS and Android builds. Keep a single source of truth for the marketing version in semver form (MAJOR.MINOR.PATCH) and inject it into CFBundleShortVersionString and Android versionName at build time. Derive the build number automatically from the CI run number so it strictly increases on every upload, and map it to CFBundleVersion and Android versionCode. Add a CI check that fails the build if the resulting version plus build pair has already been uploaded, and print both numbers in an in-app 'About' screen for bug reports.