Event Loop, Microtasks & Async Patterns
π Concept
The JavaScript event loop is the runtime mechanism that enables non-blocking I/O in a single-threaded language. Understanding it deeply is critical for React Native performance because your JS thread is single-threaded β any blocking operation freezes the entire UI.
Event Loop Architecture:
ββββββββββββββββββββββββββββββββ
β Call Stack β β Synchronous code executes here
ββββββββββββββββββββββββββββββββ€
β Microtask Queue β β Promises, queueMicrotask, MutationObserver
β (drains completely before β
β moving to macrotask queue) β
ββββββββββββββββββββββββββββββββ€
β Macrotask Queue β β setTimeout, setInterval, I/O, UI rendering
β (one task per event loop β
β iteration) β
ββββββββββββββββββββββββββββββββ
Critical order of execution:
- Execute all synchronous code on the call stack
- Drain the entire microtask queue (Promises, queueMicrotask)
- Execute ONE macrotask (setTimeout, setInterval, I/O)
- Repeat from step 2 (check microtask queue again)
Why this matters in React Native:
- The JS thread runs an event loop that processes bridge messages, timers, and React rendering
- Long synchronous operations block the JS thread β UI becomes unresponsive
- Too many microtasks can starve macrotasks (setTimeout callbacks delayed)
InteractionManager.runAfterInteractions()schedules work after animations complete- Hermes engine has its own microtask implementation
Async patterns hierarchy:
- Callbacks β Legacy, leads to callback hell
- Promises β Chainable, better error handling
- async/await β Syntactic sugar over Promises, sequential-looking async code
- Observables (RxJS) β Powerful for event streams, but heavy for most RN apps
- AsyncGenerators β Useful for paginated data fetching
π» Code Example
1// === EVENT LOOP EXECUTION ORDER ===23console.log('1 - sync'); // 1st: Call stack45setTimeout(() => console.log('2 - macrotask'), 0); // Queued to macrotask67Promise.resolve().then(() => {8 console.log('3 - microtask'); // 2nd: Microtask queue9 // Microtask inside microtask β still runs before macrotask!10 Promise.resolve().then(() => {11 console.log('4 - nested microtask'); // 3rd: Still microtask12 });13});1415queueMicrotask(() => console.log('5 - queueMicrotask')); // 4th: Microtask1617console.log('6 - sync'); // Before any async1819// Output order: 1, 6, 3, 5, 4, 220// All sync β all microtasks (including nested) β one macrotask2122// === PRODUCTION ASYNC PATTERNS IN REACT NATIVE ===2324// 1. Parallel API calls with error isolation25async function loadDashboard(): Promise<DashboardData> {26 const [userResult, feedResult, notifResult] = await Promise.allSettled([27 fetchUser(),28 fetchFeed(),29 fetchNotifications(),30 ]);3132 return {33 user: userResult.status === 'fulfilled' ? userResult.value : null,34 feed: feedResult.status === 'fulfilled' ? feedResult.value : [],35 notifications: notifResult.status === 'fulfilled' ? notifResult.value : [],36 errors: [userResult, feedResult, notifResult]37 .filter(r => r.status === 'rejected')38 .map(r => (r as PromiseRejectedResult).reason),39 };40}4142// 2. Cancellable async operation with AbortController43function useCancellableFetch<T>(url: string) {44 const [data, setData] = useState<T | null>(null);45 const [error, setError] = useState<Error | null>(null);4647 useEffect(() => {48 const controller = new AbortController();4950 async function fetchData() {51 try {52 const response = await fetch(url, { signal: controller.signal });53 if (!response.ok) throw new Error(`HTTP ${response.status}`);54 const json = await response.json();55 setData(json);56 } catch (err) {57 if (err instanceof Error && err.name !== 'AbortError') {58 setError(err);59 }60 }61 }6263 fetchData();64 return () => controller.abort(); // Cancel on unmount or URL change65 }, [url]);6667 return { data, error };68}6970// 3. Async generator for paginated data71async function* fetchPaginatedData<T>(72 endpoint: string,73 pageSize: number = 2074): AsyncGenerator<T[]> {75 let cursor: string | null = null;76 let hasMore = true;7778 while (hasMore) {79 const params = new URLSearchParams({ limit: String(pageSize) });80 if (cursor) params.set('cursor', cursor);8182 const response = await fetch(`${endpoint}?${params}`);83 const { data, nextCursor } = await response.json();8485 yield data;8687 cursor = nextCursor;88 hasMore = !!nextCursor;89 }90}9192// Usage with FlatList infinite scroll93async function loadNextPage() {94 const result = await paginatedIterator.next();95 if (!result.done) {96 setItems(prev => [...prev, ...result.value]);97 }98}
ποΈ Practice Exercise
Event Loop Deep Dive:
- Predict the exact output order of this code:
setTimeout(() => console.log('A'), 0); Promise.resolve().then(() => console.log('B')); requestAnimationFrame(() => console.log('C')); queueMicrotask(() => console.log('D')); console.log('E'); - Implement
Promise.allSettledfrom scratch usingPromise.all - Build a retry mechanism with exponential backoff for API calls
- Create an async task queue that processes max 3 concurrent operations
- Implement
InteractionManager.runAfterInteractionsequivalent using event loop knowledge - Write a function that detects if the JS thread is being blocked (using setTimeout timing drift)
β οΈ Common Mistakes
Flooding the microtask queue with recursive Promise.resolve().then() β this starves the UI thread and prevents rendering
Using await in a loop instead of Promise.all β sequential when parallel is possible, NΓlatency instead of max(latency)
Not handling Promise rejection β unhandled rejections crash the app in production
Blocking the JS thread with synchronous operations (JSON.parse of large payloads, heavy computation) β freezes the UI
Mixing setTimeout(fn, 0) with Promise.then() and expecting them to execute in order β microtasks always come first
πΌ Interview Questions
π€ Mock Interview
Mock interview is powered by AI for Event Loop, Microtasks & Async Patterns. Login to unlock this feature.