// The Apprentice's Pact
This is not a list of tutorials to watch. It is a forge. You will be given concepts, shown the patterns, then immediately thrown into real code. The only way to become a senior engineer is to make senior-level decisions — hundreds of times — until they become instinct. Every stage ends with a project you should be embarrassed to show at first, and proud to show after iteration.
// How to Use This Roadmap
- Read the tasks, then build before reading resources
- Check off tasks as you genuinely understand them
- Each assignment must be GitHub-published
- Shortcuts are for speed — learn the long way first
- If you feel comfortable, you're moving too slowly
- TypeScript is non-negotiable from day one
- Re-read this map every 2 weeks and update your pace
JavaScript & TypeScript Core
- Closures, scope, hoisting, the event loop
- Prototypal inheritance vs. class syntax
- ES6+: destructuring, spread/rest, optional chaining, nullish coalescing
- Promises, async/await, error handling patterns
- Array methods: map, filter, reduce, flatMap, find — no loops
- Modules: ESM import/export, CommonJS difference
- TypeScript: types, interfaces, union/intersection types
- TypeScript generics — write 10 generic utility functions
- TypeScript utility types: Partial, Required, Pick, Omit, Record, ReturnType
- tsconfig.json: strict mode, paths, target — understand every key flag
- Type narrowing: typeof, instanceof, discriminated unions
- Write 50 TypeScript katas on Exercism/TypeHero
Create a TypeScript library (no React yet) of 8 async utility functions — fully typed generics, JSDoc, and unit tests. Think of it as your own mini Lodash-async.
- retry<T>(fn, times): retries async fn on fail
- timeout<T>(fn, ms): rejects after ms
- memoizeAsync<T>(fn): caches by args
- batch<T>(items, fn, concurrency): parallel with limit
- All functions typed with strict generics
- 100% test coverage with Vitest
- Published as a GitHub repo with a README
Set "strict": true in tsconfig immediately. Never turn it off. Every error it shows is a future bug.
Let TS infer wherever possible. Only annotate function params, return types, and API boundaries.
Use `unknown` instead of `any`. Then narrow it. `any` is a silent type-system hole.
Use `as const` on config objects and string arrays to get literal types. Unlocks powerful patterns.
React Foundations
- JSX: what it compiles to, why keys matter, reconciliation basics
- Functional components with typed props (React.FC vs plain functions)
- useState, useReducer — know when each fits
- useEffect: dependency arrays, cleanup, avoiding common pitfalls
- useRef: DOM refs vs mutable ref container
- useMemo, useCallback — learn WHEN they help (and when they don't)
- Context API: creating, typing, providing, consuming
- Component composition: children, render props, compound components
- Forms: controlled vs uncontrolled, validation pattern
- React Router v6: routes, nested routes, loaders, useNavigate
- Vite: set up a project from scratch, understand the config
- ESLint + Prettier: configure for React + TypeScript
A fully client-side budgeting app with no backend. Real-world state management practice.
- Add/edit/delete transactions (typed with discriminated unions)
- Category breakdown with live chart (use Recharts)
- Filter by month, category, type
- Persist to localStorage with a typed custom hook
- All state managed with useReducer + Context
- Full TypeScript — no implicit any errors
- Deploy to Vercel or Netlify
Use `React.PropsWithChildren<T>` or explicit `children: React.ReactNode` for wrapper components.
Type handlers inline: `(e: React.ChangeEvent<HTMLInputElement>)`. Hover over the element to discover the type.
Prefer plain function syntax over React.FC — it's more flexible, gives better error messages.
Every function passed to a child or dep array needs useCallback. Every computed value needs useMemo. Check with the profiler, then optimize.
Mentor secret: When you can't understand a re-render, add a console.log('render:', Date.now()) at the top of the component. Then open the Profiler. Most engineers never develop this instinct before their third year — build it in month two.
React Intermediate
- Custom hooks: extract and reuse stateful logic, type them fully
- TanStack Query (React Query): queries, mutations, invalidation, optimistic updates
- Zustand: global state with typed slices, devtools, middleware
- Zod: schema validation, type inference from schemas, safe parsing
- React Hook Form + Zod: fully typed form + validation pipeline
- Error boundaries: class-based, react-error-boundary library
- Suspense: lazy loading routes, components, data (experimental)
- Testing with Vitest + React Testing Library: render, user-event, queries
- Accessibility (a11y): ARIA roles, keyboard nav, screen reader testing
- Component library: build 10 generic, typed, accessible components
- Next.js 14+: App Router, Server Components, client/server split
- API integration: fetch with typed responses, error states, loading states
Clone the core UI of a real SaaS tool (Vercel's dashboard, Linear, or Notion). Backend optional (mock with MSW).
- Next.js App Router — server and client components correctly split
- TanStack Query for all data fetching with loading/error states
- Zustand for UI state (sidebar, modals, filters)
- React Hook Form + Zod for 3 different form types
- 10 tests (RTL): at least 3 testing async behavior
- Storybook: document 5 core components
- Zero TypeScript errors, strict mode on
Create a typed query key factory object. Never write inline string arrays as query keys — it kills consistency.
Use `z.infer<typeof schema>` for all API response types. Single source of truth for type + validation.
Mock Service Worker lets you test data-fetching flows without a real backend. Set it up once, use forever.
Default to Server Components in Next.js App Router. Add 'use client' only when you need interactivity or browser APIs.
React Advanced
- React Fiber architecture: reconciliation, diffing, commit phases
- Concurrent features: useTransition, useDeferredValue, Suspense boundaries
- Advanced patterns: HOC, render props, compound components, headless UI
- forwardRef + useImperativeHandle: when and why
- Performance: virtualization with TanStack Virtual (replace any long lists)
- Code splitting: React.lazy, dynamic imports, bundle analysis
- Web Workers with React: offload heavy computation
- Advanced TypeScript: conditional types, mapped types, template literal types
- Design systems: theming with CSS variables, design tokens
- Monorepo setup with Turborepo: shared packages, build pipelines
- CI/CD: GitHub Actions for lint, test, type-check, deploy
- Web Vitals: LCP, FID, CLS — measure and improve them
A Notion-like block editor with real-time sync (use Liveblocks or Supabase Realtime). This forces concurrent state, optimistic updates, and performance at scale.
- Virtualized block list — no performance drops at 500 blocks
- useTransition for all typing/rendering flows
- Optimistic mutations with rollback on error
- Undo/redo with useReducer history pattern
- Web Worker for markdown parsing
- Bundle size under 200KB initial JS (check with analyzer)
- Lighthouse score: 90+ on Performance
Use template literal types to build typed route strings, CSS classnames, and event names. Massive DX boost.
Wrap non-urgent state updates (filtering long lists, tab changes) in startTransition. Keeps UI responsive.
Build logic-first components (headless), then layer styles. See Radix UI and TanStack Table for inspiration.
Never memoize speculatively. Always profile first, then optimize exactly what the Profiler shows is slow.
React Native Foundations
- Expo vs bare RN: understand the tradeoffs and when to pick each
- Core primitives: View, Text, TextInput, ScrollView, FlatList, SectionList
- StyleSheet: how it differs from CSS, platform-specific styles
- Flexbox in RN: defaultAxis differences from web
- Navigation: Expo Router (file-based) + React Navigation v6 stacks, tabs, drawers
- Platform APIs: Keyboard, Dimensions, StatusBar, SafeAreaView
- Native modules: what they are, how to use community ones
- Async Storage, SecureStore, MMKV for persistence
- Image handling: Expo Image, caching, lazy loading, blurhash
- Gestures: React Native Gesture Handler — Pan, Tap, Swipe
- Keyboard avoiding patterns, bottom sheets, modals
- App lifecycle: AppState, background tasks, deep linking basics
A Twitter/X-style feed. Forces you to handle all core RN patterns: lists, navigation, forms, persistence, and cross-platform parity.
- Tab + Stack navigation (Expo Router)
- Infinite scroll FlatList with skeleton loading
- Post creation: image picker, text input, character count
- MMKV persistence for draft posts and auth token
- Pull-to-refresh with optimistic UI
- Works on iOS Simulator AND Android Emulator identically
- No TypeScript errors, proper platform-specific file extensions used
Split platform-specific code at the file level, not with Platform.OS conditionals inside components.
Always use a stable, unique string — not index. Missing this is the #1 cause of list bugs in production RN apps.
Implement getItemLayout on fixed-height FlatLists. Enables scroll-to-index and eliminates layout thrashing.
Never write style objects inline. Use StyleSheet.create for memoization and RN's style optimization pipeline.
Critical difference: RN's default flex direction is column, not row. This trips up every web dev for the first two weeks. Write it on a sticky note.
React Native Advanced
- Reanimated 3: useSharedValue, useAnimatedStyle, withTiming, withSpring
- Gesture Handler + Reanimated: swipe-to-dismiss, draggable cards, bottom sheets
- Skia (React Native Skia): custom drawing, gradients, canvas animations
- New Architecture: JSI, Fabric renderer, TurboModules — read the RFC
- Write a basic native module (Swift/Kotlin): expose a native API to JS
- Push notifications: Expo Notifications, FCM, APNs setup
- Background tasks: expo-background-fetch, expo-task-manager
- Offline-first architecture: optimistic UI, conflict resolution
- Deep linking: universal links (iOS), App Links (Android), Expo Router integration
- Performance: Flashlist (Shopify), hermes engine, bundle profiling
- OTA updates: Expo Updates, rollout strategy
- EAS Build: set up build profiles, submit to App Store + Play Store
A Strava/Nike Run Club–style app. This is your portfolio centerpiece. It must be shipped to TestFlight and Google Play Internal Testing.
- Animated onboarding (Reanimated 3, Skia gradients)
- Gesture-driven workout log (swipe-to-delete, drag reorder)
- Custom animated progress ring (React Native Skia)
- Offline-first with sync on reconnect (MMKV + TanStack Query)
- Push notifications for workout reminders
- Background step counting (expo-task-manager)
- Shipped to TestFlight + Play Internal via EAS Build
Reanimated worklets run on the UI thread. Never reference JS-thread state inside them without `runOnJS()`.
Always use FlashList over FlatList for any list >50 items. Drop-in replacement, 10× faster.
Set up development, preview, and production build profiles early. Switching profiles mid-project is painful.
Enable Hermes (on by default in Expo 50+). Smaller bundle, faster startup, better garbage collection.
The Senior Edge
- Architect a feature from scratch: write the RFC, pick the stack, plan the API contract
- Lead a code review: give written feedback on a PR from a junior dev (find OSS to review)
- Micro-frontend architecture: understand Module Federation, when it helps
- Contribute to an OSS React/RN library: fix a bug, write a new feature
- Security: XSS in React, token handling in RN, certificate pinning
- Observability: Sentry integration (React + RN), custom error tracking, performance monitoring
- Feature flags: LaunchDarkly or Unleash — implement without touching the UI layer
- Mentor a mock junior: explain reconciliation, hooks rules, and the event loop in simple terms
- Interview prep: solve 10 React-specific system design questions in writing
- Write a technical blog post on something you struggled with in this roadmap
- Mock interviews: 5 technical, 2 behavioral, 1 system design — record yourself
- Build your portfolio: 3 projects on GitHub, each with a README, live demo, and architecture decision log
A web app (Next.js) + mobile app (Expo) sharing a monorepo with shared TypeScript packages. This is your proof-of-work. It should look, perform, and function like a real product that a paying user would use.
- Turborepo monorepo: packages/ui (shared components), packages/types (shared TS types), packages/utils
- Web: Next.js App Router, Server Actions, streaming, optimistic UI
- Mobile: Expo + Expo Router, Reanimated 3 animations, FlashList, EAS Build
- Shared auth: JWT with secure storage on mobile, HttpOnly cookies on web
- Real backend: Hono or Next.js API routes, Drizzle ORM, PostgreSQL
- Offline-first mobile with sync, push notifications, background tasks
- CI/CD: GitHub Actions — type-check + test + build + deploy on every PR
- Sentry monitoring on both platforms
- Architecture Decision Record (ADR) document in the repo
- Live on the web, TestFlight + Play Store internal testing
React Mastery
- Explain reconciliation & Fiber without googling
- Debug any re-render with DevTools Profiler
- Write advanced TS generics for React patterns
- Architecture a state management solution from scratch
- Optimize Web Vitals to 90+ Lighthouse on a real app
- Build accessible, tested component libraries
- Teach Server Components vs Client Components tradeoffs
React Native Mastery
- Build 60fps animations with Reanimated worklets
- Ship to both App Store and Play Store via EAS
- Debug native crashes with Flipper / native logs
- Write a custom native module (Swift + Kotlin)
- Implement offline-first with conflict resolution
- Profile and fix performance bottlenecks with Systrace
- Set up deep linking, push notifications, background tasks
Engineering Leadership
- Write ADRs that junior devs can follow
- Give constructive, specific code review feedback
- Estimate project complexity with written justification
- Communicate technical risk to non-technical stakeholders
- Mentor at least one developer through a concept
- Have an OSS contribution or published blog post
- Pass a senior system design interview on camera
When you can check all 21 items above and explain every one to a stranger, you are interview-ready for a senior role. The projects are your evidence. The checklist is your standard. The blog posts are your voice. Go.