RN

Master the job description

Study Guide

Every requirement a senior React Native role commonly asks for — explained at senior depth, then tied to where you've already done it. Read the concept, then the “your proof” line so you can speak from experience.

Be honest, then bridge Three areas are likely lighter on your CV than the JD wants: MobX, deep GraphQL caching, and heavy native code. Each card below flags a “close the gap” note so you can answer truthfully and show you can ramp — exactly the senior move.

01 · React Native, JavaScript & TypeScript

What they want: strong commercial RN/JS/TS — not tutorials, production apps. TypeScript fluency: typed props and hooks, discriminated unions for state, generics for reusable utilities, strict null-safety, typing API responses so the compiler catches shape mismatches.

Your proof 10 yrs JS, 8 yrs React/RN, 5 yrs TypeScript, 8+ shipped apps. TS is your daily driver on Valt. Talk about how typing DTOs and store slices prevents whole classes of runtime bugs.

02 · New Architecture, lifecycle, rendering & performance

What they want: deep understanding of the New Architecture (Fabric, TurboModules, JSI, Codegen, Hermes), component lifecycle (mount → render → commit → effects), why re-renders happen, and how to optimize. See the Architecture · New Architecture section for the full breakdown.

  • Render behavior: reconciliation, keys, React.memo, referential stability, when useMemo/useCallback actually help.
  • Concurrent React (18/19) — transitions, useDeferredValue — and how Fabric enables it.
  • Lists: FlatList windowing, getItemLayout, FlashList for heavy feeds.
Your proof You profiled and killed re-render flicker and frozen screens on Valt, and drove a Hermes rollout at Bits Kingdom. You can speak to rendering from the trenches.

03 · State management — Zustand & MobX

What they want: senior RN JDs increasingly name Zustand and MobX specifically. You're strong on Zustand; MobX is the one to brush up.

Zustand (your strength)

A tiny store created with create(); components subscribe with a selector so they re-render only when their slice changes. No providers, no boilerplate, works outside React. Talk about selector discipline and shallow equality to avoid extra renders.

MobX (close the gap)

MobX is transparent reactive state: you mark state observable, wrap components in observer(), and they automatically re-render when the exact observables they read change. You mutate state directly inside actions, and computed values derive cached state. Mental model: a spreadsheet — change a cell, every formula that depends on it recalculates.

ZustandMobX
StyleImmutable-ish, selector subscriptionsMutable, observable + reactions
Re-render triggerSelector result changesObserved field changes
Updatesset(state => …)mutate inside an action
Derived statecompute in selectorcomputed (cached)
How to say it honestly “My production depth is in Zustand and Redux, and I've used Context and React Query for server state. I've worked with MobX's model — observables, observer components, actions, and computed values — and since I understand reactive state deeply, I'd be productive in your MobX code quickly.” Then pivot to a Zustand performance story you own.
Key distinction to land Server state vs client state. React Query/SWR own server cache (fetching, caching, revalidation); Zustand/MobX own client/UI state. Mixing them up is a common mistake — keep them separate.

04 · GraphQL & efficient data-fetching

What they want: integrate with GraphQL services and understand efficient fetching + caching. Senior talking points: query only the fields you need (vs REST over-fetching), a normalized cache (Apollo/urql store entities by id so one update reflects everywhere), pagination with cursors + fetchMore, optimistic responses, and cache invalidation after mutations.

Your proof You built and consumed AppSync GraphQL at Novacomp (Cognito, Lambda resolvers, DynamoDB, OpenSearch) and you've used React Query/SWR caching with optimistic updates for years — the same caching instincts transfer directly to Apollo/urql.
Close the gap Skim Apollo Client's normalized cache & cache.modify, and be ready to contrast a normalized cache (Apollo) with a query-key cache (React Query). One sentence on each shows real understanding.

05 · Native iOS / Android development

What they want: hands-on native (Swift, Objective-C, Kotlin, Java), native SDK integration, and build-config changes when RN isn't enough. You don't need to be a full native engineer — you need to be the RN engineer who isn't afraid of the native side.

  • Writing/patching a native module or TurboModule; bridging a native SDK to JS.
  • Build config: CocoaPods/Xcode schemes, Gradle/build variants, signing & provisioning, permissions in Info.plist / AndroidManifest.
  • Reading native crash logs and SDK docs.
Your proof PSPDFKit native integration with native module patches, custom iOS/Android camera-permission hooks, native universal/deep-link handling, and owning app IDs, certificates, and notification setup on Apple Developer + Google Cloud.
How to say it honestly “I'm comfortable dropping into native to integrate an SDK, patch a module, or fix build config — I've done PSPDFKit patches and native permission hooks. I'm not writing large Swift/Kotlin features daily, but I read it, debug it, and ship the native glue RN needs.”

06 · Complex, large-scale & legacy codebases

What they want: ramp fast on a big, evolving codebase and contribute without breaking things. Method: read the data flow before the files, find the smallest safe change, lean on types and tests, follow existing patterns rather than importing your own.

Your proof Three years inside one growing app (Valt), Turborepo monorepo tooling, and a documented habit of “quickly understand and contribute to a large, evolving codebase.” You've also built shared packages others depend on.

07 · Testing & quality

What they want: low post-release bug rate via real testing — unit, integration, and CI-gated E2E. Know the pyramid: many fast unit tests (Jest), fewer integration tests (React Native Testing Library — test behavior, not implementation), few E2E (Maestro / Detox) on critical flows. Talk about testable architecture: pure functions, injected dependencies, thin components.

Your proof Jest unit & integration, CI-gated E2E, and Maestro on your CV; you set up CI test pipelines from scratch (Bitbucket Pipelines) early in your career. “Maintain a testable and reusable codebase” is literally a line you live.

08 · CI/CD, releases & third-party SDKs

What they want: own the path to production — CI/CD pipelines, App Store + Google Play releases, third-party SDK integration. Know: build automation (EAS Build / Fastlane / GH Actions), OTA updates (EAS Update / CodePush) and their limits (JS-only, not native), staged rollouts, and release/review processes.

Your proof You owned production releases and App Store review responses on Valt, used Expo EAS for CD, GitHub Actions, App Center, and integrated SDKs like Twilio, PSPDFKit, OneSignal, Stripe, and In-App Purchases. This is a clear strength — lead with it.

09 · Refactoring, tech debt & stability

What they want: reduce crash rates and tech debt with direction. Approach: measure first (Sentry/Crashlytics for crash-free rate), refactor behind tests in small safe steps, strangle legacy patterns gradually rather than big-bang rewrites, and tie every refactor to a metric (startup time, crash rate, re-renders).

Your proof Performance refactors on Valt (re-render & frozen-screen fixes), version-based cache invalidation for safe rollouts, and driving RN 0.59→0.62 + Hermes upgrades — all stability work.

10 · Experiment-driven development

What they want: this is core to experiment-driven product teams — A/B tests, feature flags, analytics-driven iteration. Know: feature-flag patterns (gradual rollout, kill switches, flag hygiene/cleanup), how an experiment is instrumented (assign variant → fire events → read metrics), guardrail metrics, and writing code that can run two variants cleanly.

Your proof You've worked analytics + tracking (Google Analytics, App Center, OneSignal) and shipped iteratively with product as a PM-adjacent engineer. Frame your delivery style as “ship, measure, iterate.”
Close the gap Be ready to name tools by category — flags (LaunchDarkly, Statsig, or a homegrown system), analytics (Amplitude/Mixpanel/Segment) — and say you adapt to whatever they use.

11 · Debugging, profiling & troubleshooting

What they want: strong, systematic debugging. Toolbelt: React DevTools Profiler & the “why did this render” tooling, Flipper / the new RN DevTools, Hermes profiler, native instruments (Xcode Instruments, Android Profiler), Sentry breadcrumbs, and network inspection. Method: reproduce → isolate → measure → fix → add a regression test.

Your proof Flipper profiling, render-thread debugging, and the Valt performance investigation are your headline debugging stories. “I profile instead of guessing” is your signature line.

12 · Soft skills & collaboration

What they want: adaptability, attention to detail (pixel-perfect, clean code), initiative, team play, and clear communication with non-technical stakeholders. In a remote, PST-overlapping role, written clarity and proactive updates matter as much as code.

Your proof You acted as PM with clients, ran recruiting interviews, mentored a junior dev, and coordinated dev/design teams. You explain technical things to non-technical people for a living — say so.

13 · Navigation & deep linking

Core: navigation is a tree of navigators — stack (push/pop), tabs, and drawer — that you nest to model real apps. Screens receive typed params; useFocusEffect and useIsFocused run work only while a screen is active. Deep links map an incoming URL (myapp://chat/42 or an https universal link) to a screen + params via a linking config, so a push notification or email lands the user on the exact screen.

  • Auth-gated flows: conditionally render the auth stack vs the app stack — never just hide screens.
  • Persisting navigation state across restarts; resetting stacks cleanly after login/logout.
  • Universal / App Links need native setup: associated domains (iOS) + intent filters / assetlinks.json (Android).
New concept Expo Router — file-based routing (a file in app/ is a route), nested _layout files, typed routes, and <Link prefetch>. It's becoming the default; learn how its file tree compiles down to the same React Navigation tree underneath.
Your proof You shipped universal/deep-link handling on Valt and owned app IDs, associated domains, and notification setup — so URL→screen routing and the native link config are things you've actually wired, not just read about.

14 · Animations & gestures — Reanimated & Gesture Handler

Core: the old Animated API drives values from the JS thread, so motion stutters whenever JS is busy. Reanimated fixes this by running animation code in worklets on the UI threaduseSharedValue holds the value, useAnimatedStyle maps it to styles, and withTiming/withSpring animate it at 60fps even while the JS thread is blocked. Gesture Handler processes pan/tap/pinch natively and composes gestures (simultaneous, race, sequence).

  • Worklets: tiny functions marked to run on the UI thread; hop back with runOnJS, into it with runOnUI.
  • Layout Animations — declarative entering/exiting/layout props for enter/exit.
  • useDerivedValue for values computed from other shared values without re-rendering React.
New concept The UI-thread mental model: an animation that never touches React's render is the secret to smoothness. Internalize what runs where — worklet (UI) vs JS — and you can explain jank precisely instead of hand-waving.
Your proof You eliminated re-render flicker and frozen screens on Valt — the same instinct (keep work off the thread that's busy) is exactly why Reanimated's UI-thread model wins. Say it as “I already think in terms of which thread is doing the work.”

15 · Accessibility (a11y)

Core: make the UI work with VoiceOver (iOS) and TalkBack (Android). Mark elements accessible, give them an accessibilityRole (“button”, “header”, “image”), a clear accessibilityLabel, and accessibilityState (selected / disabled / checked). Respect Dynamic Type (text scales with the OS font setting), keep touch targets ≥ 44pt, and meet WCAG AA contrast.

  • AccessibilityInfo — detect a running screen reader; announceForAccessibility for live updates.
  • Manage focus after navigation; group related elements; hide purely decorative ones from the reader.
  • Honor reduce-motion for users who disable animations.
New concept Accessibility is a senior signal, not a nice-to-have — it's tested (accessibility inspector, automated a11y lint) and often a compliance requirement. Being the engineer who bakes it in early reads as craftsmanship.
Your proof Your pixel-perfect, detail-oriented reputation is the bridge: position a11y as that same care extended to users on assistive tech. Even one shipped VoiceOver pass is a strong story.

16 · Mobile security deep-dive

Core: the JS bundle ships to the device and can be read — never put secrets in it. Store tokens in the iOS Keychain / Android Keystore (via expo-secure-store or react-native-keychain), not AsyncStorage (plaintext). Handle access/refresh tokens carefully, gate sensitive actions behind biometrics, and use HTTPS everywhere (ATS on iOS). Know certificate pinning (trust only your cert/CA) and its rotation tradeoff.

  • OWASP MASVS / Mobile Top 10 — the vocabulary interviewers expect: insecure storage, weak crypto, and so on.
  • Jailbreak/root detection and code obfuscation as defense-in-depth (not silver bullets).
  • Attestation: App Attest (iOS) / Play Integrity (Android) to verify a genuine app and device.
New concept “Parse the threat model out loud”: secrets-in-bundle, data at rest, data in transit, and device integrity are four distinct buckets. Naming them shows senior security maturity.
Your proof You shipped FaceID/TouchID biometric auth, Google Sign-In, and Salesforce auth on Valt — real auth and sensitive-data flows. You've already made the secure-storage and token decisions; now you can name the framework (Keychain/Keystore, MASVS) around them.

17 · Memory management & leak hunting

Core: two memory worlds — the JS heap (managed by Hermes' GC) and native memory (views, images, native modules). The classic RN leaks: event listeners, subscriptions, and timers you never clean up; stale closures that capture large objects; and screens retained by navigation. The fix is disciplined useEffect cleanup, AbortController to cancel in-flight fetches, and removing listeners on unmount.

  • Images are the biggest native memory cost — downsample, cache sensibly, avoid huge bitmaps in lists.
  • Tools: Xcode Instruments (Allocations / Leaks), Android Profiler, and Hermes heap snapshots.
  • Inline objects/functions in render feed churn and can keep references alive longer than expected.
New concept Leaks show up as slow, cumulative jank and crashes after long sessions — not an immediate bug. You hunt them with a profiler over time, not a single stack trace.
Your proof Your frozen-screen and re-render investigation on Valt is lifecycle/memory discipline, and the Hermes rollout you drove improved GC behavior directly. “I profile memory across a session instead of guessing” extends your signature debugging line.

18 · App startup, bundling & Metro

Core: TTI (time-to-interactive) is the metric. At launch the OS inits the native app, then the JS bundle loads and the first screen renders. Metro is RN's bundler; Hermes precompiles JS to bytecode at build time so there's no parse step on-device — faster cold start, lower memory. Cut startup further with inline requires / lazy loading so modules load on first use, not all at boot.

  • Shrink the bundle: tree-shaking, React.lazy + Suspense for heavy screens, trim/curate dependencies.
  • Optimize assets (image sizes, font subsetting); measure cold vs warm start separately.
  • Splash → first paint: don't block the first frame on network or heavy synchronous work.
New concept Treat startup as a budget: every eagerly-imported module and synchronous boot task spends milliseconds. Senior teams track TTI like a metric and defend it in code review.
Your proof You drove a Hermes rollout (Bits Kingdom) and led RN 0.59→0.62 upgrades — startup, bytecode, and bundle work is literally on your CV. You can speak to before/after launch time from real rollouts.

19 · Advanced TypeScript & runtime validation

Core: past the basics, seniors lean on discriminated unions to model state machines (impossible states won't compile), generics for reusable hooks/utilities, utility types (Partial, Pick, Omit, Record), as const for literal inference, and branded types so a UserId can't be passed where an OrgId is expected.

  • Template-literal & mapped/conditional types for typed routes, event names, and API shapes.
  • Type navigation params and API DTOs end-to-end so the compiler catches shape drift.
New concept Types are compile-time only — they vanish at runtime, so a bad API response still crashes you. Validate untrusted JSON at the boundary with zod (or io-ts): write the schema once, infer the type from it, and “parse, don't validate.” One schema = a single source of truth for type + check.
Your proof 5 yrs TypeScript, typing DTOs and store slices on Valt — you already prevent whole classes of shape bugs at compile time. Runtime validation (zod at the network edge) is the next layer, and an easy, current thing to add to your vocabulary.

20 · Observability & production health

Core: once it ships you run on signals. Sentry / Crashlytics capture crashes; crash-free users % is the headline metric. Upload source maps (Hermes needs its bytecode source maps) so traces symbolicate to real files/lines. Breadcrumbs reconstruct the steps before a crash, and performance monitoring surfaces slow renders, slow screens, and ANRs.

  • Release health: watch a new version's crash rate and halt or roll back on a spike.
  • OTA updates (EAS Update / CodePush) — track adoption, keep a rollback path, and remember OTA is JS-only.
  • Tie dashboards/alerts to guardrail metrics so regressions page you, not your users.
New concept Release-health gating: a deploy isn't “done” at submit — it's done when the crash-free rate holds across the rollout. Treating monitoring as part of shipping is a senior tell.
Your proof You owned production releases and App Store review responses on Valt and used App Center, analytics (GA), and OneSignal — plus EAS for CD. Your “ship, measure, iterate” style with crash-free rate as the guardrail is exactly this card.

21 · React Native performance playbook

Core: performance work is a loop — measure → optimize → re-measure → validate. Profile first (React Native DevTools), then reach for the right lever: virtualize long lists with FlashList (it recycles rows; v2 auto-measures), let the React Compiler auto-memoize, keep input responsive with Concurrent React (useDeferredValue / useTransition), and run animations as worklets on the UI thread with Reanimated.

  • Animate transform / opacity (GPU), never width / height / top / left (per-frame layout).
  • Threading: a sync native method blocks the JS thread — keep it < 16ms or make it async.
  • Startup: ship Hermes (bytecode, no parse), uncompress the Android bundle for mmap, and measure TTI on cold starts only.
  • Bundle: avoid barrel imports, lazy-load heavy screens, and analyze with source-map tooling.
New concept Measure, don't guess. Every fix is paired with a before/after number (FPS 45→60, TTI 3.2s→1.8s). If the metric didn't move, revert and try the next lever.
Your proof You killed re-render flicker and frozen screens on Valt and drove a Hermes rollout — this playbook is already your instinct; now you can name each lever and the metric it moves.

22 · Native UI with @expo/ui

Core: @expo/ui renders real native UI from React — SwiftUI on iOS, Jetpack Compose on Android. Work down a ladder: start with universal components (SDK 56+: Host, Column, Row, Text, Button, List, BottomSheet…) — one tree for iOS, Android, and web — and drop to @expo/ui/swift-ui / @expo/ui/jetpack-compose only when the universal API can't express what you need.

  • useNativeState + worklets give a flicker-free native TextInput that updates on the UI thread with no React render.
  • Drop-in replacements (@expo/ui/community/*) swap community libs like @gorhom/bottom-sheet by import path.
  • Prefer native navigators (UINavigationController / Fragment) and Pressable over Touchables; List isn't for large datasets.
  • Guard JSX: never {count && …} when count can be 0 — use a ternary.
New concept The universal-first ladder: reach for the cross-platform layer before writing two platform trees — you only pay the .ios/.android split when you must.
Your proof You did PSPDFKit native integration and native permission hooks — you already bridge to native. @expo/ui is that same instinct with far less glue, straight from React.

23 · Data fetching, caching & offline

Core: wrap fetch with a status check, typed errors, and exponential-backoff retry. For real apps, reach for React Query: staleTime controls cache freshness, identical queryKeys dedupe in-flight requests, and mutations refresh data via invalidateQueries (with optimistic updates rolled back on error).

  • Server vs client state: React Query / SWR own the server cache; Zustand / MobX own client state — don't mix them.
  • Offline-aware: wire React Query's onlineManager to NetInfo so queries pause offline and resume on reconnect.
  • Tokens: store in expo-secure-store (Keychain/Keystore), never AsyncStorage; use a single-flight refresh flow.
  • Config: only EXPO_PUBLIC_ vars reach the bundle (inlined at build time) — never put secrets there.
  • Waterfalls: run independent calls with Promise.all; cancel stale ones with AbortController.
New concept Treat the network boundary as untrusted: check response.ok, type your errors, and validate shapes — types vanish at runtime.
Your proof You built AppSync GraphQL at Novacomp and shipped React Query / SWR with optimistic updates plus version-based cache invalidation on Valt — this card is your daily work, framed in interview language.

24 · Memory, native modules & bundle size

Core: the senior levers below the JS layer. Hunt JS memory leaks by always cleaning up listeners/timers in a useEffect return (memory climbing per screen navigation is the tell). Build Turbo Modules that offload heavy work to a background thread and stay async. And shrink the app: tree-shaking, R8, and replacing JS with native.

  • Native over JS: Hermes has native Intl (drop ~430KB of polyfills), react-native-quick-crypto is ~58× faster than crypto-js, and native-stack beats the JS stack.
  • Bundle: enable tree-shaking (Expo SDK 52+ flags) and R8 (minifyEnabled + shrinkResources); lazy-load rare screens with React.lazy + Suspense.
  • State: atomic stores (Zustand/Jotai) with selectors beat Context's re-render-everyone; uncontrolled TextInput (defaultValue) avoids legacy-arch flicker.
  • Gotchas: collapsable={false} stops view flattening; ship the Android 16KB page alignment (Play deadline Nov 2025, third-party .so files).
New concept The native-over-JS lever: when a JS library is heavy or slow, a native/JSI equivalent often pays for itself in bundle size and speed — measure both.
Your proof You shipped PSPDFKit native patches and native permission hooks, drove a Hermes rollout, and worked a Turborepo monorepo — you already operate at this layer; now you can name each lever.

25 · Expo Router & native UI patterns

Core: modern Expo leans hard on the platform. Routing is file-based (app/ + _layout.tsx, shared group routes, always a / route, never co-locate non-routes). Stay in Expo Go until you genuinely need a custom dev build (local native modules, Apple targets, unsupported native deps).

  • Library defaults: expo-image, expo-audio/expo-video (not expo-av), react-native-safe-area-context, process.env.EXPO_OS.
  • Responsiveness: a first-child ScrollView with contentInsetAdjustmentBehavior="automatic", useWindowDimensions over Dimensions.get(), flexbox over measuring.
  • Styling: CSS boxShadow (not legacy shadow/elevation), flex gap over margins, borderCurve: 'continuous'.
  • Native nav UX: Link.Preview + Link.Menu context menus, presentation: 'modal' | 'formSheet', NativeTabs, SF Symbols via expo-image sf:.
New concept Lean on the platform: reach for native presentation, native tabs, and native menus before hand-rolling JS equivalents — you get correct gestures and feel for free.
Your proof You wired universal/deep-link handling and owned app IDs and associated domains on Valt — native routing and platform conventions are already how you think about navigation.

26 · Native iOS & Android in practice

Core: the RN engineer who isn't afraid of the native side. Know the memory models — GC (JS, Kotlin/Java), ARC reference counting (Swift/Obj-C, C++ smart pointers), and manual C/C++ — and the leaks each invites (retain cycles → fix with weak delegates and std::unique_ptr). When the JS profiler is clean, profile native.

  • Toolchains: iOS = Xcode + CocoaPods (pod install) + xcodebuild; Android = Android Studio + Gradle (./gradlew). Be fluent opening, configuring, and signing both.
  • Profiling: Xcode Instruments (Time Profiler, Leaks), Android Studio Profiler — CPU/memory by thread.
  • Assets: iOS Asset Catalog → app thinning; Android AAB → per-device splits.
  • Bridging: wrap an unwrapped SDK in a typed TurboModule (Swift/Kotlin), keep the JS surface small, run heavy work off the JS thread.
New concept Profile native when JS is clean. A flat React profile with real jank means the cost is below JS — in native init, a module, or layout — and only native instruments will show it.
Your proof You shipped PSPDFKit native patches, custom iOS/Android camera-permission hooks, native deep-link handling, and owned app IDs, certificates, and provisioning — you already live in the native layer; this names the muscle.

27 · Mobile security in depth

Core: answer security in four threat-model bucketssecrets, data at rest, data in transit, and device/app integrity — and name a control for each. Never ship secrets in the JS bundle (it's readable on-device); store tokens in the Keychain/Keystore, not AsyncStorage; encrypt sensitive local data.

  • In transit: HTTPS/ATS everywhere, plus certificate pinning (mind cert rotation and keep backup pins).
  • Integrity: App Attest (iOS) / Play Integrity (Android) attestation; jailbreak/root detection and obfuscation (R8, Hermes bytecode) as defense-in-depth.
  • Auth: biometrics gate a Secure Enclave / TEE key — the app never sees the biometric.
  • Untrusted input: validate/authorize every deep-link parameter; prefer verified Universal/App Links to custom schemes.
  • Vocabulary: OWASP MASVS / Mobile Top 10.
New concept Parse the threat model out loud. Naming the four buckets and a control per bucket reads as senior security maturity, not ad-hoc patching.
Your proof You shipped FaceID/TouchID biometric auth, Google Sign-In, and Salesforce auth on Valt — real auth and sensitive-data flows. You've already made the secure-storage and token-handling calls; now you can frame them.

28 · On-device AI in React Native

Core: on-device inference runs the model on the phone — no cloud round-trip — for privacy, offline, low latency, and zero per-call cost (trading away frontier-model quality and using device compute). The React Native ecosystem has two leading toolkits.

  • React Native ExecuTorch (Software Mansion): PyTorch's ExecuTorch runtime, .pte models, backends XNNPACK / Core ML / Vulkan. Declarative hooks: useLLM, useSpeechToText (Whisper), useObjectDetection (YOLO/SAM), useTextEmbeddings. Runs Llama 3.2, Qwen, Phi, SmolLM.
  • react-native-ai (Callstack): a Vercel AI SDK drop-in (generateText / streamText / embed) over Apple Foundation Models (built-in), Llama (GGUF), and MLC LLM — download + .prepare() non-Apple models from Hugging Face.
  • The decision: on-device for privacy/compliance/offline/cost; cloud for top quality or huge context. Name the trade-off.
New concept The privacy/cost/latency case for local inference — and that RN already has production-grade tooling for it — turns "I'm into on-device AI" into "I'd reach for react-native-executorch or react-native-ai because…".
Your proof You list on-device AI as a differentiator — now you can name the exact stack (ExecuTorch .pte + hooks, or the Vercel-AI-SDK-compatible react-native-ai) and the privacy/offline argument behind it.

29 · Shipping: EAS builds, store releases & OTA

Core: own the path to production. EAS Build compiles in the cloud from eas.json profiles; EAS Submit uploads to App Store Connect / Play (eas build --profile production --submit). For JS-only fixes, EAS Update ships OTA — but it can't touch native code, so a new module/permission means a rebuild.

  • Compatibility: runtimeVersion gates which builds an update reaches; channels map to branches, and you promote updates staging → production.
  • Versioning: marketing expo.version (app.json) vs the native build number owned by EAS (autoIncrement).
  • Release health: crash/failed-launch rate, unique users, embedded-vs-OTA split via eas update:insights — gate or roll back on a spike.
  • CI/CD: EAS Workflows (YAML in .eas/workflows/) for build/submit/update/e2e, alongside a GitHub Actions lint/typecheck/test gate.
New concept A deploy isn't “done” at submit — it's done when the crash-free rate holds across the rollout. Treat monitoring as part of shipping. Say in an interview: “I don't consider a release shipped until the crash-free rate holds across the rollout, not at submission.”
Your proof You owned production releases and App Store review responses on Valt and used EAS, GitHub Actions, and App Center — this is a clear strength; lead with it.

30 · Native modules & migrations

Core: two senior capabilities below the JS line. Authoring native modules: the Expo Modules API is a Swift/Kotlin DSL (Name, Function, AsyncFunction, View, SharedObject, Events) with autolinking, lifecycle hooks, and config plugins that edit Info.plist / AndroidManifest at prebuild. Scaffold with create-expo-module; AsyncFunction runs off the JS thread.

  • Module choice: Expo Modules API for app-specific native code & SDK wrappers; TurboModules are RN core's codegen path — both on JSI.
  • Brownfield migration: @callstack/react-native-brownfield — package RN as XCFramework / AAR, integrate one surface, repeat per feature, behind a facade.
  • RN upgrades: the Upgrade Helper / rn-diff-purge template diff, one or two minors at a time, both platforms must build.
New concept Incremental over big-bang. Whether adopting RN or upgrading it, ship in bounded, validated steps — never a frozen mega-PR. Say in an interview: “I migrate and upgrade in bounded, validated steps — never a frozen mega-PR.”
Your proof You wrote PSPDFKit native patches and permission hooks, drove RN 0.59 → 0.62 upgrades, and built shared packages in a Turborepo — module authoring and incremental migration are already your lived experience.

31 · Motion & native navigation polish

Core: a native-feeling app combines native motion and native chrome. Use Reanimated v4's declarative animations — entering / exiting / layout on Animated.View for mount/unmount/reorder, and scroll-driven effects via useScrollViewOffset + interpolate — all on the UI thread.

  • Native tabs: NativeTabs with per-trigger Icon (sf:/md:), Label, Badge, a role="search" tab, and minimizeBehavior.
  • Native navigation: Link.Preview + context menus, presentation: 'modal' | 'formSheet', large titles — lean on the platform, don't re-implement it.
  • Animate transform/opacity only; keep gesture/scroll state in shared values.
New concept Native motion + native chrome = native feel. The details (tab minimize, peek previews, sheet detents, layout animations) are what make an RN app read as first-class. Say in an interview: “I treat native motion and native chrome as the details that make an RN app read as first-class, not afterthoughts.”
Your proof You eliminated re-render flicker (UI-thread thinking) and owned deep-link navigation on Valt — native motion and native nav are exactly your strengths, now with the current APIs to name.

32 · Foundations (junior → mid)

Core: the fundamentals everything else stands on. A component renders UI from props (inputs) and state (owned, changeable). React reconciles your JSX against the previous render and updates the native views — so list keys must be stable unique ids, not array indexes.

  • Hooks: useState for local state, useEffect for side effects (always clean up).
  • Lists: FlatList (virtualized), never map inside a ScrollView for long data.
  • Layout: Flexbox (flexDirection defaults to column), StyleSheet or NativeWind.
  • Data: fetch + check response.ok + handle loading/error; graduate to React Query for caching.
  • Run it: Expo Go for fast iteration, a dev build once you add native code.
New concept Build the mental model: state → re-render → reconcile → commit. Most bugs and perf issues trace back to misunderstanding which step you're in.
Your proof With 10 years of JavaScript and 8 of React, these are second nature for you — the value at this level is teaching them clearly, which you've done mentoring juniors.

33 · Production observability & startup

Core: once it ships, you run on production signals, not dev profiling. EAS Observe tracks startup, navigation, and custom-event performance: wrap the root in ObserveRoot, call markInteractive() when the screen is usable, and add the Expo Router integration for per-route metrics.

  • TTR vs TTI: first paint vs actually-usable — TTI is the headline.
  • Cold vs warm: optimize and track cold start separately; it's what first-run users feel.
  • Diagnose: steady frames + long TTI = too much work (defer it); dropped frames = main-thread contention (move off the UI thread).
  • Crashes: crash-free rate (Sentry/Crashlytics) with source maps; gate or roll back on a spike.
New concept Measure production, not just dev. Real devices and networks surface what your laptop never will — instrument the metrics that map to user pain (TTI, crash-free rate).
Your proof You used App Center analytics and owned production releases on Valt — wiring observability and reading startup metrics is a natural extension of work you've already done.

34 · Architect-level system design

Core: architecture is decision-making before code, designed for the worst phone, worst network, worst moment. Run a structured pass (CRDDS: Clarify → Requirements → Data → Design → Scale) and lay the app out in 4 layers — UI, state, domain/data, platform/native — with dependencies pointing inward.

  • Offline-first: the local store is the source of truth; queue mutations, sync in the background, resolve conflicts, update optimistically.
  • Real-time: pick WebSocket / SSE / polling / push by need; plan reconnection, backoff, battery.
  • Stability at scale: measure first, strangle legacy gradually, tie every refactor to a metric.
  • Leverage: shared packages, conventions, lint/types, fast CI, teaching reviews — multiply the team.
New concept The architect's output isn't code — it's good decisions and team leverage: a system that's correct under the worst conditions and a team that ships safely without you in the loop.
Your proof You architected a schema-driven Form Builder reused across a Turborepo, integrated Twilio real-time chat, and shipped version-based cache invalidation — that's offline/real-time/stability architecture in production.

35 · React patterns that scale

Core: the cross-cutting React habits that keep apps fast as they grow — fewer renders, no waterfalls. Derive values during render instead of syncing them in effects; reach for a ref when a value changes often but shouldn't re-render; and run app-wide init once (module guard), not in a component effect.

  • Subscribe to derived booleans (isMobile), not raw continuous values.
  • Kill waterfalls: parallelize independent fetches (Promise.all / RSC composition), stream with Suspense.
  • Push non-critical work off the response with after(); load analytics after hydration.
  • Use Set/Map for O(1) lookups instead of array.includes in a loop.
New concept Most React performance is not memoization — it's not re-rendering when you don't need to and not waterfalling data. Fix those first.
Your proof Your re-render and frozen-screen fixes on Valt are exactly this discipline — these patterns name the habits behind that work and extend them to data fetching.

36 · Integration patterns (Expo examples)

When you wire a third-party library or service into an Expo app, don't hand-roll it. Mine expo/examples — Expo's official library of ~80 with-* integration examples, each built around one library and maintained against the current SDK. They aren't full apps; the typical one is a single screen of ~100-200 lines. You're after the pattern, not the architecture.

The canonical integration pattern that repeats across nearly every example has three parts:

  • Dependencies — the minimal package set the integration needs. Don't copy the example's pinned versions: examples track the latest SDK, so their package.json pins won't match an older project. Add only the missing packages with npx expo install <pkg>, which resolves the SDK-correct version for your app.
  • app.json config plugins — managed examples have no ios/ or android/ directories; all native setup (permissions, native deps, build settings) is declared as config plugins and permission strings in app.json / app.config.*. Merge only the plugins the example introduces into your existing config — never replace your block.
  • Minimal wiring — a provider near the root, a hook or a few calls in a screen, plus any env vars (the example's .env holds placeholders, never real secrets — recreate its shape).

Full-stack examples (payments, AI) pair the client with Expo Router +api routes — server endpoints colocated in app/ (e.g. app/api/payment-intent+api.ts). The pattern exists for one reason: secret keys stay server-side. The client calls your +api route; the route holds the Stripe secret key or OpenAI key and talks to the provider. The publishable key is all that ships in the bundle.

Two ways to use them. Inspiration mode (you already have an app): read README.mdpackage.jsonapp.json → the integration code → .env, then apply the pattern by hand; never scaffold an example on top of your project. Scaffold mode (greenfield): npx create-expo --example with-stripe spins up a fresh project from that example.

Real integrations worth knowing by name: with-stripe (payments + +api route), with-clerk (auth), with-legend-state-supabase (local-first sync backed by Supabase Postgres), with-sqlite (local DB), with-sentry (crash/error monitoring), with-maps (react-native-maps), with-skia (2D graphics/canvas), and with-openai (LLM via a server route). The default branch is master, and every example has a one-click launch URL.

New concept The +api route is the Expo-native answer to "where does my secret key live?" In a classic RN app you'd stand up a separate Node/Lambda backend for that. With Expo Router, a file named foo+api.ts in app/ is a server endpoint (deployed on EAS Hosting), so your payment-intent creation, webhook handling, and OpenAI proxying live in the same repo as the screens — and the secret key never enters the JS bundle.
Your proof You've already lived the secret-key-server-side discipline: Stripe + In-App Purchases on Valt Connect (purchase verification belongs on the server, exactly what a +api route formalizes), Twilio Conversations real-time chat (token minted server-side, client holds only the access token — same shape as with-stripe's publishable-vs-secret split), OneSignal push, and AppSync GraphQL where resolvers and auth sat behind the API. Framing these as "client provider + server route + config plugin" is exactly how you'd narrate adopting with-stripe or with-clerk; your PSPDFKit native integration is the config-plugin half of the same story.

37 · The learning method (how to use this guide)

This guide isn't a wall of notes to re-read — re-reading feels productive and barely moves retention. The app is built around the techniques cognitive science actually backs, and using it the intended way is what makes the difference:

  • Active recall — every card asks you to retrieve the answer before you reveal it. The struggle of pulling it from memory is the encoding event; passively reading the answer is not. Always answer out loud or in your head first, then check.
  • Spaced repetition — when you grade a card, the app schedules its next review. Easy cards drift far into the future; ones you fumble come back soon. Your job is to clear what's Due — the system decides the spacing, so you spend effort exactly where memory is decaying.
  • Interleaving — don't drill one category to exhaustion. Mix categories and difficulty levels in a session. Switching topics is harder in the moment but builds the discrimination you need when an interviewer jumps from re-renders to system design with no warning.
  • Active problem-solving — the coding and system-design prompts force you to generate a solution, not recognize one. That generative effort transfers to the live whiteboard far better than reading a model answer.
  • Teach-back / Feynman — use the teleprompter to say the explanation out loud as if teaching a peer. The moment you stumble or hand-wave is the exact gap you didn't know you had. If you can't teach it simply, you don't own it yet.
  • Track your gaps — watch the progress and per-level counts. Categories with low mastery or piling-up Due cards are your weak spots; let the numbers, not your gut, point you at what to study next.

A concrete daily loop (~30-45 min) that exercises all six:

  • Clear today's Due cards first — retrieve before revealing, then grade honestly (an undeserved "Easy" only schedules a relapse).
  • Do 1 coding prompt + 1 system-design prompt, pulled from different categories to force interleaving.
  • Record one pitch with the teleprompter — a Feynman-style teach-back of one concept or one of your shipped features — and listen back for the hand-wave.
  • Glance at progress + level counts; queue tomorrow's focus on whatever category is weakest or most overdue.
New concept Desirable difficulty — the techniques here are meant to feel harder than re-reading, and that's the point. Retrieval that's effortful, spacing that lets a little forgetting set in, and interleaving that denies you a comfortable rhythm all produce more durable memory than smooth, easy study. If a session feels easy, you're probably reviewing too soon or recognizing instead of recalling.
Your proof You don't need to invent stories — you've shipped them. When a card or prompt touches an area you've lived (Stripe + In-App Purchases, Twilio Conversations chat, OneSignal push, AppSync GraphQL, the PSPDFKit native integration, your re-render/perf fixes and RN 0.59→0.62 upgrades), make the teach-back about that work. Narrating a real decision out loud both nails the concept and rehearses the exact "tell me about a time you…" answer an interviewer will ask.

38 · AI in the browser & on-device

The shift: models that used to live behind an API now run locally — in the browser tab, on the user's GPU, with nothing leaving the device. As a React Native / web engineer you should be able to place any feature on the on-device-vs-cloud spectrum and name the right runtime. There are two distinct families:

  • Small task models — Transformers.js v3 (@huggingface/transformers). Runs Hugging Face models in JS on ONNX Runtime Web, accelerated by WebGPU (fallback to WASM). Great for feature-extraction (embeddings, e.g. Xenova/all-MiniLM-L6-v2 → 384-dim vectors), text classification / sentiment, NER, zero-shot, and ASR (Whisper). Models are tens-to-hundreds of MB; quantize with dtype: "q8" / "q4" to shrink them at a small quality cost.
  • Full LLMs in the browser. Two routes: (1) Chrome Built-in AI / Prompt API exposes Gemini Nano on-device via the global window.LanguageModel — no weights you ship, the browser manages the download. Sibling task APIs (each with its own availability() / create()): Summarizer, Writer, Rewriter, Translator, LanguageDetector. (2) WebLLM / @mlc-ai/web-llm runs full open models (Llama, Phi, Qwen) compiled by MLC onto WebGPU — multi-GB download, but any model you want, in any Chromium/WebGPU browser.

The killer pattern — in-browser semantic search & RAG. Embed your content once (each doc → a normalized vector), embed the user's query the same way, then rank docs by cosine similarity — which, for L2-normalized vectors, is just a dot product. That's a privacy-preserving search engine with zero backend. Add a local LLM and you have RAG in the browser: retrieve top-k similar chunks, stuff them into a prompt, generate the answer on-device.

import { pipeline } from "@huggingface/transformers"; // 1) Build an embedder once (WebGPU, quantized). Do this in a Web Worker. const embed = await pipeline( "feature-extraction", "Xenova/all-MiniLM-L6-v2", { device: "webgpu", dtype: "q8" } ); async function vector(text: string): Promise<number[]> { const out = await embed(text, { pooling: "mean", normalize: true }); return out.tolist()[0] as number[]; // 384-dim, L2-normalized } // 2) cosine sim === dot product when both are L2-normalized const cos = (a: number[], b: number[]) => a.reduce((s, x, i) => s + x * b[i], 0); // 3) Rank a corpus against a query const docVecs = await Promise.all(docs.map(vector)); const q = await vector("how do I cache models offline?"); const ranked = docs .map((d, i) => ({ d, score: cos(q, docVecs[i]) })) .sort((a, b) => b.score - a.score);

Feature-detect and degrade gracefully — always. WebGPU and the Prompt API are not everywhere, and the model may need to download first. Never assume; probe, then fall back to WASM, to a cloud call, or to a plain-text experience.

// WebGPU present? const hasGPU = "gpu" in navigator && !!(await navigator.gpu?.requestAdapter()); // Chrome Built-in AI (Prompt API). Pass the SAME options to availability() + create(). if ("LanguageModel" in self) { // "unavailable" | "downloadable" | "downloading" | "available" const status = await LanguageModel.availability(); if (status !== "unavailable") { const session = await LanguageModel.create({ monitor(m) { m.addEventListener("downloadprogress", (e) => console.log(e.loaded) ); }, }); const stream = session.promptStreaming("Summarize this changelog…"); for await (const chunk of stream) render(chunk); } } else { // graceful fallback: cloud API or non-AI UI }
  • Run inference off the main thread. Load the pipeline and run prompt() / embed() inside a Web Worker so model download and GPU work never block the UI thread (no jank — the same instinct as keeping work off the JS thread in RN).
  • Cache the weights. Transformers.js caches downloaded models via the browser Cache API / IndexedDB, so the multi-MB download is a one-time cost — subsequent loads are offline-instant.
  • Embedded contexts: an <iframe> needs allow="language-model" to use the Prompt API.
New concept Cosine similarity is the whole trick. An embedding model maps text into a vector where direction encodes meaning. Two pieces of text are "similar" when their vectors point the same way — measured by cosine of the angle between them. If you L2-normalize every vector at creation time (normalize: true), the cosine collapses to a plain dot product: one multiply-add loop, no square roots at query time. That's why in-browser semantic search over a few hundred docs is effectively free — embed once, then it's just arithmetic.

The decision — on-device vs cloud. Reach for on-device when you want: privacy (data never leaves the device), offline capability, zero per-call cost, and low latency for small models. Reach for the cloud when you need frontier quality (the largest models), huge context windows, or to avoid pushing a multi-GB download and heavy GPU/battery load onto the user's device. A common shape is hybrid: embeddings + retrieval + light tasks on-device, escalate only the hard generation to a cloud LLM.

Your proof Your CV already lists on-device AI, and your monorepo ships @huggingface/transformers — so you can speak to this from real code, not theory. Frame it in interviews as: "I added in-browser feature-extraction with Transformers.js on WebGPU, ran it in a Web Worker with cached weights, and ranked results by cosine similarity — private, offline, zero per-call cost." Tie it to Hermes: the same discipline you used keeping work off the JS thread for smooth RN frames is exactly why on-device inference belongs in a Worker. That story — place the model on the privacy/latency/cost spectrum, then pick the runtime — is what separates a senior answer from a buzzword answer.