Mobile developer interview questions
Questions an interviewer can ask a Mobile developer, grouped by years of experience. Open one to see the strongest answer and why it works.
48 of the 148 questions our bank holds for this job. The rest are in the app.
By subject
- Mobile Fundamentals
- iOS
- Android
- Cross-Platform
- Networking
- Storage
- Device Capabilities
- Mobile Testing
- Performance
- Security
- Release
0–1 years
A form’s submit button sits under the home indicator and the keyboard. What is wrong?
The strongest answer: The layout ignores safe area and keyboard insets — I inset the content and keep the primary action visible while typing
Why it works: Exactly. Safe area and keyboard avoidance are part of shipping a phone UI — web or native.
- Respect safe areas.
- Avoid the keyboard; do not cover the action.
- Do not shrink type to cheat the layout.
- This is the same judgement as a responsive web form.
The interviewer asks what happens when the user backgrounds your app. Which answer is strongest?
The strongest answer: The process can be paused, then killed at any time — I persist anything I would be sad to lose, and I do not assume timers or network calls finish
Why it works: Exactly. Lifecycle events are hints, not guarantees. Persist early; treat death as normal.
- Backgrounding is not a promise the process lives.
- Persist anything you cannot recreate the moment you leave the screen.
- Do not start unbounded work in a pause callback.
- Use the platform's background APIs when work must outlive the UI.
A web teammate asks you to “just shrink the website” into the app WebView. What do you say?
The strongest answer: A phone has different input, density, offline, and platform chrome — we adapt layout and touch targets; we do not pinch a desktop page and call it an app
Why it works: Exactly. Responsive is not a scaled desktop. Touch, safe areas, and offline still apply.
- Design for the handset, not a scaled desktop.
- Touch targets and safe areas are required.
- WebView can host a flow; it is not a substitute for platform basics.
- Same judgement a frontend engineer should already have.
The UI freezes for a beat every time a row appears. What is the most likely cause?
The strongest answer: Work on the main thread during bind or layout — JSON parse, disk, a large image decode, or a sync query
Why it works: Exactly. Frames drop when the UI thread is busy. Move the work off, then re-bind.
- Jank is usually main-thread work.
- Profile bind, layout, and decode.
- Move I/O and decode off the UI thread.
- Re-bind when the result is ready.
A crash report says “unexpectedly found nil while unwrapping an Optional value” on the profile screen.
What does that message mean, and what do you change?
The strongest answer: An optional that was nil got force-unwrapped. I handle the nil case instead — bind it or give it a default — and keep the force only where nil genuinely cannot happen
Why it works: Right — an optional is the compiler telling you a value may be missing; forcing it is a promise you have to keep.
- Nil was forced; handle it instead.
- Bind the optional or default it.
- Force only a true invariant.
- Fix where the nil comes from.
A bug “only happens on a phone.” What do you do first?
The strongest answer: Reproduce on a real device (or a close farm device) with the same OS, density, and permissions — simulators miss sensors, memory pressure, and store-signed behaviour
Why it works: Exactly. The simulator is a development tool, not the product. Device, OS, and signing matter.
- Reproduce on hardware when the bug is device-shaped.
- Match OS, density, and permission state.
- Do not treat “works in simulator” as done.
- Keep a small physical matrix even if you also have a farm.
A list of a few thousand rows stutters when you scroll. What do you check first?
The strongest answer: That rows are recycled / reused, that I am not inflating a new heavy view per bind, and that images are sized for the cell not the original file
Why it works: Exactly. Recycling, cheap bind, and correctly sized images fix most first-year jank.
- Reuse cells; do not rebuild the tree per row.
- Bind must be cheap — no decode, no layout thrash.
- Decode images to the cell size.
- Measure before you rewrite the architecture.
A teammate enables bitcode “for App Store optimization.” What do you say in 2026?
The strongest answer: Bitcode is retired — Apple no longer accepts or recompiles it. We ship a normal device slice and let App Store thinning do the rest
Why it works: Exactly. Bitcode was removed. Do not resurrect a dead flag.
- Do not enable bitcode.
- Ship standard architectures.
- Thinning is the store’s job now.
- If a tutorial still says ENABLE_BITCODE, it is stale.
A Flutter screen rebuilds the whole tree on every keystroke. What is the first fix?
The strongest answer: Lift only the state that must change, rebuild the leaf (or use a targeted widget), and keep expensive work out of build()
Why it works: Exactly. build() should be cheap and local. Rebuild the thing that changed.
- Keep build() cheap.
- Rebuild the smallest widget that needs it.
- Do not parse or decode in build().
- Profile a release build before you rewrite the app.
A new Activity is created after a rotation. Where should you put “load this screen’s data”?
The strongest answer: In a lifecycle-aware place that survives recreation — a ViewModel / equivalent — and re-bind the UI when the new view appears
Why it works: Exactly. Recreation is normal. Own the data above the view; re-bind when the view comes back.
- Treat recreation as the happy path.
- Hold screen state above the view.
- Re-bind when the new view is ready.
- Do not hide process death behind a static.
A React Native list reshuffles items and several rows show the wrong image. What do you look at first?
The strongest answer: The key / identity of each row — if I used the index, recycled views will keep the wrong child state
Why it works: Exactly. Identity is how reuse stays honest. Index keys are a classic RN (and React) miss.
- Stable keys, not indexes.
- Virtualized lists reuse views — identity matters.
- Do not “fix” it by mounting everything.
- Same rule as React on the web.
You are uploading a new Play app. What artifact do you send?
The strongest answer: An Android App Bundle (.aab) so Play can generate optimized APKs per device — not a single fat APK I built on my laptop for every ABI
Why it works: Exactly. New Play listings take an AAB. Let the store slice density and ABI.
- Ship an AAB to Play.
- Let Play generate per-device APKs.
- Sign with the upload key Play expects.
- Keep a mapping file if you will read crashes.
1–3 years
A step-counting screen keeps draining the battery long after the user has moved on to another screen. What do you check first?
The strongest answer: Whether the sensor listener is unregistered when the screen stops. Registration follows the screen’s lifecycle — start when it becomes visible, stop when it does not — and the sample rate should be the slowest that works
Why it works: Right — a sensor you forgot to stop is a background drain with no UI, and the rate is the other half of the cost.
- Register with the screen, unregister with it.
- Start when visible, stop when hidden.
- Pick the slowest usable rate.
- Never leave a sensor running.
Images in a grid still spike memory after you added a cache. What did you miss?
The strongest answer: I am decoding / caching the original bytes at full resolution — I should decode to the view size and let the cache evict under memory pressure
Why it works: Exactly. Cache the right size. A 12 MP decode into a 100 pt cell is the spike.
- Decode to the displayed size.
- Let the cache evict.
- Watch memory, not only disk hits.
- Do not pin gigabytes of bitmaps.
A new screen in an Android app — a new Activity, or a destination inside the one you have? How do you decide?
The strongest answer: Default to one Activity hosting destinations: shared navigation, one back stack and scoped view models come free. A separate Activity is for a separate entry point, such as a share target or a launcher shortcut
Why it works: Right — the default is the host, and the exception is an entry point the system itself launches.
- One host, many destinations.
- Default to a single Activity.
- Separate Activity for separate entry points.
- Keep one back stack.
UI tests pass on a CI emulator API 35 and fail on a store-sideload Android 12 phone. What is the lesson?
The strongest answer: The matrix is part of the product — I run a slice of UI tests on the oldest OS and a recent device, not only on the image that is convenient for CI
Why it works: Exactly. Green on one image is not coverage. Claimed OS versions need a run.
- CI image ≠ oldest claimed OS.
- Keep a cheap extra API level in CI if you can.
- Do not “fix” failures by dropping support in silence.
- Quarantine flakes; do not delete the suite.
The OS killed the app overnight. The user returns via a notification to a checkout screen. What should happen?
The strongest answer: I restore enough navigation and form state to put them back in checkout — or I land them somewhere honest if the session or cart is gone — and I tested process death, not only backgrounding
Why it works: Exactly. Death is common. Restore what you persisted, fail honestly, and test the kill.
- Test process death, not only onPause.
- Persist the back stack or a route + ids.
- If session/cart is gone, say so.
- Notification → dead process is a required path.
You have a cloud device farm. What do you still keep on the desk?
The strongest answer: At least one recent iPhone and one recent Android, plus the oldest OS you claim — farms miss cables, push provision, and “this build feels janky”
Why it works: Exactly. Farms are coverage. Hands are judgement and the weird radios.
- Farm for breadth.
- Hands for feel, push, and cables.
- Keep the oldest OS you advertise.
- Do not skip a whole OS family.
A feed can be tens of thousands of items. How do you load it?
The strongest answer: Page or cursor-load, virtualize the window, and cancel in-flight work for rows that scrolled away — do not fetch the world into an array
Why it works: Exactly. Window the data and the views. Prefetch a little; do not ingest the corpus.
- Cursor / page the API.
- Virtualize the viewport.
- Cancel work for offscreen rows.
- Same idea as a virtualized web list.
The app keeps a few thousand orders offline in one JSON file it rewrites on every change. When do you move to a local database?
The strongest answer: When you need to query, sort or page part of it, update one record without rewriting everything, or survive being killed mid-write. A database gives you indexes and transactions; a file gives you neither
Why it works: Right — the trigger is the shape of the access, not the size. Partial reads, partial writes and durability are the tests.
- Files cannot query or transact.
- Move when you query or page.
- Update rows, not whole files.
- Transactions survive a kill.
A production crash is a sea of `a.b.c`. What did the release miss?
The strongest answer: Uploading Proguard / R8 / dSYM mapping for that exact version — mappings are per build, not “the last one we remember”
Why it works: Exactly. A mapping (or dSYM) for the build you shipped is how you read the crash.
- Upload mappings for every store build.
- Match version + build number.
- Keep R8 / strip; keep the map.
- dSYM for iOS, mapping.txt for Android.
The feature is “attach one photo.” Which permission story is right in 2026?
The strongest answer: Use the system photo picker / share sheet so I do not need broad storage or full photo-library access — ask for more only if the product truly needs a gallery browser
Why it works: Exactly. Least privilege. The picker is the default; broad access is a review and trust cost.
- Prefer the system picker.
- Ask for library access only when you browse it.
- Purpose strings must match.
- Do not hoard the camera roll.
TalkBack reads the price, then the image, then the title, then a decorative star. What do you change?
The strongest answer: Traversal order and headings so the name comes first, hide the decorative star, and group the price with the product — I verify on a device, not only in a layout preview
Why it works: Exactly. Order, grouping, and hiding decoration are the work. Confirm with a screen reader.
- Name, then details.
- Hide decoration.
- Group related bits.
- Test with TalkBack / VoiceOver, not a screenshot.
iOS: you need an IDFA for a paid ads SDK. When do you show App Tracking Transparency?
The strongest answer: After a real explanation, before the SDK reads the identifier — and I have a product that still works if they say no
Why it works: Exactly. ATT is a gate. No means no, and the app must still function.
- Explain, then prompt.
- Honour a deny.
- Do not fingerprint around ATT.
- The app works without IDFA.
3–5 years
A designer’s card truncates at the largest Dynamic Type / font-scale. What is the bar?
The strongest answer: The task still works: wrap or reflow, do not clip essential text, and do not require a horizontal pan to read a price — I test the largest size we claim
Why it works: Exactly. Largest type is a supported configuration. Reflow beats pixel-perfect Figma.
- Reflow; do not clip the task.
- Test the largest font scale.
- Do not lock type to match a mock.
- Same bar as a responsive web page.
Cold start has drifted to several seconds. Five SDKs initialise before the first screen appears.
How do you get it back?
The strongest answer: Measure what runs before the first frame, move anything that frame does not need off the startup path — lazily on first use, or after the screen is drawn — and add a startup check to CI
Why it works: Right — measure, defer, then guard. Without the guard the same five seconds come back one dependency at a time.
- Measure before the first frame.
- Only first-frame work at startup.
- Initialise lazily or after paint.
- Guard startup time in CI.
You are designing a checkout that must survive process death and a language change. What is the spine?
The strongest answer: A persisted, versioned UI model + a navigation restorer + idempotent checkout APIs — views are disposable; the model and the server are the source of truth
Why it works: Exactly. Disposable views, persisted model, idempotent pay. Language change is just another recreation.
- Persist a versioned model.
- Restore navigation from that model.
- Make pay idempotent.
- Treat recreation (death, rotate, locale) as one problem.
Who gets the build before external TestFlight / open testing?
The strongest answer: An internal track that matches store signing as closely as we can, on the matrix we claim — I do not use a debug-signed build as the last gate
Why it works: Exactly. Internal + store-like signing + the matrix. Then wider.
- Internal track, store-like artifact.
- Exercise App Links, pay, and push there.
- Then phased / external.
- Do not last-gate on debug.
Three apps disagree on 'primary' blue. How do you fix it as a system?
The strongest answer: One token source — Style Dictionary or CSS variables — consumed by web and native; no raw hex in features
Why it works: A token pipeline is how brands stay one product. Features consume names, not paint.
- Tokens are the contract.
- Author color once.
- Emit CSS variables and native tokens.
- Ban hex in feature code.
A “perfectly virtualized” list still drops frames on mid phones. What do you inspect next?
The strongest answer: The size of the overscan window, span counts, placeholder vs real bind cost, and whether I am measuring every row on the UI thread — virtualization is necessary, not sufficient
Why it works: Exactly. Window, span, and measure cost are the next knobs. Profile them.
- Profile overscan and measure.
- Keep placeholders cheaper than the real row.
- Watch span / grid costs.
- Do not “fix” frames by changing the store device floor first.
A cross-platform codebase has is-iOS / is-Android checks scattered through the screens, and a fix for one platform keeps breaking the other. How do you organise it?
The strongest answer: Put the difference behind one interface the shared code calls, with the platform implementations at the edge, so a screen reads the same on both and each platform has exactly one place to change
Why it works: Exactly — shared code stays platform-blind, and the platform detail lives where the platform team can own it.
- Hide platform detail behind an interface.
- Shared code stays platform-blind.
- Implementations live at the edge.
- Never fork the screen.
Product asks for location “always” so a map feels snappier. What do you do?
The strongest answer: Challenge the need: while-in-use is the default; always is a review, battery, and trust cost with a visible disclosure — I will not add it for a snappier first paint
Why it works: Exactly. Always-location is a product and policy decision, not a performance trick.
- Default to while-in-use.
- Always needs a real feature and a disclosure.
- Foreground services need an honest notification.
- Do not use location as a cache for UI speed.
Camera grant rate is poor and denied-forever is high. How do you work it?
The strongest answer: I treat it as a funnel: pre-prompt education, ask in context, a settings recovery path, and I measure grant / deny / forever — I do not spam the system dialog
Why it works: Exactly. Education, context, recovery, measurement. No second system dialog.
- Measure the permission funnel.
- Educate, then ask.
- Deep-link to settings after forever.
- Do not fake system UI.
Crash-free is fine but one cluster is 30% of reports. How do you triage?
The strongest answer: By user impact and a symbolicated root cause — I fix or mitigate the cluster before I chase unique snowflakes, and I do not mute the issue to “clean the dashboard”
Why it works: Exactly. Clusters first, impact second, mute never as a strategy.
- Sort by impact, not uniqueness.
- Symbolicate before you debate.
- Do not mute to hit a vanity crash-free.
- Mitigate if a full fix misses the train.
A launch-day push must reach millions. Where does the hard part live?
The strongest answer: On the send path: fan-out, token hygiene, quiet hours, and a collapse key — the client just registers and displays; I will not put business logic in the notification service worker of the phone
Why it works: Exactly. Scale is a backend / provider problem. The app stays a good citizen.
- Fan-out and hygiene on the server.
- Collapse / collapse keys for noisy events.
- Respect quiet hours and permission.
- The client displays; it does not orchestrate the blast.
A receiver for a system broadcast fires on one phone and never on another, with the same build.
What do you check?
The strongest answer: How it is registered. Modern Android stopped delivering most implicit broadcasts to manifest-declared receivers, so register it from a running component, scoped to a lifecycle, and unregister in the matching callback
Why it works: Right — registration style decides delivery, and a context-registered receiver must be unregistered or it leaks the screen.
- Registration decides delivery.
- Implicit broadcasts skip manifest receivers.
- Register from a running component.
- Unregister with the lifecycle.
5+ years
Leadership asks “native or Flutter / RN — pick one for the company.” How do you answer?
The strongest answer: With the portfolio: which apps, which hiring market, which native seams we already own, and a 3-year cost — not a slogan, and not a rewrite of a healthy native app to chase a tweet
Why it works: Exactly. Stack choice is a portfolio and a hiring plan. Slogans are how you pay twice.
- Name the apps and the seams.
- Count hiring and upgrade cost.
- Do not rewrite a healthy app for fashion.
- Write the 3-year cost, not a slogan.
Product wants to promise the app’s data is already fresh every morning, using iOS background app refresh.
What do you commit to?
The strongest answer: Background refresh is opportunistic — the system decides whether it runs and the user can switch it off — so I promise a fast refresh when the app opens and treat a background run as a bonus
Why it works: Right — you can ask, not schedule, so the product promise has to sit on the path you control.
- Background runs are opportunistic.
- Never promise a background schedule.
- Make the open-time refresh fast.
- Treat background work as a bonus.
Finance asks why you rent a device farm and still buy phones. What is the one-sentence case?
The strongest answer: The farm is coverage and regression; the desk phones are judgement, radios, and store-like signing — cutting either recreates the outage we already paid for
Why it works: Exactly. Two tools, two jobs. Say that in the budget narrative.
- Farm = breadth.
- Hands = feel, radios, signing.
- Budget both on purpose.
- Name the outage you are buying down.
You own a large UIKit app. A team wants to build the new subscription flow in SwiftUI.
What is your call, and what do you write down?
The strongest answer: Yes for a self-contained new flow hosted inside the existing navigation — with the interop seam written down, a minimum OS version agreed, and an owner for when a SwiftUI screen misbehaves
Why it works: Right — the two interoperate in both directions, so the question is not “can we” but where the seam sits and who owns it.
- Adopt in a bounded, owned slice.
- Pick one self-contained flow.
- Write down the interop seam.
- Agree the minimum OS version.
Who owns “we might lose the store listing”?
The strongest answer: A named owner (usually mobile + legal + the PM of the risky surface) with a written review of guideline, privacy, and payments risk before the feature is built — not a surprise in week two of review
Why it works: Exactly. Listing risk is a product risk. Own it before the code exists.
- Name an owner.
- Review guideline risk at design time.
- Include privacy and payments.
- Do not discover this at upload.
'1 files' and Hebrew plural forms are wrong. What does the i18n layer need?
The strongest answer: ICU/CLDR plural categories per locale — not an English-only count === 1 check
Why it works: Pluralization is a locale table, not a boolean. Hebrew, Russian, and Arabic are the usual interview examples.
- Use real plural rules.
- ICU messages, not if (count === 1).
- Translators fill each category.
- Don't hide the number.
QA proposes one Appium suite covering iOS and Android so the tests are written once. What is your call?
The strongest answer: Native frameworks for the fast, deep checks the app teams own, and a cross-platform suite only for the few journeys that must be identical on both. One tool for everything trades speed and debuggability for sharing
Why it works: Right — pick the tool per layer, and let the cost of a failing test being hard to debug count in the decision.
- Match the tool to the layer.
- Native for fast, deep checks.
- Cross-platform for shared journeys.
- Teams own their own tests.
How do you catch layout breakage before you translate into German and Hebrew?
The strongest answer: Pseudo-localization plus string-length budgets — no concatenated sentences, every UI string extracted
Why it works: Pseudo-loc expands, wraps, and injects accents/RTL markers so layout fails in CI instead of in market.
- Break layout in a fake locale first.
- Extract every string.
- Run pseudo-loc in CI.
- Forbid concatenated sentences.
Apple’s privacy manifest / Play’s data safety keep drifting from the binary. What system do you install?
The strongest answer: An inventory of SDKs (an SBOM for the app) generated in CI, reviewed when a dependency changes, and blocked when the manifest / labels would lie — not a once-a-year spreadsheet
Why it works: Exactly. Treat collection as a bill of materials. CI is the source of truth.
- Generate an SDK inventory in CI.
- Block a lie in labels / manifest.
- Review on dependency change.
- Do not maintain this in a yearly doc.
You are writing the mobile interview loop. What must a senior still show besides syntax?
The strongest answer: Lifecycle and death, store / permission judgement, and a performance or offline story they actually owned — I do not hire only on a leetcode list adapter
Why it works: Exactly. Judgement on death, stores, and a real production story. Syntax is table stakes.
- Probe death, permissions, store.
- Hear a production story they owned.
- Do not make the loop a puzzle contest.
- Stack can be learned; judgement less so.
A support engineer pastes a crash log into a ticket. It contains a user’s full address and part of a card number.
What do you put in place?
The strongest answer: A rule about what may ever be logged, enforced where the logging happens: sensitive fields redacted at the source, crash reports and breadcrumbs scrubbed, and sensitive screens hidden from the app-switcher snapshot
Why it works: Right — redaction has to happen at the point of logging, or the next feature reintroduces it a month later.
- Redact where you log.
- Name what may never be logged.
- Scrub crash reports and breadcrumbs.
- Hide sensitive screens from snapshots.
Hebrew and Arabic layout should mirror, but CSS uses margin-left everywhere. What do you do at scale?
The strongest answer: Switch to logical properties (margin-inline-start), set dir on the document, and treat RTL as a first-class test surface
Why it works: Logical properties and a document dir are how one stylesheet serves LTR and RTL — on web and in native yoga/flex.
- One stylesheet, logical sides.
- dir on the root from locale.
- margin-inline / inset-inline, not left/right.
- Test RTL in the same pipeline as LTR.