The version number is the public, user-facing release name of your app, like 1.2.0, while the build number is an internal counter that identifies each upload of that version. On iOS they are CFBundleShortVersionString for the version and CFBundleVersion for the build; on Android they are versionName for the version and versionCode for the build. Users see the version number; the stores use the build number to tell uploads apart. You raise the version for a new release and the build for every upload.
Short answer
The version number is what users see, such as 1.2.0, and the build number is the internal identifier the stores use to order and distinguish uploads. On iOS, the CFBundleShortVersionString is the version and CFBundleVersion is the build. On Android, versionName is the version and versionCode is the build. The rule of thumb: raise the version number for a release users should notice, and raise the build number for every single upload, including re-uploads. Semantic versioning is a recommended convention, not a store requirement.
What is the version number?
The version number is the human-readable release name of your app, the one shown on its store listing, typically written as three dot-separated integers like 2.4.1. It communicates to users, and to your own team, which release they are looking at. A change here signals a meaningful iteration: new features, fixes, or changes worth a new public version.
Because it is user-facing, the version number usually follows a convention people can read at a glance, most often semantic versioning. It is not a unique per-upload identifier; you can ship several internal builds that all carry the same version number while you test, changing only the build number between them.
What is the build number?
The build number identifies a single iteration of a given version, and it changes with every upload. Where the version number might stay at 1.2.0 across a week of testing, the build number increments each time you send a new binary, so 1.2.0 might have builds 1, 2, and 3 in sequence. Its job is to make each upload distinct.
This is the number the stores lean on to tell uploads apart and to prevent confusion between two binaries that share a version. It is largely a bookkeeping value: users rarely see it, but your release process depends on it being unique and increasing, because a duplicate or lower build number is one of the most common upload rejections. Most teams automate this, incrementing the build number in their build script or CI pipeline so no one has to remember to change it by hand before every upload.
iOS: CFBundleShortVersionString vs CFBundleVersion
On iOS, the version number is CFBundleShortVersionString and the build number is CFBundleVersion, both keys in your Info.plist. Apple describes CFBundleShortVersionString as the release version, a string of three period-separated integers for major, minor, and maintenance revisions. It is what appears on the App Store.
CFBundleVersion is the build version that identifies an iteration, released or unreleased. Within a single version, every build you upload to App Store Connect must have a unique, higher CFBundleVersion, which is why TestFlight lets you push build after build under the same public version. For a new App Store release, you raise CFBundleShortVersionString; for each upload, you raise CFBundleVersion.
Android: versionName vs versionCode
On Android, the version number is versionName and the build number is versionCode, both set in your Gradle build configuration. Google's documentation describes versionName as the string shown to users, which can be any format, though a major.minor.point string is typical. It is the only version value users see.
versionCode is a positive integer that Android and Google Play use internally to decide which build is newer, and it must increase with every release. Google Play rejects an upload whose versionCode is not higher than the last, and it uses the value to prevent downgrades. The greatest value Google Play allows is 2,100,000,000, which is far more headroom than any app will ever need.
iOS vs Android at a glance
The two platforms use different names for the same two ideas, plus a few different rules. The table below lines them up so the mapping is clear.
| Aspect | iOS (Apple) | Android (Google Play) |
|---|---|---|
| User-facing version | CFBundleShortVersionString, e.g. 1.2.0 | versionName, e.g. 1.2.0 |
| Internal identifier | CFBundleVersion (build number) | versionCode (integer) |
| Update-ordering key | Version string, then build number | versionCode only |
| Increase rule | Higher version per release, unique higher build per upload | versionCode strictly higher each upload |
| Format | Up to three dot-separated integers | versionName any string, versionCode integer up to 2,100,000,000 |
| Shown to users | Version string | versionName |
The key mental model is that each platform has one user-facing string and one internal identifier. Learn which is which, and the platform-specific rules, such as Android's strict integer increase, fall into place. If you ship the same app to both stores, it is common to keep the user-facing version number aligned across platforms while letting each platform manage its own build identifier, so a 1.2.0 on iOS and a 1.2.0 on Android feel like the same release to users even though their internal numbers differ.
Do you need semantic versioning?
Semantic versioning, the MAJOR.MINOR.PATCH scheme defined at semver.org, is a widely used convention, but neither the App Store nor Google Play requires it. You are free to version your app 1.0, 2026.06, or any scheme your team agrees on, as long as you meet each store's increase rules.
That said, semantic versioning is worth adopting because it communicates intent. A bump from 1.4.2 to 2.0.0 tells everyone this is a major release, while 1.4.2 to 1.4.3 signals a small fix. For libraries and APIs it is close to essential; for a consumer app it is a helpful, low-cost habit rather than a mandate.
Store requirements: what must increase, and when
The stores care about different things, so the requirements are not symmetric. On the App Store, a new public release needs a higher CFBundleShortVersionString than the last released version, and every build uploaded under a version needs a unique, higher CFBundleVersion. You can reuse a version string only while it has not been released; once a version is live, the next release must be higher.
On Google Play, the hard rule is on versionCode: it must be a strictly higher integer than any build you have uploaded before, or the upload is rejected. versionName has no uniqueness requirement at all, so two uploads can share a versionName as long as their versionCode differs. In both stores, the safe habit is the same: always increase the build number for every upload, and increase the version number when the release is meant to be visible to users.
Practical workflow checklist
Most versioning mistakes come from forgetting which number to change in a given situation. The table below covers the common cases so you do not have to guess.
| Situation | Version number | Build number |
|---|---|---|
| New test build, same release | Keep the same | Increase |
| New public store release | Increase | Increase |
| Hotfix or patch release | Bump the patch digit | Increase |
| Re-upload after a rejected build | Keep the same | Increase |
The single habit that prevents the most rejections is to increment the build number on every upload without exception, including a re-upload of a build you just rejected. Treat the version number as a deliberate decision and the build number as automatic.
What version numbers do not tell you
Version discipline keeps your releases orderly, but a version or build number says nothing about whether the binary behind it is safe. It is easy to ship build after build, each with a correctly incremented number, that all carry the same embedded API key, the same insecure network setting, or the same debuggable flag. The bookkeeping is perfect and the security is not.
A scanner like PTKD.com analyzes your .ipa or .apk and returns findings ordered by severity and mapped to OWASP MASVS, so each build you version and upload is also checked for secrets, insecure storage, and network misconfigurations. To be clear about the boundary: PTKD does not manage or set your version and build numbers, and it does not replace your release tooling. It adds the security check that version numbers, by design, do not provide.
What to take away
- The version number is the user-facing release name; the build number is the internal per-upload identifier.
- On iOS they are CFBundleShortVersionString and CFBundleVersion; on Android they are versionName and versionCode.
- Increase the build number for every upload; increase the version number for a release users should notice.
- Semantic versioning is a helpful convention, not a store requirement, and Android's versionCode must be a strictly higher integer each time.
- Numbering keeps releases orderly but not secure, so scan each build with PTKD.com before you ship.




