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.
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.
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, whenuseMemo/useCallbackactually help. - Concurrent React (18/19) — transitions,
useDeferredValue— and how Fabric enables it. - Lists:
FlatListwindowing,getItemLayout, FlashList for heavy feeds.
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.
| Zustand | MobX | |
|---|---|---|
| Style | Immutable-ish, selector subscriptions | Mutable, observable + reactions |
| Re-render trigger | Selector result changes | Observed field changes |
| Updates | set(state => …) | mutate inside an action |
| Derived state | compute in selector | computed (cached) |
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.
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.
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.
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.
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.
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).
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.
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.
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.
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).
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.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 thread — useSharedValue
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 withrunOnUI. - Layout Animations — declarative
entering/exiting/layoutprops for enter/exit. useDerivedValuefor values computed from other shared values without re-rendering React.
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;announceForAccessibilityfor live updates.- Manage focus after navigation; group related elements; hide purely decorative ones from the reader.
- Honor reduce-motion for users who disable animations.
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.
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.
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.
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.
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.
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.
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 nativeTextInputthat updates on the UI thread with no React render.- Drop-in replacements (
@expo/ui/community/*) swap community libs like@gorhom/bottom-sheetby import path. - Prefer native navigators (UINavigationController / Fragment) and Pressable over Touchables;
Listisn't for large datasets. - Guard JSX: never
{count && …}whencountcan be0— use a ternary.
.ios/.android split when you must.@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
onlineManagerto 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 withAbortController.
response.ok, type your errors, and validate shapes — types vanish at runtime.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-cryptois ~58× faster thancrypto-js, andnative-stackbeats the JS stack. - Bundle: enable tree-shaking (Expo SDK 52+ flags) and R8 (
minifyEnabled+shrinkResources); lazy-load rare screens withReact.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.sofiles).
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(notexpo-av),react-native-safe-area-context,process.env.EXPO_OS. - Responsiveness: a first-child
ScrollViewwithcontentInsetAdjustmentBehavior="automatic",useWindowDimensionsoverDimensions.get(), flexbox over measuring. - Styling: CSS
boxShadow(not legacy shadow/elevation), flexgapover margins,borderCurve: 'continuous'. - Native nav UX:
Link.Preview+Link.Menucontext menus,presentation: 'modal' | 'formSheet',NativeTabs, SF Symbols viaexpo-imagesf:.
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.
27 · Mobile security in depth
Core: answer security in four threat-model buckets — secrets, 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.
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.
.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:
runtimeVersiongates 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.
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-purgetemplate diff, one or two minors at a time, both platforms must build.
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:
NativeTabswith per-triggerIcon(sf:/md:),Label,Badge, arole="search"tab, andminimizeBehavior. - 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.
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:
useStatefor local state,useEffectfor side effects (always clean up). - Lists:
FlatList(virtualized), nevermapinside aScrollViewfor long data. - Layout: Flexbox (
flexDirectiondefaults to column),StyleSheetor NativeWind. - Data:
fetch+ checkresponse.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.
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.
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.
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.includesin a loop.
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.jsonpins won't match an older project. Add only the missing packages withnpx expo install <pkg>, which resolves the SDK-correct version for your app. - app.json config plugins — managed examples have no
ios/orandroid/directories; all native setup (permissions, native deps, build settings) is declared as config plugins and permission strings inapp.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
.envholds 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.md → package.json → app.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.
+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.+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.
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 withdtype: "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 ownavailability()/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.
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.
- 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>needsallow="language-model"to use the Prompt API.
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.
@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.