Frontend Developer Interview Questions & Answers

Frontend Developer Interview Questions & Answers

Comprehensive Guide Across Modern Web Technologies


Question 1: Virtual DOM Reconciliation and Diffing Algorithm

Difficulty: High

Role: Senior Frontend Engineer

Level: Senior (L4-L5, 4-6 Years of Experience)

Company Examples: Meta, Google, Netflix, Airbnb

Question: “Walk me through how React’s reconciliation algorithm works. Explain the diffing algorithm, what the Virtual DOM is, and why React uses it instead of directly manipulating the real DOM. Can you describe edge cases where reconciliation might behave unexpectedly?”


1. What is This Question Testing?

This question tests critical Senior Frontend Engineer competencies:

  • React Internals Mastery: Can you explain beyond API usage to understand how React works under the hood?
  • Performance Awareness: Do you understand why Virtual DOM exists and its performance implications?
  • Technical Communication: Can you explain complex algorithms in simple terms?
  • Edge Case Thinking: Can you identify scenarios where reconciliation behaves unexpectedly?
  • Optimization Knowledge: Do you know when reconciliation can be optimized (keys, React.memo, shouldComponentUpdate)?

The interviewer wants to see if you’re a Senior Frontend Engineer who understands React’s core architecture, not just someone who uses React APIs.


2. Framework to Answer This Question

Use the “Concept → Algorithm → Optimization Framework” with these components:

Structure:
1. Virtual DOM Definition - What it is and why it exists
2. Reconciliation Process - How React compares trees
3. Diffing Algorithm - Heuristics React uses for efficient comparison
4. Key-Based Reconciliation - Why keys matter in lists
5. Edge Cases - Scenarios where reconciliation behaves unexpectedly
6. Optimization Strategies - React.memo, useMemo, keys

Key Principles:
- Start with “why” before “how”
- Use simple analogies for complex concepts
- Connect theory to practical performance implications
- Discuss trade-offs (speed vs accuracy)


3. The Answer

Answer:

The Virtual DOM is an in-memory JavaScript representation of the actual DOM tree. React maintains this lightweight copy to optimize UI updates. Let me walk through the complete reconciliation process.

First, why Virtual DOM exists:

Direct DOM manipulation is expensive. Every DOM change can trigger reflows and repaints—browser operations that recalculate layout and repaint pixels. If you update 10 elements individually, the browser potentially recalculates layout 10 times.

React’s Virtual DOM batches updates. Instead of immediately touching the real DOM, React updates its in-memory representation first, calculates the minimal set of changes needed, then applies them all at once in a single batch. This reduces browser reflows from potentially hundreds to just one.

Second, the reconciliation algorithm:

When state or props change, React creates a new Virtual DOM tree. Reconciliation is the process of comparing this new tree with the previous tree to determine what changed.

The algorithm has two phases:

Render Phase (Reconciliation):
- React walks through both trees simultaneously
- Identifies differences (additions, updates, deletions)
- Builds a list of changes needed
- This phase is interruptible (thanks to Fiber architecture)

Commit Phase:
- React applies the calculated changes to the actual DOM
- This phase is synchronous and cannot be interrupted
- Changes are applied in a single batch

Third, the diffing algorithm heuristics:

Comparing two trees algorithmically would be O(n³) complexity—too slow for real apps. React uses heuristics to achieve O(n):

Heuristic 1: Different element types produce different trees

// Old tree<div><Counter /></div>// New tree<span><Counter /></span>// React completely destroys <div> and Counter instance// Creates new <span> and new Counter instance// Even though Counter props didn't change!

Heuristic 2: Keys identify which children have changed

// Without keys - React sees positional changes<ul>  <li>Duke</li>  <li>Villanova</li></ul>// After adding Connecticut at the top<ul>  <li>Connecticut</li>  // React thinks Duke changed to Connecticut
  <li>Duke</li>          // React thinks Villanova changed to Duke
  <li>Villanova</li>     // React thinks this is newly added
</ul>// Result: Re-renders all 3 items// With keys - React identifies actual change<ul>  <li key="duke">Duke</li>  <li key="villanova">Villanova</li></ul>// After adding Connecticut<ul>  <li key="connecticut">Connecticut</li>  // React knows this is new
  <li key="duke">Duke</li>                // React knows this didn't change
  <li key="villanova">Villanova</li>      // React knows this didn't change
</ul>// Result: Only renders 1 new item

Heuristic 3: Recursively process children

React compares children element by element. If lists don’t have keys, React matches by position, which causes unnecessary re-renders when items are inserted or reordered.

Fourth, edge cases where reconciliation behaves unexpectedly:

Edge Case 1: Changing component type destroys state

// Toggling between input types{isPassword ?
  <input type="password" value={value} /> :
  <input type="text" value={value} />}
// React sees these as different components (different type attribute)// Destroys and recreates input, losing focus state// Fix: Use conditional attribute instead<input type={isPassword ? 'password' : 'text'} value={value} />

Edge Case 2: Using array index as key

// BAD: Using index as key{items.map((item, index) =>
  <Item key={index} data={item} />)}
// If you delete the first item, all items shift position// React thinks item at index 0 changed (when it was deleted)// Causes unnecessary re-renders of all items// Fix: Use stable unique IDs{items.map(item =>
  <Item key={item.id} data={item} />)}

Edge Case 3: Conditional rendering inside vs outside component

// Pattern A: Conditional inside componentfunction Profile({ user }) {
  if (!user) return <div>Loading...</div>;  return <div>{user.name}</div>;}
// Pattern B: Conditional outside{user ? <Profile user={user} /> : <div>Loading...</div>}
// Pattern A preserves Profile's component instance when user loads// Pattern B unmounts Profile entirely when user is null// Different behavior for hooks/state inside Profile

Fifth, optimization strategies:

Strategy 1: Memoization with React.memo

// Prevents re-render if props haven't changedconst ExpensiveComponent = React.memo(({ data }) => {
  // Complex rendering logic  return <div>{data.value}</div>;});// Only re-renders if data reference changes

Strategy 2: Stable keys for lists

// Use database IDs or stable unique identifiers<ul>  {users.map(user =>
    <UserCard key={user.id} user={user} />  )}</ul>

Strategy 3: Avoid inline object creation in render

// BAD: Creates new style object every render<Component style={{ margin: 10 }} />// GOOD: Define outside or memoizeconst style = { margin: 10 };<Component style={style} />

Fiber Architecture Context (for L6+ roles):

React Fiber (introduced React 16) made reconciliation interruptible. Instead of recursively walking the tree in one go (blocking the main thread), Fiber breaks work into small units. React can:
- Pause work and come back to it later
- Assign priority to different types of updates
- Reuse previously completed work
- Abort work if it’s no longer needed

This enables features like Concurrent Mode and Suspense, where low-priority updates can be interrupted by high-priority ones (like user input).

Key Takeaway:

React’s Virtual DOM and reconciliation optimize performance by minimizing real DOM operations. Understanding this helps you write efficient React code—using stable keys, avoiding unnecessary component type changes, and leveraging memoization strategically.


4. Interview Score

9/10

Why this score:
- Algorithmic Understanding: Explained O(n³) → O(n) optimization through heuristics showing deep knowledge of trade-offs
- Practical Examples: Provided code examples for edge cases (array index keys, conditional rendering patterns) demonstrating real-world application
- Performance Awareness: Connected Virtual DOM concept to browser reflow/repaint performance implications beyond just “React is fast”
- Fiber Architecture: Mentioned advanced concepts (interruptible reconciliation, work scheduling) appropriate for L5+ interviews showing staff-level knowledge


Question 2: Large-Scale React Application Architecture

Difficulty: Very High

Role: Senior Frontend Engineer / Frontend Architect

Level: Senior+ (L5-L6, 5-8 Years of Experience)

Company Examples: Meta, Google, Airbnb, Stripe, Uber

Question: “Design the frontend architecture for a large-scale social media application (or e-commerce platform). How would you structure components? What state management solution would you use? How would you handle data fetching? What about performance optimization and testing strategies?”


1. What is This Question Testing?

This question tests critical Frontend Architect competencies:

  • System Design Thinking: Can you architect scalable frontend systems, not just individual components?
  • Trade-off Analysis: Do you understand pros/cons of different architectural choices (Redux vs Context vs Zustand)?
  • Scalability Awareness: Can you design for teams of 20+ engineers working on the same codebase?
  • Performance Focus: Do you consider bundle size, code splitting, and rendering optimization from the start?
  • Holistic View: Can you connect frontend architecture to backend APIs, deployment, and testing?

The interviewer wants to see if you’re a Senior+ Frontend Engineer who can own architectural decisions for entire applications, not just implement features.


2. Framework to Answer This Question

Use the “Layered Architecture with Trade-offs Framework”:

Structure:
1. Component Organization - Folder structure, component hierarchy, design patterns
2. State Management - Local vs global, client vs server state
3. Data Fetching - Strategies for API integration, caching, synchronization
4. Performance - Code splitting, lazy loading, bundle optimization
5. Testing - Unit, integration, E2E strategy
6. Infrastructure - Build tools, deployment, monitoring

Key Principles:
- No single “right” answer—explain trade-offs
- Match architecture to team size and requirements
- Consider long-term maintainability
- Quantify decisions where possible (bundle size targets, performance budgets)


3. The Answer

Answer:

I’ll design a frontend architecture for a large-scale social media platform with these requirements: 10M+ daily active users, 20+ frontend engineers, real-time updates, mobile-responsive, and fast performance.

First, component organization and folder structure:

src/
├── features/               # Feature-based organization
│   ├── feed/
│   │   ├── components/    # Feature-specific components
│   │   ├── hooks/         # Custom hooks for this feature
│   │   ├── api/           # API calls for this feature
│   │   ├── types/         # TypeScript types
│   │   └── index.ts       # Public exports
│   ├── profile/
│   ├── messaging/
│   └── notifications/
├── shared/                # Shared across features
│   ├── components/        # Button, Card, Modal, etc.
│   ├── hooks/             # useDebounce, useLocalStorage
│   ├── utils/             # Helper functions
│   └── types/             # Global types
├── layouts/               # Page layouts (authenticated, public)
├── pages/                 # Next.js pages or React Router routes
└── services/              # Global services (auth, analytics, API client)

Why feature-based over component-based:
- Scalability: Teams can own entire features without conflicts
- Colocation: Related code lives together (components + hooks + API)
- Clear boundaries: Easy to understand what code belongs where
- Code splitting: Can lazy load entire features

Component hierarchy pattern:

// Container components (data fetching, logic)
function FeedContainer() {
  const { data, isLoading } = useFeedData();
  const { createPost } = useCreatePost();

  if (isLoading) return <FeedSkeleton />;

  return <FeedView posts={data} onCreatePost={createPost} />;
}

// Presentational components (UI only, no logic)
function FeedView({ posts, onCreatePost }) {
  return (
    <div>
      <PostComposer onSubmit={onCreatePost} />
      {posts.map(post => <PostCard key={post.id} post={post} />)}
    </div>
  );
}

Benefits: Testability (UI separate from logic), reusability, clear responsibilities.

Second, state management strategy:

I’d use a hybrid approach—different tools for different state types:

Local component state (useState, useReducer):

// UI state: toggles, form inputs, accordions
const [isOpen, setIsOpen] = useState(false);
const [formData, setFormData] = useState({ name: '', email: '' });

When: State used by single component or closely related components.

Client state (Zustand or Context API):

// App-level state: theme, user preferences, UI modes
import create from 'zustand';

const useUIStore = create((set) => ({
  theme: 'light',
  sidebarOpen: true,
  setTheme: (theme) => set({ theme }),
  toggleSidebar: () => set((state) => ({ sidebarOpen: !state.sidebarOpen }))
}));

// Usage
const { theme, setTheme } = useUIStore();

Why Zustand over Redux:
- 10x less boilerplate (no actions, reducers, dispatchers)
- 3KB bundle size vs Redux 12KB + Redux Toolkit 15KB
- Built-in TypeScript support
- Easy to split into multiple stores

When to use Context API instead: Simple app-wide state (theme, auth user) with < 5 pieces of state. Context causes re-renders of all consumers, so avoid for frequently changing state.

Server state (React Query or SWR):

import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

// Fetching posts
function useFeedPosts() {
  return useQuery({
    queryKey: ['feed', 'posts'],
    queryFn: () => api.get('/feed/posts'),
    staleTime: 30000,  // Cache for 30 seconds
    refetchInterval: 60000  // Refetch every 60s for real-time feel
  });
}

// Creating post
function useCreatePost() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (postData) => api.post('/posts', postData),
    onSuccess: () => {
      // Invalidate and refetch feed
      queryClient.invalidateQueries(['feed', 'posts']);
    }
  });
}

Why React Query for server state:
- Automatic caching and synchronization
- Built-in loading/error states
- Request deduplication (if 5 components request same data, only 1 API call)
- Optimistic updates
- Background refetching for real-time updates
- Pagination and infinite scroll support

State management decision matrix:

State TypeToolExample
Local UI stateuseStateModal open/closed
Form stateuseState + react-hook-formLogin form
Global UI stateZustandTheme, sidebar state
AuthenticationContext APICurrent user
Server data (read-heavy)React QueryPosts, comments, users
Real-time updatesReact Query + WebSocketNotifications, messages

Third, data fetching and API integration:

API client setup:

// services/api.ts
import axios from 'axios';

const apiClient = axios.create({
  baseURL: process.env.NEXT_PUBLIC_API_URL,
  timeout: 10000
});

// Request interceptor for auth tokens
apiClient.interceptors.request.use((config) => {
  const token = localStorage.getItem('auth_token');
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

// Response interceptor for error handling
apiClient.interceptors.response.use(
  (response) => response.data,
  (error) => {
    if (error.response?.status === 401) {
      // Redirect to login
      window.location.href = '/login';
    }
    return Promise.reject(error);
  }
);

export default apiClient;

Custom hook pattern for data fetching:

// features/feed/hooks/useFeedPosts.ts
export function useFeedPosts(filter = 'recent') {
  return useQuery({
    queryKey: ['feed', 'posts', filter],
    queryFn: () => apiClient.get(`/feed/posts?filter=${filter}`),
    select: (data) => data.posts,  // Transform response
    staleTime: 30000,
    enabled: !!filter  // Conditional fetching
  });
}

// Usage in component
function Feed() {
  const { data: posts, isLoading, error } = useFeedPosts('recent');

  if (isLoading) return <Skeleton />;
  if (error) return <ErrorBoundary error={error} />;

  return <PostList posts={posts} />;
}

Infinite scrolling implementation:

function useInfiniteFeed() {
  return useInfiniteQuery({
    queryKey: ['feed', 'infinite'],
    queryFn: ({ pageParam = 0 }) =>
      apiClient.get(`/feed/posts?offset=${pageParam}`),
    getNextPageParam: (lastPage) => lastPage.nextOffset
  });
}

// Usage
function InfiniteFeed() {
  const { data, fetchNextPage, hasNextPage } = useInfiniteFeed();

  return (
    <InfiniteScroll onLoadMore={fetchNextPage} hasMore={hasNextPage}>
      {data.pages.flatMap(page => page.posts).map(post =>
        <PostCard key={post.id} post={post} />
      )}
    </InfiniteScroll>
  );
}

Fourth, performance optimization:

Code splitting by route:

// pages/_app.tsx (Next.js) or App.tsx (React Router)
import { lazy, Suspense } from 'react';

const Feed = lazy(() => import('@/features/feed'));
const Profile = lazy(() => import('@/features/profile'));
const Messaging = lazy(() => import('@/features/messaging'));

function App() {
  return (
    <Suspense fallback={<PageLoader />}>
      <Routes>
        <Route path="/" element={<Feed />} />
        <Route path="/profile/:id" element={<Profile />} />
        <Route path="/messages" element={<Messaging />} />
      </Routes>
    </Suspense>
  );
}

Bundle size optimization:

// Target: < 200KB initial bundle{  "scripts": {    "analyze": "ANALYZE=true next build"  }}

Strategy:
- Tree-shaking unused code (import specific functions)
- Replace heavy libraries (lodash → lodash-es, moment → date-fns)
- Code split third-party libraries > 50KB
- Lazy load non-critical features

Image optimization:

import Image from 'next/image';

<Image
  src={post.imageUrl}
  alt={post.title}
  width={800}
  height={600}
  placeholder="blur"
  loading="lazy"  // Native lazy loading
  sizes="(max-width: 768px) 100vw, 50vw"
/>

Memoization (strategic, not everywhere):

// Only memoize if profiling shows benefit
const MemoizedExpensiveList = React.memo(ExpensiveList, (prevProps, nextProps) => {
  // Custom comparison logic
  return prevProps.items.length === nextProps.items.length;
});

Fifth, testing strategy:

Unit tests (70% of tests) - Jest + React Testing Library:

// features/feed/components/PostCard.test.tsx
import { render, screen } from '@testing-library/react';
import PostCard from './PostCard';

test('renders post content', () => {
  const post = { id: '1', content: 'Hello world', author: 'John' };
  render(<PostCard post={post} />);

  expect(screen.getByText('Hello world')).toBeInTheDocument();
  expect(screen.getByText('John')).toBeInTheDocument();
});

Integration tests (20% of tests) - Test user workflows:

test('user can create and see new post', async () => {
  const user = userEvent.setup();
  render(<Feed />);

  // Type post
  const input = screen.getByPlaceholderText('What\'s on your mind?');
  await user.type(input, 'New post content');

  // Submit
  await user.click(screen.getByRole('button', { name: /post/i }));

  // Verify post appears
  expect(await screen.findByText('New post content')).toBeInTheDocument();
});

E2E tests (10% of tests) - Playwright/Cypress:

// e2e/feed.spec.ts
test('complete post creation flow', async ({ page }) => {
  await page.goto('/');
  await page.fill('[placeholder="What\'s on your mind?"]', 'E2E test post');
  await page.click('text=Post');

  await expect(page.locator('text=E2E test post')).toBeVisible();
});

Testing philosophy:
- Don’t chase 100% coverage—focus on critical paths
- Test behavior, not implementation
- Mock external dependencies (APIs, third-party libraries)
- Visual regression testing for design system components (Chromatic)

Sixth, infrastructure and deployment:

Build tool: Vite or Next.js
- Vite for React SPA: Fast dev server, optimized production builds
- Next.js for SSR/SSG: Built-in routing, API routes, image optimization

CI/CD pipeline:

# .github/workflows/ci.ymlname: CIon: [push, pull_request]jobs:  test:    runs-on: ubuntu-latest    steps:      - uses: actions/checkout@v3      - name: Install dependencies        run: npm ci      - name: Lint        run: npm run lint      - name: Type check        run: npm run type-check      - name: Test        run: npm run test      - name: Build        run: npm run build

Performance budgets:

// lighthouserc.json{  "ci": {    "assert": {      "assertions": {        "first-contentful-paint": ["error", { "maxNumericValue": 2000 }],        "interactive": ["error", { "maxNumericValue": 3500 }],        "total-blocking-time": ["error", { "maxNumericValue": 300 }]      }    }  }}

Monitoring:
- Sentry for error tracking
- DataDog or New Relic for performance monitoring
- Google Analytics + custom events for user behavior

Key Architectural Principles:

  1. Separation of Concerns: Features, shared code, layouts clearly separated
  1. Progressive Enhancement: Works without JS, enhanced with JS
  1. Performance Budget: 200KB initial bundle, < 2s FCP, < 3.5s TTI
  1. Type Safety: TypeScript everywhere
  1. Testability: Design for testing from the start
  1. Scalability: Can add features without refactoring architecture

This architecture supports 20+ engineers working concurrently, with clear ownership boundaries and minimal merge conflicts.


4. Interview Score

9.5/10

Why this score:
- Comprehensive Architecture: Covered all layers from component organization to deployment showing holistic system design thinking
- Trade-off Analysis: Explained Zustand vs Redux, React Query vs manual fetching with specific metrics (bundle size, boilerplate reduction)
- Scalability Focus: Feature-based organization for teams, code splitting strategy, performance budgets showing enterprise-level thinking
- Concrete Implementation: Provided actual code examples for every pattern (not just theory) demonstrating practical experience


Question 3: JavaScript Closure Gotchas and Reference Behavior

Difficulty: Medium

Role: Mid-Level Frontend Engineer

Level: Mid-Level (L3-L4, 3-5 Years of Experience)

Company Examples: Meta, Google, Stripe, Apple, Amazon

Question: “Explain JavaScript closures. Then solve this: write a function that returns an array of 10 functions, each returning a number from 0 to 9. What’s the common mistake? How do you fix it? Explain the reference behavior.”


1. What is This Question Testing?

  • JavaScript Fundamentals: Deep understanding of function scope, closure mechanics, and execution context
  • Debugging Skills: Can you identify the classic var/let closure bug?
  • Reference vs Value: Do you understand how JavaScript captures variables in closures?
  • Problem Solving: Can you provide multiple solutions and explain trade-offs?

2. Framework to Answer This Question

Structure:
1. Closure Definition - Explain with simple example
2. The Problem - Show the buggy code
3. Root Cause - Explain why it fails
4. Solutions - Multiple fixes with explanations
5. Practical Applications - When closures are useful


3. The Answer

Answer:

First, what is a closure:

A closure is a function that has access to variables in its outer (enclosing) lexical scope, even after the outer function has returned. The inner function “closes over” the outer function’s variables.

function makeCounter() {
  let count = 0;  return function() {
    count++;    return count;  };}
const counter = makeCounter();console.log(counter());  // 1console.log(counter());  // 2console.log(counter());  // 3// The inner function remembers 'count' even after makeCounter() finished

Second, the array of functions problem - THE WRONG WAY:

// BUGGY VERSIONfunction createFunctions() {
  const arr = [];  for (var i = 0; i < 10; i++) {
    arr.push(function() {
      return i;    });  }
  return arr;}
const functions = createFunctions();console.log(functions[0]());  // Expected: 0, Actual: 10console.log(functions[5]());  // Expected: 5, Actual: 10console.log(functions[9]());  // Expected: 9, Actual: 10// All functions return 10!

Third, why this fails - root cause:

The issue is that var has function scope, not block scope. All 10 functions reference the same variable i. By the time any function is called, the loop has finished and i equals 10.

// What actually happens:var i;  // Hoisted to function scopeconst arr = [];i = 0;arr.push(function() { return i; });  // References the shared 'i'i = 1;arr.push(function() { return i; });  // References the same 'i'// ... loop continues ...i = 10;  // Loop ends// NOW when you call any function, they all see i = 10

Fourth, solutions:

Solution 1: Use let instead of var (Simplest)

function createFunctions() {
  const arr = [];  for (let i = 0; i < 10; i++) {  // Changed var to let    arr.push(function() {
      return i;    });  }
  return arr;}
const functions = createFunctions();console.log(functions[0]());  // 0console.log(functions[5]());  // 5console.log(functions[9]());  // 9

Why this works: let has block scope. Each iteration creates a NEW binding for i, so each function captures a different i value.

Solution 2: IIFE (Immediately Invoked Function Expression)

function createFunctions() {
  const arr = [];  for (var i = 0; i < 10; i++) {
    arr.push((function(num) {  // IIFE with parameter      return function() {
        return num;      };    })(i));  // Pass current i value  }
  return arr;}

Why this works: The IIFE creates a new scope for each iteration, capturing the current value of i as num.

Solution 3: Function factory

function makeFunction(num) {
  return function() {
    return num;  };}
function createFunctions() {
  const arr = [];  for (var i = 0; i < 10; i++) {
    arr.push(makeFunction(i));  }
  return arr;}

Why this works: Each call to makeFunction(i) creates a new scope with its own num parameter.

Solution 4: Modern approach with Array.from

function createFunctions() {
  return Array.from({ length: 10 }, (_, i) => () => i);}

Why this works: Each arrow function is created in a separate scope with its own i value.

Fifth, reference vs value behavior:

Primitives are captured by value:

let x = 5;function captureX() {
  return function() { return x; };}
const fn = captureX();x = 10;console.log(fn());  // 10 - references the variable, not the value at creation time

Objects are captured by reference:

let obj = { count: 0 };function captureObj() {
  return function() { return obj.count; };}
const fn = captureObj();obj.count = 5;console.log(fn());  // 5 - references the objectobj = { count: 10 };console.log(fn());  // 5 - still references original object

Practical applications of closures:

  1. Data privacy (private variables):
function createBankAccount(initialBalance) {
  let balance = initialBalance;  // Private variable  return {
    deposit(amount) {
      balance += amount;      return balance;    },    withdraw(amount) {
      if (balance >= amount) {
        balance -= amount;        return balance;      }
      return 'Insufficient funds';    },    getBalance() {
      return balance;    }
  };}
const account = createBankAccount(100);console.log(account.getBalance());  // 100account.deposit(50);console.log(account.getBalance());  // 150console.log(account.balance);  // undefined - can't access directly!
  1. Event handlers with dynamic data:
buttons.forEach((button, index) => {
  button.addEventListener('click', () => {
    console.log(`Button ${index} clicked`);  // Closure captures index  });});

4. Interview Score

8.5/10

Why this score:
- Clear Explanation: Explained closure concept with simple examples before diving into the problem
- Root Cause Analysis: Identified var function scope vs let block scope as the core issue
- Multiple Solutions: Provided 4 different fixes (let, IIFE, factory, Array.from) showing depth of knowledge
- Practical Applications: Connected theory to real-world use cases (data privacy, event handlers)


Question 4: Event Loop, Microtasks, and Macrotasks Execution Order

Difficulty: High

Role: Mid-to-Senior Frontend Engineer

Level: Mid-to-Senior (L3-L5, 3-6 Years of Experience)

Company Examples: Meta, Google, Shopify, Netflix, Uber

Question: “Trace the execution order of this code and explain why. What’s the output? [Shows code mixing console.log, setTimeout, Promise.resolve().then()]”


1. What is This Question Testing?

  • Event Loop Mastery: Deep understanding of JavaScript’s asynchronous execution model
  • Task Queue Knowledge: Can you distinguish microtasks from macrotasks?
  • Attention to Detail: Can you trace complex execution patterns accurately?
  • Async Debugging: Can you reason about real-world async bugs?

2. The Answer

Answer:

JavaScript’s event loop manages asynchronous code execution through different task queues with different priorities.

Core concepts:

  1. Call Stack: Executes synchronous code (LIFO - Last In, First Out)
  1. Microtask Queue: High-priority async tasks (Promises, queueMicrotask)
  1. Macrotask Queue: Lower-priority async tasks (setTimeout, setInterval, I/O)

Execution order: Synchronous code → Microtasks → Macrotasks → (repeat)

Example problem:

console.log('Start');setTimeout(() => {
  console.log('setTimeout 1');  Promise.resolve().then(() => {
    console.log('Promise inside setTimeout 1');  });}, 0);Promise.resolve()
  .then(() => {
    console.log('Promise 1');    setTimeout(() => {
      console.log('setTimeout inside Promise 1');    }, 0);  });console.log('End');

Correct output:

Start
End
Promise 1
setTimeout 1
Promise inside setTimeout 1
setTimeout inside Promise 1

Step-by-step execution:

  1. Synchronous execution (Call Stack):
    • console.log('Start') → Output: “Start”
    • setTimeout registered in macrotask queue
    • Promise.resolve().then registered in microtask queue
    • console.log('End') → Output: “End”
  1. Microtask queue (runs before macrotasks):
    • Promise 1 executes → Output: “Promise 1”
    • setTimeout inside Promise registered in macrotask queue
  1. Macrotask queue:
    • setTimeout 1 executes → Output: “setTimeout 1”
    • Promise inside setTimeout 1 registered in microtask queue
  1. Microtask queue again (empties before next macrotask):
    • Promise inside setTimeout 1 → Output: “Promise inside setTimeout 1”
  1. Next macrotask:
    • setTimeout inside Promise 1 → Output: “setTimeout inside Promise 1”

Key rule: After each macrotask, the event loop empties the entire microtask queue before moving to the next macrotask.


4. Interview Score

9/10

Why this score:
- Precise Execution Model: Correctly explained call stack → microtasks → macrotasks order
- Visual Clarity: Showed step-by-step execution tracing
- Rule Articulation: Stated the key principle (microtasks empty before next macrotask)


Question 5: Web Vitals Optimization (INP, CLS, LCP)

Difficulty: High

Role: Mid-to-Senior Frontend Engineer

Level: Mid-to-Senior (L4-L5, 4-6 Years of Experience)

Company Examples: Google, Meta, Shopify, Vercel, Airbnb

Question: “A client says their website is slow. Walk me through how you’d diagnose and optimize Core Web Vitals. Explain what INP, CLS, and LCP are, what causes performance issues, and specific optimization strategies.”


1. What is This Question Testing?

  • Performance Expertise: Do you understand modern web performance metrics?
  • Diagnostic Skills: Can you systematically identify performance bottlenecks?
  • Optimization Knowledge: Do you know practical techniques to improve metrics?
  • Business Awareness: Do you understand SEO and user experience implications?

2. The Answer

Answer:

Core Web Vitals are Google’s standardized metrics for measuring user experience. Let me explain each and how to optimize them.

1. Largest Contentful Paint (LCP) - Target: < 2.5 seconds

What it measures: Time until the largest content element (image, video, or text block) is painted.

Common causes:
- Slow server response times
- Render-blocking CSS/JavaScript
- Large, unoptimized images
- Client-side rendering delays

Optimization strategies:

<!-- Preload critical resources --><link rel="preload" href="/critical.css" as="style"><link rel="preload" href="/hero-image.jpg" as="image"><!-- Optimize images --><img  src="hero.jpg"  srcset="hero-400.jpg 400w, hero-800.jpg 800w"  sizes="(max-width: 600px) 400px, 800px"  loading="eager"  fetchpriority="high"  alt="Hero"/>
// Code splitting to reduce initial bundleconst HeavyComponent = lazy(() => import('./Heavy'));// Use CDN for static assets// Implement HTTP/2 or HTTP/3// Enable compression (Brotli, Gzip)

2. Cumulative Layout Shift (CLS) - Target: < 0.1

What it measures: Visual stability—unexpected layout shifts during page load.

Common causes:
- Images without dimensions
- Ads/embeds/iframes without reserved space
- Web fonts causing FOIT/FOUT
- Dynamically injected content

Optimization strategies:

<!-- Always specify image dimensions --><img src="photo.jpg" width="800" height="600" alt="Photo" /><!-- Reserve space for ads --><div style="min-height: 250px;">  <div id="ad-slot"></div></div>
/* Font loading strategy */@font-face {
  font-family: 'CustomFont';  src: url('/fonts/custom.woff2') format('woff2');  font-display: optional;  /* Prevents layout shift */}
/* Aspect ratio boxes for dynamic content */.video-container {
  aspect-ratio: 16 / 9;}

3. Interaction to Next Paint (INP) - Target: < 200ms

What it measures: Responsiveness—time between user interaction and visual feedback.

Common causes:
- Long JavaScript tasks blocking main thread
- Heavy event handlers
- Unnecessary re-renders in React

Optimization strategies:

// Break long tasks into smaller chunksasync function processLargeDataset(data) {
  for (let i = 0; i < data.length; i += 100) {
    // Process 100 items    await scheduler.postTask(() => {
      processChunk(data.slice(i, i + 100));    }, { priority: 'user-blocking' });  }
}
// Debounce expensive operationsconst debouncedSearch = useMemo(
  () => debounce((query) => fetchResults(query), 300),  []
);// Use Web Workers for CPU-intensive tasksconst worker = new Worker('heavy-computation.js');worker.postMessage({ data });

Measurement tools:

// Web Vitals APIimport {onCLS, onINP, onLCP} from 'web-vitals';onCLS(console.log);onINP(console.log);onLCP(console.log);// Send to analyticsfunction sendToAnalytics(metric) {
  const body = JSON.stringify(metric);  navigator.sendBeacon('/analytics', body);}
onCLS(sendToAnalytics);

Diagnostic workflow:

  1. Run Lighthouse audit in Chrome DevTools
  1. Check PageSpeed Insights for real-world data
  1. Use Performance tab to identify bottlenecks
  1. Test on real devices (not just desktop)
  1. Implement fixes and re-measure

4. Interview Score

8.5/10

Why this score:
- Comprehensive Coverage: Explained all three Core Web Vitals with target values
- Practical Solutions: Provided code examples for each optimization technique
- Measurement Approach: Included Web Vitals API integration and diagnostic workflow
- Real-World Focus: Mentioned business impact (SEO, user experience)


Question 6: TypeScript Generics and Constraints

Difficulty: High

Role: Mid-to-Senior Frontend Engineer

Level: Mid-to-Senior (L4-L5, 4-6 Years of Experience)

Company Examples: Google, Meta, Airbnb, Stripe, Microsoft

Question: “Write a generic function that safely gets a property from an object. The function should only accept keys that actually exist on the object. Use TypeScript generics and constraints.”


1. What is This Question Testing?

  • TypeScript Mastery: Beyond basic types to advanced generics
  • Type Safety: Can you create APIs that prevent errors at compile time?
  • Constraint Usage: Do you understand extends and keyof?
  • Practical Application: Can you solve real-world type problems?

2. The Answer

Answer:

The solution:

function getProperty<T extends object, K extends keyof T>(
  obj: T,  key: K
): T[K] {
  return obj[key];}
// Usage examplesconst person = { name: 'Alice', age: 30, email: 'alice@example.com' };const name = getProperty(person, 'name');    // Type: string ✅const age = getProperty(person, 'age');      // Type: number ✅const invalid = getProperty(person, 'salary'); // TypeScript Error! ❌

Explanation of syntax:

  1. <T extends object>: Generic type T must be an object
  1. K extends keyof T: K must be a key that exists in T
  1. T[K]: Return type is the type of property K in object T

Advanced example - nested property access:

type NestedKeyOf<T> = {
  [K in keyof T]: T[K] extends object    ? K | `${K & string}.${NestedKeyOf<T[K]> & string}`    : K;}[keyof T];function getNestedProperty<T extends object>(
  obj: T,  path: NestedKeyOf<T>): any {
  return path.split('.').reduce((current, key) => current?.[key], obj);}
// Usageconst user = {
  profile: {
    address: {
      city: 'New York'    }
  }
};getNestedProperty(user, 'profile.address.city'); // ✅ ValidgetNestedProperty(user, 'profile.invalid');       // ❌ TypeScript Error

Practical applications:

// 1. Type-safe event emitterclass TypedEventEmitter<Events extends Record<string, any>> {
  on<K extends keyof Events>(
    event: K,    handler: (data: Events[K]) => void  ): void {
    // implementation  }
}
type AppEvents = {
  'user:login': { userId: string };  'user:logout': void;};const emitter = new TypedEventEmitter<AppEvents>();emitter.on('user:login', (data) => {
  console.log(data.userId); // TypeScript knows structure});// 2. Type-safe form validationtype FormErrors<T> = {
  [K in keyof T]?: string;};function validateForm<T extends object>(
  data: T,  rules: Partial<Record<keyof T, (value: any) => string | undefined>>): FormErrors<T> {
  const errors: FormErrors<T> = {};  for (const key in rules) {
    const validate = rules[key];    if (validate) {
      const error = validate(data[key]);      if (error) errors[key] = error;    }
  }
  return errors;}

4. Interview Score

9/10

Why this score:
- Complete Solution: Implemented type-safe property accessor with proper constraints
- Advanced Concepts: Showed nested property access and conditional types
- Real-World Examples: Provided practical applications (event emitter, form validation)
- Type Safety: Demonstrated preventing runtime errors through compile-time checks


Question 7: Custom Hooks Anti-patterns and useMemo/useCallback Misuse

Difficulty: Medium

Role: Mid-Level Frontend Engineer

Level: Mid-Level (L3-L4, 3-5 Years of Experience)

Company Examples: Meta, Airbnb, Spotify, Netflix

Question: “Look at this code with nested useMemo and useCallback. What problems do you see? When should you actually use memoization hooks? What are the common anti-patterns developers fall into?”


1. What is This Question Testing?

  • Performance Optimization Wisdom: Do you know when NOT to optimize?
  • Hook Understanding: Deep knowledge of useMemo/useCallback purpose
  • Code Quality: Can you identify over-engineering?
  • Profiling Mindset: Do you measure before optimizing?

2. The Answer

Answer:

Common anti-pattern - unnecessary memoization:

// BAD: Over-memoization
function UserProfile({ user }) {
  // Anti-pattern #1: Memoizing simple values
  const fullName = useMemo(() => {
    return `${user.firstName} ${user.lastName}`;
  }, [user.firstName, user.lastName]);

  // Anti-pattern #2: Memoizing callbacks for non-memoized components
  const handleClick = useCallback(() => {
    console.log(user.id);
  }, [user.id]);

  // Anti-pattern #3: Cascading memoization
  const userMetadata = useMemo(() => {
    return { name: fullName, id: user.id };
  }, [fullName, user.id]);

  return (
    <div onClick={handleClick}>
      {userMetadata.name}
    </div>
  );
}

Why this is bad:

  1. useMemo overhead: Setting up memoization has a cost. Simple string concatenation is faster than memoization setup.
  1. Premature optimization: No evidence this component has performance issues.
  1. Cascading complexity: One memo forces others, creating maintenance burden.
  1. False benefit: handleClick memoization only helps if the child component is memoized with React.memo.

The correct approach:

// GOOD: Simple and clear
function UserProfile({ user }) {
  const fullName = `${user.firstName} ${user.lastName}`;

  const handleClick = () => {
    console.log(user.id);
  };

  return (
    <div onClick={handleClick}>
      {fullName}
    </div>
  );
}

When to actually use memoization:

1. Expensive computations (use useMemo):

function DataVisualization({ rawData }) {
  // Expensive calculation - thousands of data points
  const processedData = useMemo(() => {
    return rawData
      .map(transform)
      .filter(validate)
      .reduce(aggregate, {});
  }, [rawData]);

  return <Chart data={processedData} />;
}

2. Preventing child re-renders (use React.memo + useCallback):

// Child component is expensive to render
const ExpensiveList = React.memo(function ExpensiveList({ items, onItemClick }) {
  // Heavy rendering logic
  return items.map(item => <ExpensiveItem key={item.id} onClick={onItemClick} />);
});

function Parent() {
  const [filter, setFilter] = useState('');

  // useCallback prevents ExpensiveList re-render when Parent re-renders
  const handleItemClick = useCallback((item) => {
    console.log(item);
  }, []); // Empty deps - stable reference

  return <ExpensiveList items={items} onItemClick={handleItemClick} />;
}

3. Context value optimization:

function AppProvider({ children }) {
  const [user, setUser] = useState(null);

  // Prevents all context consumers from re-rendering
  const value = useMemo(() => ({
    user,
    setUser
  }), [user]);

  return (
    <AppContext.Provider value={value}>
      {children}
    </AppContext.Provider>
  );
}

Decision tree for memoization:

Should I use useMemo/useCallback?
│
├─ Is there a proven performance problem?
│  └─ NO → Don't memoize
│  └─ YES → Continue
│
├─ For useMemo: Is the calculation expensive?
│  └─ < 1ms → Don't memoize
│  └─ > 1ms → Profile and memoize if beneficial
│
├─ For useCallback: Is the child component memoized?
│  └─ NO → Don't memoize (no benefit)
│  └─ YES → Memoize if deps are stable

How to measure:

// Use React DevTools Profiler
// OR manually profile
function ProfiledComponent() {
  const start = performance.now();
  const result = expensiveCalculation();
  const end = performance.now();

  if (end - start > 1) {
    console.log(`Calculation took ${end - start}ms`);
  }

  return <div>{result}</div>;
}

4. Interview Score

9/10

Why this score:
- Anti-pattern Identification: Clearly explained why over-memoization is harmful
- Practical Guidelines: Provided decision tree for when to actually memoize
- Performance Focus: Emphasized profiling before optimizing
- Real Examples: Showed both bad and good patterns with explanations


Question 8: Service Workers, PWAs, and Offline Functionality

Difficulty: High

Role: Mid-to-Senior Frontend Engineer

Level: Mid-to-Senior (L4-L5, 4-6 Years of Experience)

Company Examples: Google, Twitter/X, LinkedIn, Spotify, Uber

Question: “Explain how Progressive Web Apps work. What’s the difference between Service Workers and Web Workers? How would you implement offline support? What are caching strategies?”


1. What is This Question Testing?

  • Modern Web APIs: Understanding of advanced browser capabilities
  • Offline-First Architecture: Can you design for poor connectivity?
  • Performance Awareness: Do you understand caching trade-offs?
  • User Experience: Can you handle network failures gracefully?

2. The Answer

Answer:

Service Workers vs Web Workers:

AspectService WorkersWeb Workers
PurposeNetwork proxy, offline supportCPU-intensive tasks
LifecyclePersists beyond page lifecycleDies when page closes
ScopeControls network requests for all pagesIsolated to creating page
Use CasesCaching, push notifications, background syncData processing, image manipulation

Progressive Web App (PWA) core features:

  1. Service Worker for offline access
  1. Web Manifest for install ability
  1. HTTPS (security requirement)
  1. Responsive design
  1. App-like experience

Service Worker implementation:

// register-sw.jsif ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker      .register('/service-worker.js')
      .then(registration => {
        console.log('SW registered:', registration);      })
      .catch(error => {
        console.log('SW registration failed:', error);      });  });}
// service-worker.jsconst CACHE_NAME = 'app-v1';const URLS_TO_CACHE = [
  '/',  '/styles/main.css',  '/script/main.js',  '/images/logo.png'];// Install event - cache resourcesself.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(cache => cache.addAll(URLS_TO_CACHE))
  );});// Fetch event - serve from cache or networkself.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request)
      .then(response => {
        // Cache hit - return cached response        if (response) {
          return response;        }
        // Cache miss - fetch from network        return fetch(event.request);      })
  );});// Activate event - clean old cachesself.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then(cacheNames => {
      return Promise.all(
        cacheNames
          .filter(name => name !== CACHE_NAME)
          .map(name => caches.delete(name))
      );    })
  );});

Caching strategies:

1. Cache First (fastest, may serve stale data):

self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request)
      .then(response => response || fetch(event.request))
  );});// Use for: Static assets (CSS, JS, images)

2. Network First (fresh data, offline fallback):

self.addEventListener('fetch', (event) => {
  event.respondWith(
    fetch(event.request)
      .then(response => {
        // Update cache with fresh data        const clonedResponse = response.clone();        caches.open(CACHE_NAME).then(cache => {
          cache.put(event.request, clonedResponse);        });        return response;      })
      .catch(() => {
        // Network failed - return cached version        return caches.match(event.request);      })
  );});// Use for: API requests that should be fresh

3. Stale-While-Revalidate (serve cached, update in background):

self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request)
      .then(cachedResponse => {
        const fetchPromise = fetch(event.request)
          .then(networkResponse => {
            // Update cache in background            caches.open(CACHE_NAME).then(cache => {
              cache.put(event.request, networkResponse.clone());            });            return networkResponse;          });        // Return cached immediately, update in background        return cachedResponse || fetchPromise;      })
  );});// Use for: News feeds, social media posts

Web Manifest:

{  "name": "My PWA App",  "short_name": "PWA",  "start_url": "/",  "display": "standalone",  "background_color": "#ffffff",  "theme_color": "#000000",  "icons": [    {      "src": "/icon-192.png",      "sizes": "192x192",      "type": "image/png"    },    {      "src": "/icon-512.png",      "sizes": "512x512",      "type": "image/png"    }  ]}

Offline UI handling:

// Detect online/offline statuswindow.addEventListener('online', () => {
  document.body.classList.remove('offline');  showNotification('You are back online!');});window.addEventListener('offline', () => {
  document.body.classList.add('offline');  showNotification('You are offline. Some features may be unavailable.');});// Check current statusif (!navigator.onLine) {
  showOfflineMessage();}

4. Interview Score

8.5/10

Why this score:
- Clear Distinctions: Explained Service Workers vs Web Workers with practical examples
- Caching Strategies: Covered three main patterns with use cases
- Complete Implementation: Showed lifecycle events (install, fetch, activate)
- User Experience: Included offline detection and UI handling


Question 9: Accessibility (WCAG) and Semantic HTML Implementation

Difficulty: Medium

Role: Mid-Level Frontend Engineer

Level: Mid-Level (L3-L4, 3-5 Years of Experience)

Company Examples: Google, Microsoft, Shopify, Adobe, GitHub

Question: “Explain WCAG compliance levels. How would you make a custom select component accessible? What are common accessibility issues, and how do you test for them?”


1. What is This Question Testing?

  • Accessibility Knowledge: Understanding of WCAG principles and standards
  • Semantic HTML: Can you write accessible markup?
  • ARIA Usage: Do you know when and how to use ARIA attributes?
  • Testing Approach: Can you validate accessibility systematically?

2. The Answer

Answer:

WCAG Principles (POUR):

  1. Perceivable: Content accessible to senses (sight, hearing, touch)
  1. Operable: Interface usable via keyboard, voice, assistive tech
  1. Understandable: Content and navigation are clear
  1. Robust: Works with current and future assistive technologies

Compliance Levels:

  • Level A: Minimum compliance (basic accessibility)
  • Level AA: Enhanced compliance (most commonly required by law)
  • Level AAA: Advanced compliance (highest standard, not always achievable)

Accessible Custom Select Component:

function AccessibleSelect({ options, value, onChange, label }) {
  const [isOpen, setIsOpen] = useState(false);  const [focusedIndex, setFocusedIndex] = useState(0);  const listboxRef = useRef(null);  const buttonRef = useRef(null);  const handleKeyDown = (e) => {
    switch (e.key) {
      case 'ArrowDown':        e.preventDefault();        if (isOpen) {
          setFocusedIndex((prev) =>
            Math.min(prev + 1, options.length - 1)
          );        } else {
          setIsOpen(true);        }
        break;      case 'ArrowUp':        e.preventDefault();        setFocusedIndex((prev) => Math.max(prev - 1, 0));        break;      case 'Enter':      case ' ':        e.preventDefault();        if (isOpen) {
          onChange(options[focusedIndex]);          setIsOpen(false);          buttonRef.current?.focus();        } else {
          setIsOpen(true);        }
        break;      case 'Escape':        setIsOpen(false);        buttonRef.current?.focus();        break;      case 'Home':        e.preventDefault();        setFocusedIndex(0);        break;      case 'End':        e.preventDefault();        setFocusedIndex(options.length - 1);        break;    }
  };  return (
    <div className="select-container">      {/* Label for screen readers */}      <label id="select-label">{label}</label>      {/* Select button */}      <button        ref={buttonRef}        type="button"        aria-haspopup="listbox"        aria-expanded={isOpen}        aria-labelledby="select-label"        onClick={() => setIsOpen(!isOpen)}        onKeyDown={handleKeyDown}        className="select-button"      >        {value || 'Select an option'}      </button>      {/* Options list */}      {isOpen && (
        <ul          ref={listboxRef}          role="listbox"          aria-labelledby="select-label"          tabIndex={-1}          onKeyDown={handleKeyDown}          className="select-listbox"        >          {options.map((option, index) => (
            <li              key={option}              role="option"              aria-selected={value === option}              data-focused={index === focusedIndex}              onClick={() => {
                onChange(option);                setIsOpen(false);                buttonRef.current?.focus();              }}              className={index === focusedIndex ? 'focused' : ''}            >              {option}            </li>          ))}        </ul>      )}    </div>  );}

Common accessibility issues and fixes:

1. Missing alt text:

<!-- BAD --><img src="logo.png" /><!-- GOOD --><img src="logo.png" alt="Company Logo" /><!-- Decorative image --><img src="decoration.png" alt="" />  <!-- Empty alt for decorative -->

2. Poor color contrast:

/* BAD - Contrast ratio < 4.5:1 */color: #777;
background: #fff;
/* GOOD - Contrast ratio ≥ 4.5:1 */color: #595959;
background: #fff;

3. Non-semantic HTML:

<!-- BAD --><div class="button" onclick="submit()">Submit</div><!-- GOOD --><button type="submit">Submit</button>

4. Missing form labels:

<!-- BAD --><input type="email" placeholder="Email" /><!-- GOOD --><label for="email">Email</label><input type="email" id="email" name="email" />

5. Keyboard navigation issues:

// BAD - Div with onClick but no keyboard support<div onClick={handleClick}>Click me</div>// GOOD - Button with built-in keyboard support<button onClick={handleClick}>Click me</button>// If you MUST use div, add keyboard support<div
  role="button"
  tabIndex={0}  onClick={handleClick}  onKeyDown={(e) => {
    if (e.key === 'Enter' || e.key === ' ') {
      handleClick();    }
  }}>  Click me
</div>

Semantic HTML structure:

<body>  <header>    <nav aria-label="Main navigation">      <ul>        <li><a href="/">Home</a></li>        <li><a href="/about">About</a></li>      </ul>    </nav>  </header>  <main>    <article>      <h1>Page Title</h1>      <section>        <h2>Section Title</h2>        <p>Content...</p>      </section>    </article>  </main>  <aside aria-label="Sidebar">    <!-- Related content -->  </aside>  <footer>    <!-- Footer content -->  </footer></body>

Testing accessibility:

// 1. Automated testing with jest-axeimport { render } from '@testing-library/react';import { axe, toHaveNoViolations } from 'jest-axe';expect.extend(toHaveNoViolations);test('should not have accessibility violations', async () => {
  const { container } = render(<MyComponent />);  const results = await axe(container);  expect(results).toHaveNoViolations();});// 2. Keyboard navigation testingtest('should be keyboard navigable', () => {
  render(<Select options={['A', 'B', 'C']} />);  const select = screen.getByRole('button');  // Tab to element  userEvent.tab();  expect(select).toHaveFocus();  // Open with Enter  userEvent.keyboard('{Enter}');  expect(screen.getByRole('listbox')).toBeInTheDocument();  // Navigate with arrows  userEvent.keyboard('{ArrowDown}');  // Assert focused option changed});

Manual testing checklist:

  • Navigate entire site with keyboard only (Tab, Enter, Escape)
  • Test with screen reader (NVDA, JAWS, VoiceOver)
  • Check color contrast with browser DevTools
  • Zoom to 200% - content should reflow
  • Use Lighthouse accessibility audit
  • Test with Chrome accessibility extensions

4. Interview Score

9/10

Why this score:
- WCAG Knowledge: Explained POUR principles and compliance levels
- Complete Implementation: Built accessible custom select with full keyboard navigation and ARIA
- Common Issues: Identified 5 typical problems with fixes
- Testing Strategy: Covered automated (jest-axe) and manual testing approaches


Question 10: Memory Leak Detection and Debugging with Chrome DevTools

Difficulty: High

Role: Mid-to-Senior Frontend Engineer

Level: Mid-to-Senior (L4-L5, 4-6 Years of Experience)

Company Examples: Google, Meta, Netflix, Uber, Airbnb

Question: “Walk me through how you’d debug a memory leak in a React application. Show me the Chrome DevTools Memory tab workflow. What are common sources of memory leaks in React?”


1. What is This Question Testing?

  • Debugging Expertise: Can you use advanced DevTools features?
  • Memory Management: Do you understand JavaScript garbage collection?
  • React-Specific Knowledge: Can you identify React memory leak patterns?
  • Systematic Approach: Do you follow a methodical debugging process?

2. The Answer

Answer:

Chrome DevTools Memory Debugging Workflow:

Step 1: Detect the leak (Performance Monitor)

  1. Open DevTools → More tools → Performance Monitor
  1. Watch “JS Heap Size” metric
  1. Perform actions that might leak (open/close modals, navigate pages)
  1. If heap size grows without dropping, memory leak likely

Step 2: Take Heap Snapshots

  1. Open DevTools → Memory tab → Heap snapshot
  1. Take snapshot before action (Snapshot 1)
  1. Perform memory-intensive action (open modal, fetch data)
  1. Take snapshot after action (Snapshot 2)
  1. Perform action again (to confirm pattern)
  1. Take third snapshot (Snapshot 3)
  1. Select “Comparison” view between snapshots

Step 3: Analyze Retained Objects

Look for:
- Objects with increasing "#New" count
- Large "Retained Size" (memory held including references)
- Detached DOM nodes
- Event listeners that weren't removed

Step 4: Identify Retainers

Click on leaked object → Expand “Retainers” section → Find what’s preventing garbage collection

Common React Memory Leak Sources:

1. Uncleared event listeners:

// BAD - Memory leakfunction Component() {
  useEffect(() => {
    window.addEventListener('resize', handleResize);    // Missing cleanup!  }, []);  return <div>Content</div>;}
// GOOD - Cleanup on unmountfunction Component() {
  useEffect(() => {
    const handleResize = () => {
      console.log('Resize');    };    window.addEventListener('resize', handleResize);    return () => {
      window.removeEventListener('resize', handleResize);    };  }, []);  return <div>Content</div>;}

2. Uncanceled timers:

// BAD - Memory leakfunction Component() {
  useEffect(() => {
    setInterval(() => {
      console.log('Tick');    }, 1000);    // Interval keeps running after unmount!  }, []);}
// GOOD - Clear timerfunction Component() {
  useEffect(() => {
    const intervalId = setInterval(() => {
      console.log('Tick');    }, 1000);    return () => {
      clearInterval(intervalId);    };  }, []);}

3. Unclosed subscriptions:

// BAD - Memory leakfunction Component() {
  useEffect(() => {
    const subscription = observable.subscribe(data => {
      console.log(data);    });    // Subscription keeps component in memory!  }, []);}
// GOOD - Unsubscribefunction Component() {
  useEffect(() => {
    const subscription = observable.subscribe(data => {
      console.log(data);    });    return () => {
      subscription.unsubscribe();    };  }, []);}

4. Detached DOM nodes:

// BAD - Holds reference to removed DOMfunction Component() {
  const nodeRef = useRef(null);  useEffect(() => {
    nodeRef.current = document.querySelector('.some-element');    // If element is removed, ref still holds it in memory  }, []);}
// GOOD - Nullify refs when no longer neededfunction Component() {
  const nodeRef = useRef(null);  useEffect(() => {
    nodeRef.current = document.querySelector('.some-element');    return () => {
      nodeRef.current = null;  // Release reference    };  }, []);}

5. Closures holding large objects:

// BAD - Closure holds entire largeData in memoryfunction Component({ largeData }) {
  const handleClick = () => {
    // Only needs largeData.id, but holds entire object    console.log(largeData.id);  };  return <button onClick={handleClick}>Click</button>;}
// GOOD - Extract only needed datafunction Component({ largeData }) {
  const dataId = largeData.id;  // Extract primitive  const handleClick = () => {
    console.log(dataId);  // Closure only holds small primitive  };  return <button onClick={handleClick}>Click</button>;}

6. State updates on unmounted components:

// BAD - Sets state after unmountfunction Component() {
  const [data, setData] = useState(null);  useEffect(() => {
    fetchData().then(result => {
      setData(result);  // Might run after unmount!    });  }, []);}
// GOOD - Cancel on unmountfunction Component() {
  const [data, setData] = useState(null);  useEffect(() => {
    let cancelled = false;    fetchData().then(result => {
      if (!cancelled) {
        setData(result);      }
    });    return () => {
      cancelled = true;    };  }, []);}

Heap Snapshot Analysis Example:

Snapshot Comparison View:

Object               | #New | #Deleted | #Delta | Shallow Size | Retained Size
---------------------|------|----------|--------|--------------|---------------
(array)              |  +50 |     0    |   +50  | 400 bytes    | 50 MB
EventListener        |  +10 |     0    |   +10  | 80 bytes     | 10 KB
DetachedHTMLDivElement| +5  |     0    |    +5  | 200 bytes    | 5 MB

^ Red flag: Objects increasing with no deletions

Prevention strategies:

// Custom hook for safe async operationsfunction useSafeAsync() {
  const mountedRef = useRef(true);  useEffect(() => {
    return () => {
      mountedRef.current = false;    };  }, []);  const safeSetState = useCallback((setter) => {
    if (mountedRef.current) {
      setter();    }
  }, []);  return safeSetState;}
// Usagefunction Component() {
  const [data, setData] = useState(null);  const safeSetState = useSafeAsync();  useEffect(() => {
    fetchData().then(result => {
      safeSetState(() => setData(result));    });  }, [safeSetState]);}

4. Interview Score

9/10

Why this score:
- Systematic Workflow: Detailed 4-step debugging process using Chrome DevTools
- React-Specific Patterns: Identified 6 common leak sources with code examples
- Practical Solutions: Provided fixes for each leak pattern
- Prevention Strategy: Included custom hook for safe async operations


Question 11: REST vs GraphQL API Design Trade-offs

Difficulty: High

Role: Mid-to-Senior Frontend Engineer

Level: Mid-to-Senior (L4-L5, 4-6 Years of Experience)

Company Examples: Stripe, Shopify, Amazon, Airbnb, GitHub

Question: “Design an API for a social media feed. Would you use REST or GraphQL? Discuss the trade-offs, pros, and cons of each approach for this specific use case.”


1. What is This Question Testing?

  • API Architecture: Understanding of modern API patterns
  • Trade-off Analysis: Can you make informed architectural decisions?
  • Frontend-Backend Collaboration: Do you understand both perspectives?
  • Real-World Experience: Have you worked with both approaches?

2. The Answer

Answer:

For a social media feed, I’d choose GraphQL. Here’s why:

REST Challenges for Social Media Feed:

// REST requires multiple requests for a single feed post// Request 1: Get postsGET /api/posts?limit=10// Response:[
  { id: 1, authorId: 123, content: "Hello", likeCount: 5 },  { id: 2, authorId: 456, content: "World", likeCount: 10 }
]
// Request 2: Get author details for each postGET /api/users/123GET /api/users/456// Request 3: Get comments for each postGET /api/posts/1/comments
GET /api/posts/2/comments
// Result: 1 + N + N requests (11 total for 10 posts)// This is the N+1 problem

GraphQL Solution:

# Single request gets everythingquery FeedPosts {
  posts(limit: 10) {
    id
    content
    createdAt
    author {
      id
      name
      avatar
    }
    likes {
      count
    }
    comments(limit: 3) {
      id
      content
      author {
        name
      }
    }
  }
}
# Response has exact shape requested# 1 request instead of 11

Implementation:

// GraphQL schematype Post {
  id: ID!  content: String!  author: User!  likes: LikeConnection!  comments(limit: Int): [Comment!]!  createdAt: DateTime!}
type User {
  id: ID!  name: String!  avatar: String}
type Query {
  posts(limit: Int, offset: Int): [Post!]!}
// React component with Apollo Client
import { useQuery, gql } from '@apollo/client';

const FEED_QUERY = gql`
  query FeedPosts($limit: Int!) {
    posts(limit: $limit) {
      id
      content
      author {
        name
        avatar
      }
      likes {
        count
      }
    }
  }
`;

function Feed() {
  const { data, loading, error } = useQuery(FEED_QUERY, {
    variables: { limit: 10 }
  });

  if (loading) return <Skeleton />;
  if (error) return <Error message={error.message} />;

  return (
    <div>
      {data.posts.map(post => (
        <PostCard key={post.id} post={post} />
      ))}
    </div>
  );
}

When REST is better:

  1. Simple CRUD operations:
// REST is clearer for simple operationsPOST /api/users      // Create userGET /api/users/123   // Get userPUT /api/users/123   // Update userDELETE /api/users/123 // Delete user
  1. Caching is critical:
    REST has built-in HTTP caching. GraphQL requires custom caching solutions.
  1. File uploads:
    REST file uploads are simpler than GraphQL.
  1. Team familiarity:
    If team doesn’t know GraphQL, REST is faster to implement.

Hybrid approach (recommended for large apps):

// Use GraphQL for complex queries (feeds, dashboards)const { data } = useQuery(DASHBOARD_QUERY);// Use REST for simple CRUD and file uploadsawait fetch('/api/profile/avatar', {
  method: 'POST',  body: formData
});

Trade-offs summary:

AspectRESTGraphQL
Learning curveLowMedium-High
Over-fetchingCommonEliminated
Under-fetching (N+1)CommonEliminated
CachingBuilt-in HTTPComplex, custom
VersioningURL versioningSchema evolution
File uploadsSimpleComplex
Type safetyManual (OpenAPI)Built-in

My recommendation: Use GraphQL for the social media feed because:
1. Eliminates N+1 queries (critical for performance)
2. Flexible - clients request exactly what they need
3. Single endpoint simplifies mobile app development
4. Real-time subscriptions for new posts
5. Type safety with generated TypeScript types


4. Interview Score

8.5/10

Why this score:
- Clear Trade-off Analysis: Compared REST vs GraphQL with specific examples
- Real-World Solution: Showed complete Apollo Client implementation
- Practical Recommendation: Suggested hybrid approach for large apps
- Decision Justification: Explained why GraphQL fits social media feed use case


Question 12: Rendering Patterns (SSR, SSG, ISR, CSR) Trade-offs

Difficulty: High

Role: Mid-to-Senior Frontend Engineer

Level: Mid-to-Senior (L4-L5, 4-6 Years of Experience)

Company Examples: Vercel, Next.js teams, Google, Meta, Airbnb

Question: “Compare CSR, SSR, SSG, and ISR rendering strategies. For different types of content (product pages, blog posts, user dashboards), which would you use and why?”


1. What is This Question Testing?

  • Modern Rendering Knowledge: Understanding of Next.js/modern frameworks
  • Performance Optimization: Can you choose the right strategy for performance?
  • SEO Awareness: Do you understand crawling and indexing implications?
  • Trade-off Thinking: Can you match strategy to use case?

2. The Answer

Answer:

Rendering Strategies Explained:

1. Client-Side Rendering (CSR) - Traditional React SPA

// All rendering happens in browserfunction Dashboard() {
  const [data, setData] = useState(null);  useEffect(() => {
    fetch('/api/user/dashboard')
      .then(res => res.json())
      .then(setData);  }, []);  if (!data) return <Spinner />;  return <div>{data.content}</div>;}

Pros: Highly interactive, best for authenticated user-specific content

Cons: Slow initial load (must download JS first), poor SEO (content renders after JS executes)

Use for: User dashboards, admin panels, authenticated apps

2. Server-Side Rendering (SSR) - Render HTML on each request

// Next.js SSRexport async function getServerSideProps() {
  const data = await fetchDashboard();  return {
    props: { data }
  };}
function Dashboard({ data }) {
  return <div>{data.content}</div>;}

Pros: Fresh data on every request, good SEO, fast First Contentful Paint

Cons: Slower than SSG (server processing on each request), higher server costs

Use for: Personalized content, real-time data, pages requiring auth

3. Static Site Generation (SSG) - Pre-render at build time

// Next.js SSGexport async function getStaticProps() {
  const posts = await fetchBlogPosts();  return {
    props: { posts },    revalidate: false  // No revalidation  };}
function Blog({ posts }) {
  return (
    <div>      {posts.map(post => <PostCard key={post.id} post={post} />)}    </div>  );}

Pros: Fastest (served from CDN), lowest server cost, excellent SEO

Cons: Must rebuild to update content, not suitable for dynamic data

Use for: Marketing pages, documentation, blogs (content changes infrequently)

4. Incremental Static Regeneration (ISR) - SSG + periodic updates

// Next.js ISRexport async function getStaticProps() {
  const products = await fetchProducts();  return {
    props: { products },    revalidate: 60  // Regenerate page every 60 seconds  };}
function Products({ products }) {
  return (
    <div>      {products.map(product => <ProductCard key={product.id} product={product} />)}    </div>  );}

Pros: Fast like SSG, content updates automatically, scalable

Cons: Stale data for up to revalidate duration, complex cache invalidation

Use for: E-commerce product pages, news sites, content that updates periodically

Use Case Matching:

Content TypeStrategyReasoning
Blog PostsSSGRarely changes, SEO critical, should be fastest
Product PagesISRUpdates periodically (price, stock), SEO important
User DashboardCSRUser-specific, no SEO needed, highly interactive
News FeedSSR or ISRFresh content important, SEO important
Marketing LandingSSGStatic content, performance critical for conversion
Search ResultsSSRDynamic queries, personalized results

Complete Next.js example:

// pages/blog/[slug].tsx - SSG for blogexport async function getStaticPaths() {
  const posts = await fetchAllPosts();  return {
    paths: posts.map(post => ({
      params: { slug: post.slug }
    })),    fallback: 'blocking'  // Generate on-demand for new posts  };}
export async function getStaticProps({ params }) {
  const post = await fetchPost(params.slug);  return {
    props: { post },    revalidate: false  // Never regenerate (static content)  };}
// pages/products/[id].tsx - ISR for productsexport async function getStaticProps({ params }) {
  const product = await fetchProduct(params.id);  return {
    props: { product },    revalidate: 60  // Regenerate every 60 seconds  };}
// pages/dashboard.tsx - CSR for user dashboardfunction Dashboard() {
  const { data } = useQuery(DASHBOARD_QUERY);  return <div>{data}</div>;}
// pages/search.tsx - SSR for search resultsexport async function getServerSideProps({ query }) {
  const results = await searchProducts(query.q);  return {
    props: { results }
  };}

Performance comparison:

Initial Load Speed (from fastest to slowest):
SSG (CDN) → ISR (CDN, might regenerate) → SSR (server processing) → CSR (JS download + execution)

Time to Interactive:
SSG/ISR (immediate) → SSR (hydration only) → CSR (must fetch data + hydrate)

SEO Quality:
SSG = ISR = SSR (all have HTML) >> CSR (no HTML until JS runs)

Freshness:
SSR (always fresh) → ISR (stale up to revalidate) → SSG (stale until rebuild) → CSR (depends on cache)

4. Interview Score

9/10

Why this score:
- Complete Coverage: Explained all 4 strategies with code examples
- Clear Use Cases: Matched each strategy to specific content types with reasoning
- Performance Analysis: Compared speed, SEO, and freshness across strategies
- Practical Implementation: Showed Next.js patterns for each approach


Question 13: CSS-in-JS Trade-offs: Styled Components vs Emotion vs Tailwind

Difficulty: Medium

Role: Mid-Level Frontend Engineer

Level: Mid-Level (L3-L4, 3-5 Years of Experience)

Company Examples: Stripe, Shopify, Square, Vercel, GitHub

Question: “Compare CSS-in-JS solutions (Styled Components, Emotion) vs utility-first CSS (Tailwind). What are the benefits and drawbacks? When would you choose one over the other?”


1. What is This Question Testing?

  • Styling Knowledge: Understanding of modern CSS approaches
  • Trade-off Analysis: Can you weigh bundle size vs DX?
  • Performance Awareness: Do you understand runtime costs?
  • Team Collaboration: Consideration of designer-developer workflows

2. The Answer

Answer:

Three Main Approaches:

1. Styled Components (CSS-in-JS)

import styled from 'styled-components';const Button = styled.button`  background: ${props => props.primary ? 'blue' : 'gray'};  color: white;  padding: 12px 24px;  border-radius: 4px;  &:hover {    opacity: 0.8;  }`;<Button primary>Click Me</Button>

Pros:
- Scoped styles (no naming conflicts)
- Dynamic styling with props
- Theming support
- Colocated with components

Cons:
- Runtime overhead (15KB + parsing cost)
- Larger bundle size
- CSS-in-JS parsing happens at runtime
- Harder to cache

2. Tailwind CSS (Utility-First)

function Button({ primary, children }) {
  return (
    <button className={`      px-6 py-3 rounded      ${primary ? 'bg-blue-500' : 'bg-gray-500'}      text-white hover:opacity-80    `}>      {children}    </button>  );}
<Button primary>Click Me</Button>

Pros:
- Zero runtime cost (compiled at build time)
- Smaller bundle (purged unused classes)
- Faster development (no context switching)
- Better caching (static CSS file)

Cons:
- Verbose className strings
- Learning curve for class names
- Harder for designers to contribute
- Dynamic styling requires extra logic

3. Emotion (CSS-in-JS, Optimized)

import { css } from '@emotion/react';const buttonStyle = (primary) => css`  background: ${primary ? 'blue' : 'gray'};  color: white;  padding: 12px 24px;  &:hover {    opacity: 0.8;  }`;<button css={buttonStyle(true)}>Click Me</button>

Pros:
- Smaller bundle than Styled Components (7KB)
- css prop more flexible than styled API
- Better performance than Styled Components

Cons:
- Still has runtime cost
- Requires Babel plugin for css prop
- Less mature ecosystem

Decision Matrix:

FactorStyled ComponentsEmotionTailwind
Bundle Size15KB7KB3KB (purged)
Runtime CostHighMediumZero
DXExcellentExcellentGood
PerformanceMediumMedium-HighExcellent
ThemingBuilt-inBuilt-inConfig-based
Designer-FriendlyYesYesNo

When to choose each:

Styled Components: Component library, design system, dynamic themes

Emotion: Need CSS-in-JS but want better performance

Tailwind: Performance-critical app, rapid prototyping, small team

Hybrid approach (recommended):

// Tailwind for layout and utilities<div className="flex items-center gap-4 p-6">  {/* Styled Components for complex, dynamic components */}  <StyledButton variant={variant} /></div>

My recommendation for most projects: Tailwind CSS

Reasons:
1. Zero runtime cost = faster performance
2. Smaller bundle size after purging
3. Faster development once learned
4. Better for code splitting (static CSS)
5. Easier to optimize (CSS cached separately)

Use CSS-in-JS only when you need:
- Extremely dynamic styling based on complex state
- Component libraries sold to external users
- Theme switching with many variants


4. Interview Score

8.5/10

Why this score:
- Comprehensive Comparison: Covered three major approaches with code examples
- Quantified Trade-offs: Specific bundle sizes and performance characteristics
- Decision Framework: Clear guidance on when to choose each
- Practical Recommendation: Suggested Tailwind with reasoning, plus hybrid approach


Question 14: Module Federation and Micro Frontends Architecture

Difficulty: Very High

Role: Senior Frontend Engineer / Architect

Level: Senior (L5-L6, 5-8 Years of Experience)

Company Examples: Netflix, Amazon, Shopify, Square, Spotify

Question: “Explain Module Federation in Webpack 5 and how it enables Micro Frontend architecture. What are the key challenges in implementing Micro Frontends? How would you handle shared dependencies, versioning, and inter-app communication?”


1. What is This Question Testing?

  • Advanced Architecture: Understanding of distributed frontend systems
  • Webpack Knowledge: Deep understanding of build tools and bundling
  • Scalability Thinking: Can you design for multiple teams and applications?
  • Real-World Experience: Have you implemented or worked with Micro Frontends?

2. The Answer

Answer:

Module Federation Basics:

Module Federation allows separate JavaScript applications to dynamically share code at runtime without bundling together.

Traditional approach (monolithic):

App builds all code → Single bundle → Deploy everything together

Module Federation approach:

App 1 (host) ← Loads modules from → App 2, App 3 (remotes)
Each app deploys independently

Implementation example:

// webpack.config.js for Host Appconst ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'host',      remotes: {
        productApp: 'productApp@http://localhost:3001/remoteEntry.js',        checkoutApp: 'checkoutApp@http://localhost:3002/remoteEntry.js',      },      shared: {
        react: { singleton: true, requiredVersion: '^18.0.0' },        'react-dom': { singleton: true, requiredVersion: '^18.0.0' },      },    }),  ],};// webpack.config.js for Remote App (Product)module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'productApp',      filename: 'remoteEntry.js',      exposes: {
        './ProductList': './src/ProductList',        './ProductDetail': './src/ProductDetail',      },      shared: {
        react: { singleton: true },        'react-dom': { singleton: true },      },    }),  ],};

Using remote components:

// Host Appimport React, { lazy, Suspense } from 'react';const ProductList = lazy(() => import('productApp/ProductList'));const CheckoutFlow = lazy(() => import('checkoutApp/CheckoutFlow'));function App() {
  return (
    <div>      <Suspense fallback={<Loading />}>        <ProductList />        <CheckoutFlow />      </Suspense>    </div>  );}

Key Challenges and Solutions:

1. Shared Dependency Version Conflicts

Problem: Product app uses React 18, Checkout app uses React 17

Solution:

// Strict version matchingshared: {
  react: {
    singleton: true,  // Only one version loaded    requiredVersion: '^18.0.0',  // Enforce version    strictVersion: true,  // Fail if version mismatch  },}
// Or version ranges with fallbackshared: {
  react: {
    singleton: true,    requiredVersion: '^17.0.0 || ^18.0.0',  },}

2. Inter-App Communication

Solution: Event Bus Pattern

// shared-utils/eventBus.tsclass EventBus {
  private events: Map<string, Set<Function>> = new Map();  emit(event: string, data: any) {
    const handlers = this.events.get(event);    if (handlers) {
      handlers.forEach(handler => handler(data));    }
  }
  on(event: string, handler: Function) {
    if (!this.events.has(event)) {
      this.events.set(event, new Set());    }
    this.events.get(event)!.add(handler);  }
  off(event: string, handler: Function) {
    this.events.get(event)?.delete(handler);  }
}
export const eventBus = new EventBus();// Product App - Emit eventimport { eventBus } from 'shared/eventBus';function ProductList() {
  const handleAddToCart = (product) => {
    eventBus.emit('cart:add', product);  };  return <button onClick={() => handleAddToCart(product)}>Add</button>;}
// Checkout App - Listen to eventimport { eventBus } from 'shared/eventBus';function Cart() {
  const [items, setItems] = useState([]);  useEffect(() => {
    const handler = (product) => {
      setItems(prev => [...prev, product]);    };    eventBus.on('cart:add', handler);    return () => eventBus.off('cart:add', handler);  }, []);  return <div>{items.length} items</div>;}

3. Type Safety Across Apps

Solution: Shared type packages

// @types/shared-contracts/index.tsexport interface Product {
  id: string;  name: string;  price: number;}
export interface CartEvent {
  type: 'add' | 'remove';  product: Product;}
// Product Appimport { Product } from '@types/shared-contracts';function ProductList(): JSX.Element {
  const products: Product[] = fetchProducts();  // ...}

4. Deployment and Versioning

Solution: Version manifests and graceful degradation

// version-manifest.json (hosted separately){
  "productApp": {
    "url": "https://cdn.example.com/productApp@1.2.3/remoteEntry.js",    "version": "1.2.3"  },  "checkoutApp": {
    "url": "https://cdn.example.com/checkoutApp@2.1.0/remoteEntry.js",    "version": "2.1.0"  }
}
// Dynamic remote loadingasync function loadRemoteApp(appName) {
  const manifest = await fetch('/version-manifest.json').then(r => r.json());  const remoteUrl = manifest[appName].url;  // Load remote dynamically  await loadScript(remoteUrl);}

5. Error Boundaries

class MicroFrontendBoundary extends React.Component {
  state = { hasError: false, error: null };  static getDerivedStateFromError(error) {
    return { hasError: true, error };  }
  componentDidCatch(error, errorInfo) {
    console.error('Micro Frontend Error:', error, errorInfo);    // Send to error tracking  }
  render() {
    if (this.state.hasError) {
      return (
        <div>          <h2>This module failed to load</h2>          <button onClick={() => window.location.reload()}>            Reload
          </button>        </div>      );    }
    return this.props.children;  }
}
// Usage<MicroFrontendBoundary>  <Suspense fallback={<Loading />}>    <RemoteProductApp />  </Suspense></MicroFrontendBoundary>

When Micro Frontends make sense:

  • Large teams (50+ engineers)
  • Multiple product areas with clear boundaries
  • Need independent deployment cadences
  • Different teams using different tech stacks

When they DON’T make sense:

  • Small team (< 10 engineers)
  • Tightly coupled features
  • Performance is critical (overhead from federation)
  • Simple CRUD applications

4. Interview Score

9/10

Why this score:
- Deep Technical Knowledge: Explained Module Federation configuration and implementation
- Complete Solutions: Addressed 5 key challenges with concrete code examples
- Architecture Awareness: Discussed when Micro Frontends are appropriate vs overkill
- Production-Ready: Included error handling, versioning, and deployment strategies


Question 15: Testing Strategies (Jest, React Testing Library) for Enterprise Apps

Difficulty: Medium-High

Role: Mid-to-Senior Frontend Engineer

Level: Mid-to-Senior (L4-L5, 4-6 Years of Experience)

Company Examples: Meta, Google, Amazon, Shopify, Microsoft

Question: “Design a comprehensive testing strategy for a large React application. How would you balance unit tests, integration tests, and E2E tests? What’s your philosophy on test coverage? How do you test async code, forms, and API integration?”


1. What is This Question Testing?

  • Testing Philosophy: Do you understand the testing pyramid?
  • Practical Experience: Have you written tests in production apps?
  • Quality Mindset: Do you test behavior or implementation?
  • Tool Knowledge: Jest, React Testing Library, Playwright/Cypress familiarity

2. The Answer

Answer:

Testing Pyramid for Enterprise React Apps:

         ╱ E2E Tests (5%)
        ╱  Critical user flows only
       ╱─────────────────────────
      ╱ Integration Tests (20%)
     ╱  Component workflows, API mocking
    ╱─────────────────────────────────
   ╱ Unit Tests (75%)
  ╱  Components, hooks, utils
 ╱───────────────────────────────────

Philosophy: Test behavior, not implementation

Unit Tests (Jest + React Testing Library):

// ❌ BAD - Testing implementation detailstest('state updates correctly', () => {
  const { result } = renderHook(() => useState(0));  const [count, setCount] = result.current;  act(() => setCount(1));  expect(result.current[0]).toBe(1);  // Testing internal state});// ✅ GOOD - Testing user-visible behaviortest('counter increments when button clicked', () => {
  render(<Counter />);  const button = screen.getByRole('button', { name: /increment/i });  const count = screen.getByText(/count: 0/i);  fireEvent.click(button);  expect(screen.getByText(/count: 1/i)).toBeInTheDocument();});

Testing async code:

import { render, screen, waitFor } from '@testing-library/react';import userEvent from '@testing-library/user-event';test('loads and displays user data', async () => {
  // Mock API  jest.spyOn(global, 'fetch').mockResolvedValue({
    json: async () => ({ name: 'John', email: 'john@example.com' }),  });  render(<UserProfile userId="123" />);  // Loading state  expect(screen.getByText(/loading/i)).toBeInTheDocument();  // Wait for data to load  await waitFor(() => {
    expect(screen.getByText('John')).toBeInTheDocument();    expect(screen.getByText('john@example.com')).toBeInTheDocument();  });  global.fetch.mockRestore();});// Or use findBy (combines getBy + waitFor)test('same test with findBy', async () => {
  jest.spyOn(global, 'fetch').mockResolvedValue({
    json: async () => ({ name: 'John' }),  });  render(<UserProfile userId="123" />);  // findBy automatically waits  expect(await screen.findByText('John')).toBeInTheDocument();  global.fetch.mockRestore();});

Testing forms:

test('validates email and submits form', async () => {
  const handleSubmit = jest.fn();  const user = userEvent.setup();  render(<LoginForm onSubmit={handleSubmit} />);  // Invalid email  const emailInput = screen.getByLabelText(/email/i);  const passwordInput = screen.getByLabelText(/password/i);  const submitButton = screen.getByRole('button', { name: /submit/i });  await user.type(emailInput, 'invalid-email');  await user.type(passwordInput, 'password123');  await user.click(submitButton);  // Error should display  expect(await screen.findByText(/invalid email/i)).toBeInTheDocument();  expect(handleSubmit).not.toHaveBeenCalled();  // Valid email  await user.clear(emailInput);  await user.type(emailInput, 'john@example.com');  await user.click(submitButton);  // Form should submit  await waitFor(() => {
    expect(handleSubmit).toHaveBeenCalledWith({
      email: 'john@example.com',      password: 'password123',    });  });});

Testing API integration with MSW (Mock Service Worker):

// setupTests.jsimport { setupServer } from 'msw/node';import { rest } from 'msw';const server = setupServer(
  rest.get('/api/users/:id', (req, res, ctx) => {
    return res(
      ctx.json({
        id: req.params.id,        name: 'John Doe',      })
    );  })
);beforeAll(() => server.listen());afterEach(() => server.resetHandlers());afterAll(() => server.close());// Test filetest('fetches and displays user', async () => {
  render(<UserProfile userId="123" />);  expect(await screen.findByText('John Doe')).toBeInTheDocument();});test('handles API error', async () => {
  // Override handler for this test  server.use(
    rest.get('/api/users/:id', (req, res, ctx) => {
      return res(ctx.status(500));    })
  );  render(<UserProfile userId="123" />);  expect(await screen.findByText(/error/i)).toBeInTheDocument();});

Integration Tests (Testing Library + MSW):

test('complete checkout flow', async () => {
  const user = userEvent.setup();  render(<App />);  // Navigate to product  await user.click(screen.getByText(/products/i));  // Add to cart  await user.click(await screen.findByText(/add to cart/i));  // Open cart  await user.click(screen.getByLabelText(/cart/i));  expect(await screen.findByText(/1 item/i)).toBeInTheDocument();  // Proceed to checkout  await user.click(screen.getByText(/checkout/i));  // Fill form  await user.type(screen.getByLabelText(/name/i), 'John');  await user.type(screen.getByLabelText(/email/i), 'john@example.com');  await user.click(screen.getByText(/submit order/i));  // Success message  expect(await screen.findByText(/order confirmed/i)).toBeInTheDocument();});

E2E Tests (Playwright):

// e2e/checkout.spec.tsimport { test, expect } from '@playwright/test';test('user can complete purchase', async ({ page }) => {
  await page.goto('http://localhost:3000');  // Login  await page.fill('[name="email"]', 'test@example.com');  await page.fill('[name="password"]', 'password123');  await page.click('button:has-text("Login")');  // Add product to cart  await page.click('text=Products');  await page.click('button:has-text("Add to Cart")');  // Checkout  await page.click('[aria-label="Cart"]');  await page.click('text=Checkout');  // Payment  await page.fill('[name="cardNumber"]', '4242424242424242');  await page.fill('[name="expiry"]', '12/25');  await page.fill('[name="cvc"]', '123');  await page.click('text=Pay Now');  // Confirmation  await expect(page.locator('text=Order Confirmed')).toBeVisible();});

Test Coverage Philosophy:

Don’t chase 100% coverage. Focus on:

  1. Critical paths: Login, checkout, payment
  1. Complex logic: Algorithms, calculations, state machines
  1. Bug-prone areas: Forms, async operations, edge cases

Skip testing:
- Trivial getters/setters
- Third-party library internals
- Simple presentational components with no logic

Coverage targets:
- Critical features: 90%+
- Business logic: 80%+
- UI components: 60%+
- Overall: 70-75%

Jest configuration:

// jest.config.jsmodule.exports = {
  coverageThreshold: {
    global: {
      statements: 70,      branches: 70,      functions: 70,      lines: 70,    },    './src/critical/**/*.ts': {
      statements: 90,      branches: 90,    },  },  collectCoverageFrom: [
    'src/**/*.{ts,tsx}',    '!src/**/*.test.{ts,tsx}',    '!src/**/*.stories.{ts,tsx}',  ],};

Testing custom hooks:

import { renderHook, act, waitFor } from '@testing-library/react';test('useFetch returns data', async () => {
  jest.spyOn(global, 'fetch').mockResolvedValue({
    json: async () => ({ data: 'test' }),  });  const { result } = renderHook(() => useFetch('/api/data'));  // Initial state  expect(result.current.loading).toBe(true);  expect(result.current.data).toBe(null);  // After fetch  await waitFor(() => {
    expect(result.current.loading).toBe(false);    expect(result.current.data).toEqual({ data: 'test' });  });  global.fetch.mockRestore();});

4. Interview Score

9/10

Why this score:
- Comprehensive Strategy: Covered unit, integration, and E2E tests with pyramid philosophy
- Practical Examples: Showed testing async code, forms, API mocking, and custom hooks
- Modern Tools: Demonstrated MSW, React Testing Library best practices, Playwright
- Realistic Philosophy: Explained coverage targets (70-75%) and what NOT to test


End of Frontend Developer Interview Questions

This completes all 15 comprehensive frontend developer interview questions covering:
1. Virtual DOM & Reconciliation
2. Large-Scale Architecture
3. JavaScript Closures
4. Event Loop & Microtasks
5. Web Vitals Optimization
6. TypeScript Generics
7. Custom Hooks Anti-patterns
8. Service Workers & PWAs
9. Accessibility (WCAG)
10. Memory Leak Debugging
11. REST vs GraphQL
12. Rendering Patterns (SSR/SSG/ISR/CSR)
13. CSS-in-JS vs Tailwind
14. Module Federation & Micro Frontends
15. Testing Strategies

All questions follow the comprehensive format with difficulty levels, role specifications, company examples, frameworks, detailed answers, and interview scores.