In a React Native clinic-booking app whose date picker differs per platform, when do separate .ios and .android files beat inline Platform.select branches?
answer
- values versus structure
- different imports and dependencies
- top-level imports load on both platforms
- keep the contract in shared files
- split the smallest piece
basics
~20 sSplit into per-platform files when the implementations differ in structure, state or dependencies, not just values; keep inline Platform.select for a few differing values. Keep the shared contract, logic and types in platform-neutral files so the split stays thin.
solid answer
~40 sInline `Platform.select` is right when the component is the same and a few values differ, such as a height or an icon. Once the iOS and Android date pickers render different trees, hold different state, or depend on different libraries, a file split reads better: each file is plain code for one platform, and each bundle only contains, and loads, that file's imports. That last point is concrete: a module imported at the top of a shared file is bundled and evaluated on **both** platforms even if only one branch uses it, which matters for a library with platform-specific native code. To keep the split healthy I put `DatePickerProps` and the booking rules in shared files, split only the leaf component, and make sure CI type-checks and tests both platforms.
code
tsx · 28 lines// DatePicker.android.tsx
import { useState } from 'react';
import { Pressable, Text } from 'react-native';
import type { DatePickerProps } from './DatePicker.types';
import { useAppointmentDate } from './useAppointmentDate';
import { BookingDateDialog } from './BookingDateDialog';
export default function DatePicker({ value, minimumDate, onChange }: DatePickerProps) {
const [open, setOpen] = useState(false);
const { label, select } = useAppointmentDate({ value, minimumDate, onChange });
return (
<>
<Pressable accessibilityRole="button" onPress={() => setOpen(true)}>
<Text>{label}</Text>
</Pressable>
<BookingDateDialog
visible={open}
value={value}
onConfirm={(date) => {
setOpen(false);
select(date);
}}
onCancel={() => setOpen(false)}
/>
</>
);
}go deeper
Recall that React Native offers inline Platform checks for small differences and separate .ios and .android files for larger ones.
Explain the signals: values versus structure, and that top-level imports in a shared file are bundled and evaluated on both platforms.
Design the split: shared contract and logic, the smallest component split, suffix-free imports, and CI that type-checks and tests both platforms.
Set a codebase rule for when components graduate to per-platform files, and who owns each platform's variant, so splits stay deliberate rather than accidental.
## The two tools React Native offers two ways to make code differ by platform: - **Inline branching**: `Platform.OS` checks and `Platform.select` inside one file. - **Per-platform files**: `DatePicker.ios.tsx` and `DatePicker.android.tsx`, chosen by Metro when another file imports `./DatePicker`. React Native's own guidance is short: use the `Platform` module when only small parts of a component are platform-specific, and consider separate files when the platform-specific code is more complex. The interview question is what "more complex" means in practice. ## Signals that favour a file split - **Different trees.** The iOS picker renders an inline wheel inside a bottom sheet; the Android one renders a field that opens a dialog. Branching whole subtrees inline turns one component into two components sharing a file. - **Different state and effects.** One implementation tracks an open or closed sheet, the other a dialog lifecycle. Inline, every hook runs on both platforms even when its result is unused. - **Different dependencies.** An `import` at the top of a shared file is bundled for every platform and evaluated when the file loads, whichever branch runs later. If a library has native code for only one platform, or does work on import, a shared file drags it into the other app. With a split, each file's imports only enter its own platform's bundle. - **Different owners.** When separate people maintain the iOS and Android experiences, separate files mean separate diffs and fewer conflicts. ## Signals that favour inline branching - Only **values** differ: a padding, a font size, an icon name, a hit slop. - The difference is a **single conditional element**, such as a hint shown only on Android. - Most of the component's logic is shared and would have to be duplicated or extracted to split cleanly. | Question | Inline `Platform.select` | Per-platform files | |---|---|---| | What differs | values, one element | structure, state, dependencies | | Readability per platform | good while small | always one platform per file | | Imports on the other platform | bundled and evaluated | excluded | | Risk | branch sprawl | two files drifting apart | | Type-checking | automatic | needs a `.d.ts` or `moduleSuffixes` | ## Keeping a split healthy 1. **Share the contract.** `DatePicker.types.ts` exports `DatePickerProps`; both implementations annotate their props with it, so they cannot silently diverge. 2. **Share the logic.** Booking rules, such as blocking past dates and clinic closing days, live in a plain `useAppointmentDate.ts` hook both files call. The platform files contain presentation only. 3. **Split the smallest piece.** Split `DatePicker`, not `BookingScreen`. A screen that differs only because one child differs should not be duplicated. 4. **Check both platforms.** Type-check each platform (a shared declaration file or one `moduleSuffixes` config per platform), and remember React Native's Jest preset resolves the iOS file by default, so the Android implementation needs its own test run or direct tests. 5. **Import without the suffix.** Screens import `./DatePicker`; an import of `./DatePicker.ios` would ship the iOS picker to Android. ## The clinic app decision In the booking flow, the header spacing that differs by a few points stays as an inline `Platform.select`. The date picker is split: different trees, different interaction models, and a dependency only one platform needs. Its props and rules are shared, so a new requirement, say a maximum booking window, is implemented once in the hook and surfaced through the shared props, and both platform files fail to compile until they handle it. ## When to merge back A split has an ongoing cost: two files to review, test and keep aligned. Revisit it when the reason goes away. If the design team unifies the booking picker across platforms, or a single cross-platform component now covers both, fold the two files back into one and delete the declaration file or suffix configuration that supported them. A split that outlives its reason is just duplicated code with extra tooling. ## Mistakes interviewers listen for - Splitting a whole screen because of one differing child component. - Copying shared logic into both platform files, then fixing a booking bug in only one. - Believing inline branches keep the other platform's imports out of the bundle. - Testing and type-checking only the platform the team develops on.
- Doesn't Metro strip the unused Platform.select branch anyway, so bundle contents are the same either way?Metro does inline a static `Platform.select` and release builds fold away the dead branch, but that removes the branch's code, not the file's `import` statements. A module imported at the top of a shared file is still bundled and evaluated on both platforms. Only a file split keeps a platform-only dependency out of the other platform's bundle.
- How do you make sure the Android date picker is tested when React Native's Jest preset resolves iOS files by default?Either add a second Jest project whose platform-aware resolution defaults to `android`, so the same tests run against the Android files, or test each implementation by importing it directly. Shared logic in `useAppointmentDate` is platform-neutral and needs testing only once.
A file split is like printing two editions of a clinic leaflet, one per language, from a shared fact sheet; inline branching is one leaflet with a few words printed in both languages. Two editions pay off once the layout itself differs, and the shared fact sheet keeps the facts from drifting apart.
saying these in an interview costs you the question
- Any platform difference, even a padding, deserves its own .ios and .android files.
- Inline Platform.select keeps the other platform's imports out of its bundle.
- Split files should each contain their own copy of the booking rules.
- Splitting the whole screen is simpler than splitting the one differing component.
- Once files are split, TypeScript checks both platforms automatically.