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:

  1. Execute all synchronous code on the call stack
  2. Drain the entire microtask queue (Promises, queueMicrotask)
  3. Execute ONE macrotask (setTimeout, setInterval, I/O)
  4. 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:

  1. Callbacks β€” Legacy, leads to callback hell
  2. Promises β€” Chainable, better error handling
  3. async/await β€” Syntactic sugar over Promises, sequential-looking async code
  4. Observables (RxJS) β€” Powerful for event streams, but heavy for most RN apps
  5. AsyncGenerators β€” Useful for paginated data fetching

πŸ’» Code Example

codeTap to expand β›Ά
1// === EVENT LOOP EXECUTION ORDER ===
2
3console.log('1 - sync'); // 1st: Call stack
4
5setTimeout(() => console.log('2 - macrotask'), 0); // Queued to macrotask
6
7Promise.resolve().then(() => {
8 console.log('3 - microtask'); // 2nd: Microtask queue
9 // Microtask inside microtask β€” still runs before macrotask!
10 Promise.resolve().then(() => {
11 console.log('4 - nested microtask'); // 3rd: Still microtask
12 });
13});
14
15queueMicrotask(() => console.log('5 - queueMicrotask')); // 4th: Microtask
16
17console.log('6 - sync'); // Before any async
18
19// Output order: 1, 6, 3, 5, 4, 2
20// All sync β†’ all microtasks (including nested) β†’ one macrotask
21
22// === PRODUCTION ASYNC PATTERNS IN REACT NATIVE ===
23
24// 1. Parallel API calls with error isolation
25async function loadDashboard(): Promise<DashboardData> {
26 const [userResult, feedResult, notifResult] = await Promise.allSettled([
27 fetchUser(),
28 fetchFeed(),
29 fetchNotifications(),
30 ]);
31
32 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}
41
42// 2. Cancellable async operation with AbortController
43function useCancellableFetch<T>(url: string) {
44 const [data, setData] = useState<T | null>(null);
45 const [error, setError] = useState<Error | null>(null);
46
47 useEffect(() => {
48 const controller = new AbortController();
49
50 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 }
62
63 fetchData();
64 return () => controller.abort(); // Cancel on unmount or URL change
65 }, [url]);
66
67 return { data, error };
68}
69
70// 3. Async generator for paginated data
71async function* fetchPaginatedData<T>(
72 endpoint: string,
73 pageSize: number = 20
74): AsyncGenerator<T[]> {
75 let cursor: string | null = null;
76 let hasMore = true;
77
78 while (hasMore) {
79 const params = new URLSearchParams({ limit: String(pageSize) });
80 if (cursor) params.set('cursor', cursor);
81
82 const response = await fetch(`${endpoint}?${params}`);
83 const { data, nextCursor } = await response.json();
84
85 yield data;
86
87 cursor = nextCursor;
88 hasMore = !!nextCursor;
89 }
90}
91
92// Usage with FlatList infinite scroll
93async 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:

  1. 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');
    
  2. Implement Promise.allSettled from scratch using Promise.all
  3. Build a retry mechanism with exponential backoff for API calls
  4. Create an async task queue that processes max 3 concurrent operations
  5. Implement InteractionManager.runAfterInteractions equivalent using event loop knowledge
  6. 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.