skip to content

In React Native, why is Platform.Version a number on Android but a string on iOS, and how do you compare it safely?

level: middleimportance: should knowfreq 42%

answer

  1. two different notions of version
  2. Android: API level, a number
  3. iOS: systemVersion string like '18.2'
  4. parseInt the iOS major version
  5. narrow on Platform.OS first

basics

~20 s

On Android Platform.Version is the API level, a number such as 34; on iOS it is the system version string such as '18.2'. Compare Android numerically, parse the iOS major version with parseInt, and check Platform.OS first.

solid answer

~40 s

Each platform exposes the version it natively versions APIs by. On Android, `Platform.Version` is the **API level**, a number like `34`; the marketing release, such as `'14'`, is a string in `Platform.constants.Release`. On iOS it is the **system version string** from the device, such as `'18.2'`, also available as `Platform.constants.osVersion`. So I branch on `Platform.OS` first, compare Android API levels numerically, and on iOS parse the major version with `parseInt(Platform.Version, 10)` rather than comparing strings, because `'10.3' < '9.0'` is true for strings. In TypeScript the narrowing is required anyway: React Native types `Platform` as a union keyed by `OS`, so `Version` is `number | string` until you check it.

code

typescript · 11 lines
typescript
import { Platform } from 'react-native';

export function isAndroidApiAtLeast(level: number): boolean {
  return Platform.OS === 'android' && Platform.Version >= level;
}

export function isIOSAtLeast(major: number, minor = 0): boolean {
  if (Platform.OS !== 'ios') return false;
  const [maj = 0, min = 0] = Platform.Version.split('.').map((part) => parseInt(part, 10));
  return maj > major || (maj === major && min >= minor);
}

go deeper

for a junior

Recall that Platform.Version is a number on Android and a string on iOS, and that you check Platform.OS before using it.

for a middle

Explain what each value means, API level versus system version, where the Android release string lives, and why lexical comparison of iOS versions fails.

for a senior

Centralize version gates in tested helpers, let TypeScript's OS narrowing enforce the platform check, and prefer gating on the real capability when a version is only a proxy.

for a principal

Decide which OS versions the app supports and retire version gates when the minimum rises, so old branches do not accumulate as dead code.

## Two platforms, two notions of version `Platform.Version`, from React Native's `Platform` module, answers "which OS version is this?", but each platform answers in its own native currency: | | Android | iOS | |---|---|---| | `Platform.Version` | API level, a **number** such as `34` | system version, a **string** such as `'18.2'` | | Human release | `Platform.constants.Release`, a string | same as `Version` | | Also in constants | `Platform.constants.Version` | `Platform.constants.osVersion` | Both values are read once from a native constants module and cached, so reading them is cheap. ## Android: the API level Android versions its SDK by **API level**, a single integer that increases with every release. Features, permissions and behaviour changes are documented against an API level, so a number is exactly what you want to gate on: `Platform.Version >= 33`. React Native's docs stress that it is the API version, **not** the Android OS version; the release name users see, like `'14'`, is `Platform.constants.Release`. Comparing `Platform.Version` with a release number such as `14` is a classic bug, because API levels are far larger than release numbers. ## iOS: the system version string On iOS, `Platform.Version` is the device's system version string, for example `'17.4'` or `'18.2'`. React Native's docs show the safe way to use it: `parseInt(Platform.Version, 10)` for the major version. Pitfalls: - **String comparison is lexical.** `'10.3' >= '9.0'` is `false` because `'1'` sorts before `'9'`, so a check written against single-digit versions breaks at the next major version. - **`Number('17.4.1')` is `NaN`.** Patch versions have a second dot, so parse the parts you need rather than converting the whole string. - **Minor versions matter sometimes.** When they do, split on `.` and compare the major and minor numbers in order. ## Comparing safely 1. Branch on `Platform.OS` first; never compare `Platform.Version` without knowing which platform you are on. 2. On Android, compare the number directly with an API level. 3. On iOS, parse the major and, if needed, minor components as integers. 4. Wrap the logic in one small helper, such as `isAndroidApiAtLeast(33)` and `isIOSAtLeast(17)`, so the parsing lives in one place and is unit tested. ## Where version gates show up in practice Version checks are legitimate when the operating system's behaviour, not your code, changed between releases: 1. an Android permission or notification behaviour that only exists from a given API level, where the code path must differ below it; 2. an iOS system UI change that shifts a layout, where older versions need the previous spacing; 3. telemetry and support tooling, where the release string (`Platform.constants.Release` or `osVersion`) is logged so a bug report says which OS it came from. For the first two, write the gate once in a helper and name it after the capability, for example `supportsRuntimeNotificationPermission()`, not after the number. Readers then see the intent, and when the app's minimum OS version rises past the threshold, the helper, and every branch behind it, can be deleted in one change. ## Not the React Native version `Platform.Version` is the **operating system's** version. The version of React Native itself is a separate constant, `Platform.constants.reactNativeVersion`, an object with `major`, `minor`, `patch` and an optional `prerelease`. Mixing the two up, for example gating on `Platform.Version` when a behaviour changed in a React Native release, is a mistake worth naming in an interview. ## TypeScript narrowing React Native's TypeScript types model `Platform` as a union of per-platform shapes discriminated by `OS`. Before narrowing, `Platform.Version` is `number | string`, and `Platform.Version >= 33` does not type-check cleanly. After `if (Platform.OS === 'android')`, the compiler knows it is a `number`; after `if (Platform.OS === 'ios')`, a `string`. The types therefore enforce step 1 of the list above for you, provided you do not silence them with a cast. ## Mistakes interviewers listen for - Comparing Android's `Platform.Version` with a marketing version like `12` instead of an API level. - Comparing iOS version strings with `<` or `>=`. - Using `Number()` on an iOS version with a patch component. - Casting `Platform.Version as number` to make TypeScript quiet, which breaks on iOS. - Gating a feature on a version when the real question is whether a capability, such as a native module, is present in the build; version checks answer the OS question only.

  • Where do you find the Android release name, such as '14', in React Native?
    In `Platform.constants.Release`, a string. `Platform.Version` and `Platform.constants.Version` hold the API level number instead. Log the release for support or analytics context, but gate behaviour on the API level, because Android documents API changes against API levels.
  • Why does a React Native check like parseInt(Platform.Version, 10) <= 9 work on iOS but misbehave if run on Android?
    On iOS, `parseInt` on `'9.3'` yields `9`, the major version. On Android `Platform.Version` is already an API level such as `34`, so the same check silently compares API levels against iOS version numbers. Guard it with `Platform.OS === 'ios'`; TypeScript flags the unguarded call because `parseInt` expects a string.

saying these in an interview costs you the question

  • Platform.Version on Android is the marketing release such as 14.
  • iOS version strings can be compared with >= like numbers.
  • Platform.Version is a string on both platforms.
  • Number(Platform.Version) safely converts any iOS version.
  • Casting Platform.Version to number is a fine way to satisfy TypeScript.