Thursday, October 2, 2025

Eventloop, micro macrotasks, promise async await,ui performance, debug

JavaScript Interview Questions Answers

1. What is Event Loop?

Answer:

"The Event Loop is a mechanism in JavaScript that handles asynchronous operations even though JavaScript is single-threaded. It continuously checks if the call stack is empty, and if it is, it moves tasks from the task queues to the call stack for execution.

Let me explain with an example:

console.log('First');

setTimeout(() => {
  console.log('Second');
}, 0);

console.log('Third');

// Output: First, Third, Second

Here's what happens:

  1. JavaScript executes synchronous code first - so 'First' and 'Third' print immediately
  2. The setTimeout is sent to Web APIs, even with 0ms delay
  3. After the callback is ready, it goes to the callback queue
  4. The Event Loop checks: "Is call stack empty?" If yes, it moves the callback from queue to call stack
  5. Then 'Second' prints

The Event Loop ensures non-blocking operations - so our application doesn't freeze while waiting for data from APIs, timers, or file operations."

Follow-up points:

  • "The Event Loop has a priority system - it checks microtask queue before macrotask queue"
  • "This allows JavaScript to handle multiple operations without blocking the main thread"

2. What are the differences between Macrotask and Microtask queues?

Answer:

"In the Event Loop, there are two types of queues with different priorities - Microtask Queue and Macrotask Queue.

Microtask Queue (Higher Priority):

  • Contains: Promise callbacks (.then, .catch, .finally), async/await, queueMicrotask()
  • Executes: ALL microtasks are executed before moving to the next macrotask
  • Priority: High - executes first

Macrotask Queue (Lower Priority):

  • Contains: setTimeout, setInterval, setImmediate, I/O operations, UI rendering
  • Executes: ONE macrotask at a time, then checks microtask queue again
  • Priority: Low - executes after all microtasks

Here's a practical example:

console.log('1 - Sync');

setTimeout(() => {
  console.log('2 - Macrotask (setTimeout)');
}, 0);

Promise.resolve().then(() => {
  console.log('3 - Microtask (Promise)');
});

console.log('4 - Sync');

// Output:
// 1 - Sync
// 4 - Sync
// 3 - Microtask (Promise)
// 2 - Macrotask (setTimeout)

Execution Order:

  1. All synchronous code first (1, 4)
  2. All microtasks (3)
  3. One macrotask (2)
  4. Check microtasks again (if any)
  5. Next macrotask... and so on

Key Difference: Microtasks always execute before the next macrotask, which is why Promises execute before setTimeout even when both are ready."


3. Differences between MAP, FILTER, and REDUCE methods

Answer:

"MAP, FILTER, and REDUCE are array methods that help process data without mutating the original array. Each serves a different purpose:

MAP - Transforms Each Element

Purpose: Creates a new array by transforming each element

const numbers = [1, 2, 3, 4];
const doubled = numbers.map(num => num * 2);
console.log(doubled); // [2, 4, 6, 8]
  • Returns: New array with same length as original
  • Use case: When you need to transform or modify each element
  • Example: Converting prices from dollars to rupees, extracting specific properties from objects

FILTER - Selects Specific Elements

Purpose: Creates a new array with elements that pass a condition

const numbers = [1, 2, 3, 4, 5, 6];
const evenNumbers = numbers.filter(num => num % 2 === 0);
console.log(evenNumbers); // [2, 4, 6]
  • Returns: New array with fewer or equal elements
  • Use case: When you need to select/filter elements based on condition
  • Example: Getting active users, filtering products by price range

REDUCE - Combines into Single Value

Purpose: Reduces array to a single value by accumulating results

const numbers = [1, 2, 3, 4];
const sum = numbers.reduce((accumulator, current) => {
  return accumulator + current;
}, 0);
console.log(sum); // 10
  • Returns: Single value (can be number, object, array, etc.)
  • Use case: When you need to calculate totals, combine data
  • Example: Calculating cart total, finding max value, grouping data

Comparison Table:

Method Returns Length Purpose
MAP New array Same as original Transform each element
FILTER New array Same or less Select elements by condition
REDUCE Single value N/A Combine/accumulate into one value

Real-World Example:

const products = [
  { name: 'Laptop', price: 1000, inStock: true },
  { name: 'Phone', price: 500, inStock: false },
  { name: 'Tablet', price: 300, inStock: true }
];

// FILTER: Get only in-stock products
const availableProducts = products.filter(p => p.inStock);
// [Laptop, Tablet]

// MAP: Get just the names
const productNames = products.map(p => p.name);
// ['Laptop', 'Phone', 'Tablet']

// REDUCE: Calculate total price of in-stock items
const totalValue = products
  .filter(p => p.inStock)
  .reduce((total, p) => total + p.price, 0);
// 1300

Key Point: All three methods are immutable - they don't change the original array, which is important for writing predictable, bug-free code."


4. How are Promises and Async/Await different? When to use Async/Await over Promises?

Answer:

"Promises and Async/Await are both used for handling asynchronous operations, but Async/Await is basically syntactic sugar built on top of Promises to make code more readable.

Promises (Traditional Approach)

function fetchUserData() {
  fetch('https://api.example.com/user/1')
    .then(response => response.json())
    .then(user => {
      console.log(user.name);
      return fetch(`https://api.example.com/posts/${user.id}`);
    })
    .then(response => response.json())
    .then(posts => {
      console.log(posts);
    })
    .catch(error => {
      console.error('Error:', error);
    });
}

Issues with Promises:

  • Chain of .then() can become nested (callback hell)
  • Harder to read when multiple operations
  • Error handling with .catch() for entire chain
  • Difficult to use with loops or conditionals

Async/Await (Modern Approach)

async function fetchUserData() {
  try {
    const response = await fetch('https://api.example.com/user/1');
    const user = await response.json();
    console.log(user.name);
    
    const postsResponse = await fetch(`https://api.example.com/posts/${user.id}`);
    const posts = await postsResponse.json();
    console.log(posts);
  } catch (error) {
    console.error('Error:', error);
  }
}

Advantages of Async/Await:

  • Looks like synchronous code - easier to read
  • Better error handling with try/catch
  • Easier to debug (can set breakpoints on each line)
  • Works well with loops and conditionals
  • No .then() chaining

Key Differences:

Aspect Promises Async/Await
Syntax .then().catch() chaining try/catch with await
Readability Can get messy with multiple chains Clean, sequential code
Error Handling .catch() for entire chain try/catch for specific blocks
Debugging Harder to debug chains Easier - line-by-line debugging
Conditional Logic Difficult Easy

When to Use Async/Await Over Promises:

Use Async/Await when:

  1. Sequential operations where each depends on previous:
async function processOrder() {
  const order = await createOrder();
  const payment = await processPayment(order.id);
  const shipping = await arrangeShipping(payment.id);
  return shipping;
}
  1. Complex conditional logic:
async function getUser(id) {
  const user = await fetchUser(id);
  
  if (user.isPremium) {
    return await fetchPremiumData(user.id);
  } else {
    return await fetchBasicData(user.id);
  }
}
  1. Error handling for specific operations:
async function saveData() {
  try {
    const validated = await validateData(data);
    await saveToDatabase(validated);
    console.log('Saved successfully');
  } catch (error) {
    console.error('Validation or save failed:', error);
  }
}
  1. Using with loops:
async function processItems(items) {
  for (const item of items) {
    await processItem(item); // Wait for each to complete
  }
}

Use Promises when:

  1. Parallel operations (use Promise.all):
// Fetch multiple things at once
Promise.all([
  fetch('/api/users'),
  fetch('/api/products'),
  fetch('/api/orders')
])
.then(([users, products, orders]) => {
  // All done together
});
  1. Fire and forget (don't need to wait):
saveAnalytics(data).then(() => console.log('Logged'));
// Continue without waiting

Important: Async/Await still uses Promises under the hood - an async function always returns a Promise."


5. How to improve UI performance if application is performing slow?

Answer:

"If my application is performing slowly, I would approach it systematically by identifying the bottleneck first, then applying appropriate optimization techniques.

Step 1: Identify the Problem

Performance Profiling:

  • Use Chrome DevTools Performance tab to record and analyze
  • Check Lighthouse audit for performance score
  • Measure using Performance API: performance.now()

Step 2: Common Performance Issues & Solutions

A. JavaScript Execution Issues

1. Heavy Computations Blocking Main Thread

// Problem: Blocking operation
function processLargeData(data) {
  // Heavy calculation blocks UI
  return data.map(item => complexCalculation(item));
}

// Solution 1: Use Web Workers
const worker = new Worker('worker.js');
worker.postMessage(data);
worker.onmessage = (e) => {
  const result = e.data;
};

// Solution 2: Break into chunks with setTimeout
function processInChunks(data, chunkSize = 100) {
  let index = 0;
  
  function processChunk() {
    const chunk = data.slice(index, index + chunkSize);
    chunk.forEach(item => processItem(item));
    
    index += chunkSize;
    
    if (index < data.length) {
      setTimeout(processChunk, 0); // Give UI time to breathe
    }
  }
  
  processChunk();
}

2. Unnecessary Re-renders (React)

// Problem: Component re-renders on every parent update
function UserList({ users, theme }) {
  return users.map(user => <UserCard user={user} />);
}

// Solution: Use React.memo
const UserCard = React.memo(({ user }) => {
  return <div>{user.name}</div>;
});

// Solution: Use useMemo for expensive calculations
const sortedUsers = useMemo(() => {
  return users.sort((a, b) => a.name.localeCompare(b.name));
}, [users]);

3. Memory Leaks

// Problem: Event listeners not cleaned up
useEffect(() => {
  window.addEventListener('scroll', handleScroll);
  // Missing cleanup
}, []);

// Solution: Clean up properly
useEffect(() => {
  window.addEventListener('scroll', handleScroll);
  
  return () => {
    window.removeEventListener('scroll', handleScroll); // Cleanup
  };
}, []);

B. Network & Resource Loading

1. Too Many HTTP Requests

// Problem: Loading all data at once
fetch('/api/products') // 10,000 products

// Solution: Implement pagination
fetch('/api/products?page=1&limit=20')

// Solution: Lazy loading
const ProductImage = ({ src }) => {
  return <img loading="lazy" src={src} />;
};

2. Large Bundle Size

// Problem: Importing entire library
import _ from 'lodash'; // Entire library

// Solution: Import only what you need
import debounce from 'lodash/debounce'; // Just one function

// Solution: Code splitting
const AdminPanel = lazy(() => import('./AdminPanel'));

3. Unoptimized Images

<!-- Problem: Large image files -->
<img src="photo.jpg" /> <!-- 5MB image -->

<!-- Solution: Use optimized formats -->
<img src="photo.webp" width="300" height="200" />

<!-- Solution: Responsive images -->
<img 
  srcset="small.jpg 300w, medium.jpg 600w, large.jpg 1200w"
  sizes="(max-width: 600px) 300px, 600px"
/>

C. DOM Manipulation

1. Excessive DOM Updates

// Problem: Multiple DOM updates
for (let i = 0; i < 1000; i++) {
  const div = document.createElement('div');
  div.textContent = i;
  document.body.appendChild(div); // Reflow each time
}

// Solution: Batch DOM updates
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
  const div = document.createElement('div');
  div.textContent = i;
  fragment.appendChild(div);
}
document.body.appendChild(fragment); // Single reflow

2. Lack of Virtualization for Large Lists

// Problem: Rendering 10,000 items
{items.map(item => <ListItem item={item} />)}

// Solution: Use virtual scrolling (react-window)
import { FixedSizeList } from 'react-window';

<FixedSizeList
  height={500}
  itemCount={items.length}
  itemSize={50}
>
  {({ index, style }) => (
    <div style={style}>{items[index].name}</div>
  )}
</FixedSizeList>

D. Debouncing & Throttling

Problem: Too many function calls

// Problem: Search on every keystroke
<input onChange={(e) => searchAPI(e.target.value)} />

// Solution: Debounce (wait for pause in typing)
const debouncedSearch = debounce((value) => {
  searchAPI(value);
}, 300);

<input onChange={(e) => debouncedSearch(e.target.value)} />

// Solution: Throttle (limit frequency)
const throttledScroll = throttle(() => {
  checkScrollPosition();
}, 200);

window.addEventListener('scroll', throttledScroll);

Performance Optimization Checklist:

Frontend:

  • [ ] Minimize JavaScript bundle size (code splitting)
  • [ ] Implement lazy loading for routes and images
  • [ ] Use React.memo / useMemo / useCallback appropriately
  • [ ] Debounce/throttle event handlers
  • [ ] Virtualize long lists
  • [ ] Optimize images (WebP, proper sizing)
  • [ ] Remove unused code (tree shaking)
  • [ ] Cache API responses
  • [ ] Use service workers for offline support

Network:

  • [ ] Enable gzip/brotli compression
  • [ ] Use CDN for static assets
  • [ ] Implement HTTP/2 or HTTP/3
  • [ ] Add caching headers
  • [ ] Minimize API calls (pagination, infinite scroll)
  • [ ] Prefetch critical resources

In an interview, I would say: 'First, I'd use Chrome DevTools to identify the bottleneck - whether it's JavaScript execution, network requests, or rendering. Then I'd apply targeted optimizations like code splitting, lazy loading, memoization, or debouncing based on the specific issue.'"


6. Factors Responsible for UI Performance & Debugging Techniques

Answer:

A. Factors Responsible for UI Performance

"UI performance is affected by several factors:

1. JavaScript Execution Time

  • Problem: Heavy computations block the main thread
  • Impact: UI becomes unresponsive, animations stutter
  • Example: Processing large datasets, complex calculations

2. Rendering & Painting

  • Problem: Frequent DOM changes cause reflows and repaints
  • Impact: Janky animations, slow scrolling
  • Example: Animating properties that trigger layout (width, height)

3. Network Latency

  • Problem: Slow API responses, large resource downloads
  • Impact: Long loading times, delayed interactions
  • Example: Unoptimized images, too many HTTP requests

4. Memory Leaks

  • Problem: Unused memory not garbage collected
  • Impact: Application slows down over time, crashes
  • Example: Event listeners not removed, unclosed connections

5. Bundle Size

  • Problem: Large JavaScript files take time to download and parse
  • Impact: Slow initial page load
  • Example: Including entire libraries instead of specific functions

6. Inefficient Re-renders (React/Vue/Angular)

  • Problem: Components re-render unnecessarily
  • Impact: Wasted computation, slow updates
  • Example: Not using memoization, improper state management

B. Debugging Techniques

When I'm stuck on a performance issue, here's my systematic approach:

1. Chrome DevTools - Performance Tab

How I use it:

1. Open DevTools (F12)
2. Go to Performance tab
3. Click Record button
4. Perform the slow action
5. Stop recording
6. Analyze the flame graph

What I look for:

  • Long tasks (yellow/red blocks) - JavaScript blocking main thread
  • Layout/Reflow (purple) - DOM changes causing recalculation
  • Rendering (green) - Paint and composite operations
  • Network requests - Slow or too many requests

Example finding:

If I see a long yellow block labeled 'processData()',
I know that function is blocking the UI.
Solution: Break it into chunks or use Web Worker.

2. Console Timing

Measure specific operations:

// Measure function execution time
console.time('dataProcessing');
processLargeDataset(data);
console.timeEnd('dataProcessing');
// Output: dataProcessing: 2341ms

// Measure render time
const startTime = performance.now();
renderComponent();
const endTime = performance.now();
console.log(`Render took ${endTime - startTime}ms`);

3. React DevTools Profiler

For React applications:

1. Install React DevTools extension
2. Open Profiler tab
3. Click record
4. Interact with app
5. Stop and analyze which components re-rendered and why

What I check:

  • Components that re-render frequently
  • Components with long render times
  • Unnecessary renders (props didn't change)

4. Network Tab Analysis

Check for network bottlenecks:

1. Open Network tab
2. Reload page or trigger action
3. Look for:
   - Large file sizes (>500KB)
   - Slow requests (>1s)
   - Too many requests (>50)
   - Requests blocking others (waterfall)

Common issues I find:

  • Unoptimized images (5MB instead of 50KB)
  • API calls made in loops (N+1 problem)
  • Missing caching headers

5. Memory Profiler

Detect memory leaks:

1. Go to Memory tab
2. Take heap snapshot
3. Interact with app (add/remove components)
4. Take another snapshot
5. Compare snapshots
6. Look for objects that should be garbage collected but aren't

Red flags:

  • Memory consistently increasing
  • Event listeners piling up
  • Detached DOM nodes

6. Lighthouse Audit

Quick performance check:

1. Open DevTools
2. Go to Lighthouse tab
3. Select Performance
4. Generate report
5. Review recommendations

Gives scores for:

  • First Contentful Paint (FCP)
  • Largest Contentful Paint (LCP)
  • Total Blocking Time (TBT)
  • Cumulative Layout Shift (CLS)

7. Console Logging Strategy

Strategic debugging:

// Problem: Don't know why component re-renders
function MyComponent({ data, config }) {
  console.log('MyComponent rendered');
  console.log('Data:', data);
  console.log('Config:', config);
  
  useEffect(() => {
    console.log('Effect ran - data changed');
  }, [data]);
  
  return <div>...</div>;
}

8. Browser's Source Tab - Breakpoints

Step-by-step debugging:

1. Go to Sources tab
2. Find the problematic file
3. Set breakpoint (click line number)
4. Trigger the action
5. Execution pauses at breakpoint
6. Inspect variables, step through code

Types of breakpoints I use:

  • Line breakpoints - Stop at specific line
  • Conditional breakpoints - Stop only if condition true
  • Event listener breakpoints - Stop when event fires
  • Exception breakpoints - Stop when error thrown

9. Using debugger Statement

Quick debugging in code:

function calculateTotal(items) {
  let total = 0;
  
  debugger; // Execution pauses here
  
  items.forEach(item => {
    total += item.price;
  });
  
  return total;
}

10. Third-Party Performance Tools

When built-in tools aren't enough:

  • Sentry - Error tracking and performance monitoring
  • New Relic - Application performance monitoring
  • WebPageTest - Detailed performance analysis
  • Bundle Analyzer - Visualize bundle size

My Debugging Workflow (Step-by-Step)

When I encounter a bug or performance issue:

  1. Reproduce the issue consistently
    • Know the exact steps to trigger it
  2. Identify the scope
    • Is it frontend, backend, or network?
    • Use Network tab to check API responses
  3. Use appropriate tool
    • Performance issue → Performance Profiler
    • Logic bug → Console logging + Breakpoints
    • Memory issue → Memory Profiler
    • Rendering issue → React DevTools Profiler
  4. Isolate the problem
    • Comment out code sections
    • Binary search approach (disable half, see if issue persists)
  5. Form hypothesis
    • Based on data, guess the cause
  6. Test hypothesis
    • Make targeted change, see if it fixes issue
  7. Verify fix
    • Test in different browsers
    • Check edge cases
    • Measure performance improvement

Example Debugging Session:

Problem: 'Search is slow when typing'

// Step 1: Measure
console.time('search');
searchProducts(query);
console.timeEnd('search');
// Output: search: 1842ms (Too slow!)

// Step 2: Check what's happening
function searchProducts(query) {
  console.log('Search called with:', query); // Logs on every keystroke!
  fetch(`/api/search?q=${query}`)
    .then(response => response.json())
    .then(data => setResults(data));
}

// Step 3: Solution identified - too many API calls
// Apply debouncing
const debouncedSearch = debounce(searchProducts, 300);

In an interview, I would say: 'When debugging, I start by reproducing the issue, then use Chrome DevTools Performance tab to identify the bottleneck. If it's JavaScript execution, I use breakpoints and console logging. For rendering issues, I use React DevTools Profiler. For network issues, I check the Network tab. The key is using the right tool for the specific problem.'"

No comments:

Post a Comment