Brain Busters
QuizzesMock TestsGamesLibrary
UpdatesCommunityAboutContactPremium
Brain BustersLearning and Exam Intelligence

A student learning app built for practice discipline, exam simulation, and visible improvement.

Move from reading to execution with guided quizzes, mock tests, performance signals, and current exam updates in one system.

Student-first
Built for focused learners
More than content
Practice, revise, and measure
Progress system
Study with exam-ready feedback

Platform

  • Practice Quizzes
  • Mock Tests
  • Brain Games
  • Learning Library
  • Premium Plans

Resources

  • About Us
  • Exam Updates
  • Community
  • Contact
Weekly Signals

Join the intelligence loop

Receive product updates, study prompts, and exam alerts without the noise.

Location
Azamgarh, Uttar Pradesh, India
Support Line
+91 9161060447
Direct Email
support@brainbusters.in

© 2026 Brain Busters. Practice with intent.

PrivacyTermsSitemap
    Back to library
    Learning article
    Web Development
    JavaScript

    Frontend Interview Questions 2025: Hooks Intricacies, RSC, Performance Tradeoffs, and System Design

    The frontend landscape in 2025 has evolved significantly, with React Server Components becoming mainstream, hooks patterns maturing, and performance optimization techniques becoming more sophisticated. This guide explores the most critical interview topics that separate senior de

    RC

    R.S. Chauhan

    Brain Busters editorial

    October 13, 2025
    50 min read
    0 likes

    Article snapshot

    Read with revision in mind.

    Use the article to understand the topic, identify weak areas, and move back into quizzes with more context.

    Best for concept review
    Start here before timed practice if the topic feels rusty.
    Revision friendly
    Use the tags and related posts to build a tighter study path around the same theme.
    Discuss and clarify
    Add a comment if you want examples, clarifications, or a follow-up explanation.
    Frontend Interview Questions 2025: Hooks Intricacies, RSC, Performance Tradeoffs, and System Design

    Introduction

    The frontend landscape in 2025 has evolved significantly, with React Server Components becoming mainstream, hooks patterns maturing, and performance optimization techniques becoming more sophisticated. This guide explores the most critical interview topics that separate senior developers from the rest.


    Part 1: React Hooks Intricacies

    Question 1: Explain the closure trap in useEffect and how to avoid it

    The Problem:

    The closure trap occurs when a useEffect hook captures stale values from previous renders because it doesn't properly declare its dependencies. This happens because JavaScript closures capture variables from their lexical scope at the time of function creation.

    Example of the Problem:

    function Counter() {
      const [count, setCount] = useState(0);
      
      useEffect(() => {
        const timer = setInterval(() => {
          console.log(count); // Always logs 0!
          setCount(count + 1); // Always sets to 1!
        }, 1000);
        
        return () => clearInterval(timer);
      }, []); // Empty dependency array causes closure trap
      
      return <div>{count}</div>;
    }

    Why It Happens:

    The interval callback captures the initial count value (0) when the effect first runs. Since the dependency array is empty, the effect never re-runs, and the callback never gets updated with new count values.

    Solutions:

    1. Functional Updates: Use the function form of setState
    setCount(c => c + 1); // Works correctly
    
    1. Proper Dependencies: Include all used variables
    useEffect(() => {
      const timer = setInterval(() => {
        setCount(count + 1);
      }, 1000);
      return () => clearInterval(timer);
    }, [count]); // Re-creates interval on each count change
    
    1. useRef for Latest Values: When you need current values without re-running effects
    const countRef = useRef(count);
    countRef.current = count;
    
    useEffect(() => {
      const timer = setInterval(() => {
        setCount(countRef.current + 1);
      }, 1000);
      return () => clearInterval(timer);
    }, []);
    

    Question 2: When would you use useMemo vs useCallback, and what are their performance implications?

    useMemo: Memoizes a computed value

    useCallback: Memoizes a function reference

    Key Differences:

    // useMemo - returns the result of the function
    const expensiveValue = useMemo(() => {
      return computeExpensiveValue(a, b);
    }, [a, b]);
    
    // useCallback - returns the function itself
    const memoizedCallback = useCallback(() => {
      doSomething(a, b);
    }, [a, b]);
    
    // These are equivalent:
    const memoizedCallback = useCallback(fn, deps);
    const memoizedCallback = useMemo(() => fn, deps);
    

    When to Use useMemo:

    1. Expensive calculations: When computing a value is CPU-intensive
    2. Referential equality: When passing objects/arrays to child components that use React.memo
    3. Dependency in other hooks: When the value is used in dependency arrays

    When to Use useCallback:

    1. Passing callbacks to optimized children: Components wrapped in React.memo
    2. Dependencies in useEffect: When the function is a dependency in another hook
    3. Event handlers for expensive renders: But only when the child component is memoized

    Performance Tradeoffs:

    Both hooks have overhead:

    • Memory cost: Storing previous values/functions
    • Comparison cost: Checking dependencies on each render

    Don't use them when:

    • The computation is cheap (faster to just recalculate)
    • Child components aren't memoized
    • The dependencies change frequently anyway

    Good Example:

    function ProductList({ products, filter }) {
      // Good: Expensive filtering operation
      const filteredProducts = useMemo(() => {
        return products.filter(p => 
          complexFilterLogic(p, filter)
        ).sort((a, b) => complexSort(a, b));
      }, [products, filter]);
      
      // Good: Prevents re-render of memoized child
      const handleClick = useCallback((id) => {
        selectProduct(id);
      }, [selectProduct]);
      
      return (
        <div>
          {filteredProducts.map(p => 
            <MemoizedProduct 
              key={p.id} 
              product={p}
              onClick={handleClick}
            />
          )}
        </div>
      );
    }
    

    Question 3: Explain the rules of hooks and why they exist at the implementation level

    The Rules:

    1. Only call hooks at the top level (not in loops, conditions, or nested functions)
    2. Only call hooks from React function components or custom hooks

    Why These Rules Exist:

    React relies on the order of hook calls to maintain state between renders. React doesn't use variable names or IDs to track which state belongs to which hook call.

    Internal Implementation Concept:

    // Simplified React internals
    let currentFiber = null;
    let hookIndex = 0;
    
    function useState(initialValue) {
      const fiber = currentFiber;
      const hooks = fiber.hooks || [];
      const hook = hooks[hookIndex] || { state: initialValue };
      
      hooks[hookIndex] = hook;
      hookIndex++;
      
      const setState = (newValue) => {
        hook.state = newValue;
        scheduleRender(fiber);
      };
      
      return [hook.state, setState];
    }
    

    What Breaks When Rules Are Violated:

    function BrokenComponent({ condition }) {
      const [a, setA] = useState(1);
      
      if (condition) {
        const [b, setB] = useState(2); // ❌ Conditional hook
      }
      
      const [c, setC] = useState(3);
    }
    
    // First render (condition=true):
    // hooks[0] = state for 'a'
    // hooks[1] = state for 'b'
    // hooks[2] = state for 'c'
    
    // Second render (condition=false):
    // hooks[0] = state for 'a'
    // hooks[1] = state for 'c' (WRONG! This gets the old 'b' state)
    

    Correct Pattern:

    function CorrectComponent({ condition }) {
      const [a, setA] = useState(1);
      const [b, setB] = useState(2); // Always call
      const [c, setC] = useState(3);
      
      // Use condition in render logic instead
      return <div>{condition ? b : null}</div>;
    }
    

    Part 2: React Server Components (RSC)

    Question 4: Explain the fundamental difference between Server Components and traditional SSR

    Traditional SSR (Server-Side Rendering):

    1. Server renders HTML from React components
    2. Sends HTML to browser
    3. Browser downloads JavaScript bundle
    4. React hydrates the HTML (attaches event handlers, makes it interactive)
    5. All components become client-side components after hydration

    React Server Components:

    1. Server components run ONLY on the server
    2. They never ship to the client (zero JavaScript bundle impact)
    3. Can directly access databases, file systems, etc.
    4. Output is a special format (not HTML), describing the UI
    5. Client components are rendered on the client as usual

    Key Differences:

    Aspect SSR RSC
    JavaScript bundle Full bundle sent Only client components sent
    Re-rendering Client-side Can refetch from server
    Data fetching useEffect or getServerSideProps Direct in component
    State/Interactivity After hydration Only in Client Components

    Example:

    // Server Component (no 'use client' directive)
    async function ProductPage({ id }) {
      // Runs on server only - direct DB access
      const product = await db.products.findById(id);
      const reviews = await db.reviews.findByProduct(id);
      
      return (
        <div>
          <h1>{product.name}</h1>
          <ProductImage src={product.image} />
          {/* Client Component for interactivity */}
          <AddToCartButton product={product} />
          <ReviewList reviews={reviews} />
        </div>
      );
    }
    
    // Client Component
    'use client'
    function AddToCartButton({ product }) {
      const [added, setAdded] = useState(false);
      
      return (
        <button onClick={() => setAdded(true)}>
          {added ? 'Added!' : 'Add to Cart'}
        </button>
      );
    }
    

    Benefits:

    • Reduced JavaScript bundle size
    • Better initial load performance
    • Automatic code splitting
    • Direct backend access without API routes
    • Can use server-only libraries

    Question 5: How do you handle data mutations in a Server Component architecture?

    The Challenge:

    Server Components can't use hooks like useState or handle events directly. You need patterns for mutations that work in this model.

    Solution 1: Server Actions

    // Server Component
    async function TodoList() {
      const todos = await db.todos.findAll();
      
      async function addTodo(formData) {
        'use server' // Marks this as a Server Action
        
        const text = formData.get('text');
        await db.todos.create({ text });
        revalidatePath('/todos'); // Refresh this route
      }
      
      return (
        <div>
          {todos.map(todo => <div key={todo.id}>{todo.text}</div>)}
          
          <form action={addTodo}>
            <input name="text" />
            <button type="submit">Add</button>
          </form>
        </div>
      );
    }
    

    Solution 2: Client Components with API Routes

    // Server Component
    async function ProductPage({ id }) {
      const product = await getProduct(id);
      
      // Pass data to Client Component
      return <ProductDetails product={product} />;
    }
    
    // Client Component
    'use client'
    function ProductDetails({ product }) {
      const [optimisticStock, setOptimisticStock] = useState(product.stock);
      
      async function handlePurchase() {
        // Optimistic update
        setOptimisticStock(prev => prev - 1);
        
        try {
          await fetch('/api/purchase', {
            method: 'POST',
            body: JSON.stringify({ productId: product.id })
          });
          
          // Trigger server re-fetch
          router.refresh();
        } catch (error) {
          // Revert optimistic update
          setOptimisticStock(product.stock);
        }
      }
      
      return (
        <div>
          <p>Stock: {optimisticStock}</p>
          <button onClick={handlePurchase}>Buy Now</button>
        </div>
      );
    }
    

    Solution 3: Hybrid Approach with Optimistic Updates

    'use client'
    import { useOptimistic } from 'react';
    
    function CommentSection({ comments, postId }) {
      const [optimisticComments, addOptimisticComment] = useOptimistic(
        comments,
        (state, newComment) => [...state, { ...newComment, pending: true }]
      );
      
      async function submitComment(formData) {
        const text = formData.get('text');
        
        addOptimisticComment({ text, author: 'You' });
        
        await createComment(postId, text); // Server Action
      }
      
      return (
        <div>
          {optimisticComments.map((comment, i) => (
            <div key={i} style={{ opacity: comment.pending ? 0.5 : 1 }}>
              {comment.text}
            </div>
          ))}
          
          <form action={submitComment}>
            <input name="text" />
            <button>Submit</button>
          </form>
        </div>
      );
    }
    

    Question 6: What are the security implications of Server Components and how do you prevent data leaks?

    Key Security Concerns:

    1. Accidental Data Exposure: Server Components can access sensitive data, but you must be careful what you pass to Client Components

    Problem Example:

    // ❌ DANGEROUS: Server Component
    async function UserProfile({ userId }) {
      const user = await db.users.findById(userId);
      
      // This passes EVERYTHING to the client!
      return <ClientSideProfile user={user} />;
    }
    
    // The entire user object (including passwordHash, email, etc.)
    // is serialized and sent to the browser
    

    Safe Approach:

    // ✅ SAFE: Server Component
    async function UserProfile({ userId }) {
      const user = await db.users.findById(userId);
      
      // Only pass what's needed
      const publicData = {
        name: user.name,
        avatar: user.avatar,
        bio: user.bio
      };
      
      return <ClientSideProfile user={publicData} />;
    }
    
    1. Server Action Security: Validate everything, never trust client input
    // ✅ Proper validation
    async function updateProfile(formData) {
      'use server'
      
      // 1. Authentication
      const session = await getSession();
      if (!session) throw new Error('Unauthorized');
      
      // 2. Authorization
      const userId = formData.get('userId');
      if (userId !== session.userId) {
        throw new Error('Forbidden');
      }
      
      // 3. Input validation
      const bio = formData.get('bio');
      if (bio.length > 500) {
        throw new Error('Bio too long');
      }
      
      // 4. Sanitization
      const sanitizedBio = sanitizeHtml(bio);
      
      await db.users.update(userId, { bio: sanitizedBio });
    }
    
    1. Environment Variable Exposure
    // ❌ WRONG: Client Component
    'use client'
    function Dashboard() {
      // This tries to access server-only env var
      const apiKey = process.env.SECRET_API_KEY; // undefined in browser!
    }
    
    // ✅ CORRECT: Server Component
    async function Dashboard() {
      const data = await fetch('https://api.example.com', {
        headers: {
          'Authorization': process.env.SECRET_API_KEY // Only on server
        }
      });
      
      return <DataDisplay data={data} />;
    }
    

    Best Practices:

    • Always validate Server Action inputs
    • Use type-safe parsers (like Zod) for form data
    • Never pass sensitive data to Client Components
    • Implement proper authentication checks in Server Actions
    • Use environment variables prefixed with NEXT_PUBLIC_ only for truly public data

    Part 3: Performance Tradeoffs

    Question 7: Explain the performance implications of code splitting strategies

    What is Code Splitting?

    Breaking your JavaScript bundle into smaller chunks that are loaded on-demand rather than all at once.

    Strategy 1: Route-Based Splitting

    // Automatic with Next.js app router
    app/
      products/page.js  // Separate chunk
      checkout/page.js  // Separate chunk
      profile/page.js   // Separate chunk
    

    Pros:

    • Simple to implement
    • Reduces initial bundle size
    • Clear loading boundaries

    Cons:

    • May delay rendering of routes
    • Doesn't help with large shared dependencies

    Strategy 2: Component-Based Splitting

    import dynamic from 'next/dynamic';
    
    const HeavyChart = dynamic(() => import('./HeavyChart'), {
      loading: () => <Spinner />,
      ssr: false // Don't render on server
    });
    
    function Dashboard() {
      return (
        <div>
          <QuickStats />
          {/* Only loads when this component renders */}
          <HeavyChart data={data} />
        </div>
      );
    }
    

    Pros:

    • Fine-grained control
    • Can defer expensive components
    • Reduces main bundle size

    Cons:

    • More complex to manage
    • Can cause layout shifts
    • Network waterfalls if not careful

    Strategy 3: Vendor Splitting

    // webpack config
    optimization: {
      splitChunks: {
        cacheGroups: {
          vendor: {
            test: /[\\/]node_modules[\\/]/,
            name: 'vendors',
            chunks: 'all'
          },
          common: {
            minChunks: 2,
            name: 'common',
            chunks: 'all'
          }
        }
      }
    }
    

    Pros:

    • Better caching (vendor code changes less)
    • Shared dependencies in one file
    • Predictable bundle structure

    Cons:

    • Large vendor bundle if not careful
    • May download unused code

    Performance Tradeoffs to Consider:

    1. Bundle Size vs Request Count

      • Fewer, larger bundles: Less overhead, but more unused code
      • Many small bundles: More requests, more overhead
    2. Initial Load vs Navigation Speed

      • Eager loading: Fast navigation, slow initial
      • Lazy loading: Fast initial, potential delays later
    3. Caching vs Freshness

      • Aggressive splitting: Better caching
      • Less splitting: Simpler updates

    Optimal Strategy (2025):

    // Combine approaches
    // 1. Route-based for pages
    // 2. Component-based for heavy components
    // 3. Vendor splitting for libraries
    
    // Example: Next.js app structure
    app/
      layout.js           // Core layout (eager)
      page.js            // Home (eager)
      products/
        page.js          // Products route (lazy)
        [id]/
          page.js        // Product detail (lazy)
    
    components/
      Header.js          // Shared (in main bundle)
      ProductGrid.js     // Shared (in main bundle)
      Reviews.js         // Dynamic import (lazy)
      Analytics.js       // Dynamic import (lazy, low priority)
    

    Measurement Approach:

    // Monitor with performance API
    const observer = new PerformanceObserver((list) => {
      for (const entry of list.getEntries()) {
        if (entry.initiatorType === 'script') {
          console.log('Script load:', entry.name, entry.duration);
        }
      }
    });
    
    observer.observe({ entryTypes: ['resource'] });
    

    Question 8: When should you use virtual scrolling vs pagination vs infinite scroll?

    Virtual Scrolling (Windowing)

    Render only visible items plus a buffer, reuse DOM elements as you scroll.

    import { FixedSizeList } from 'react-window';
    
    function VirtualList({ items }) {
      const Row = ({ index, style }) => (
        <div style={style}>
          {items[index].name}
        </div>
      );
      
      return (
        <FixedSizeList
          height={600}
          itemCount={items.length}
          itemSize={50}
          width="100%"
        >
          {Row}
        </FixedSizeList>
      );
    }
    

    Best for:

    • 1,000+ items available in memory
    • Fixed or predictable item sizes
    • Tables, logs, data grids
    • When you need instant access to any item

    Performance:

    • DOM nodes: ~20-50 (constant)
    • Memory: All data in RAM
    • Scrolling: Smooth, instant

    Tradeoffs:

    • Complex implementation
    • Accessibility challenges
    • SEO: Content not in HTML
    • Poor for dynamic heights

    Pagination

    Divide data into discrete pages, load one page at a time.

    function PaginatedList({ totalItems, itemsPerPage = 20 }) {
      const [page, setPage] = useState(1);
      const [items, setItems] = useState([]);
      
      useEffect(() => {
        fetch(`/api/items?page=${page}&limit=${itemsPerPage}`)
          .then(r => r.json())
          .then(setItems);
      }, [page]);
      
      return (
        <div>
          {items.map(item => <ItemCard key={item.id} {...item} />)}
          
          <button onClick={() => setPage(p => p - 1)} disabled={page === 1}>
            Previous
          </button>
          <span>Page {page}</span>
          <button onClick={() => setPage(p => p + 1)}>
            Next
          </button>
        </div>
      );
    }
    

    Best for:

    • Search results
    • E-commerce product listings
    • When users need to find specific items
    • When you need stable URLs for each page

    Performance:

    • DOM nodes: Fixed per page
    • Memory: Only current page
    • Navigation: Requires page load

    Tradeoffs:

    • Slower browsing experience
    • Good for SEO (each page indexable)
    • Good for accessibility
    • Users can share specific pages

    Infinite Scroll

    Load more items as user scrolls near the bottom.

    function InfiniteScrollList() {
      const [items, setItems] = useState([]);
      const [page, setPage] = useState(1);
      const [hasMore, setHasMore] = useState(true);
      const observerRef = useRef();
      
      const lastItemRef = useCallback(node => {
        if (observerRef.current) observerRef.current.disconnect();
        
        observerRef.current = new IntersectionObserver(entries => {
          if (entries[0].isIntersecting && hasMore) {
            setPage(p => p + 1);
          }
        });
        
        if (node) observerRef.current.observe(node);
      }, [hasMore]);
      
      useEffect(() => {
        fetch(`/api/items?page=${page}`)
          .then(r => r.json())
          .then(newItems => {
            setItems(prev => [...prev, ...newItems]);
            setHasMore(newItems.length > 0);
          });
      }, [page]);
      
      return (
        <div>
          {items.map((item, i) => {
            if (i === items.length - 1) {
              return <div ref={lastItemRef} key={item.id}>{item.name}</div>;
            }
            return <div key={item.id}>{item.name}</div>;
          })}
          {!hasMore && <div>No more items</div>}
        </div>
      );
    }
    

    Best for:

    • Social media feeds
    • News feeds
    • Content discovery
    • Mobile applications
    • When users browse more than search

    Performance:

    • DOM nodes: Grows with scroll
    • Memory: Grows with scroll (can cause issues)
    • Scrolling: Seamless experience

    Tradeoffs:

    • Can't reach footer
    • Hard to return to specific position
    • Poor SEO (content loads dynamically)
    • Growing memory usage
    • Scrollbar becomes meaningless

    Decision Matrix:

    Scenario Best Choice Why
    10,000 row data table Virtual Scrolling Need instant access, constant DOM
    E-commerce catalog Pagination SEO, filtering, stable URLs
    Social feed Infinite Scroll Discovery, engagement
    Logs/Terminal Virtual Scrolling Performance with huge lists
    Search results Pagination Users need to find things
    Chat history Virtual Scrolling Need to jump to any message

    Hybrid Approach (Advanced):

    function HybridList() {
      const [mode, setMode] = useState('infinite'); // or 'paginated'
      
      // Switch based on data size or user preference
      useEffect(() => {
        if (totalItems > 10000) {
          setMode('virtual');
        }
      }, [totalItems]);
      
      if (mode === 'virtual') return <VirtualScrollList />;
      if (mode === 'infinite') return <InfiniteScrollList />;
      return <PaginatedList />;
    }
    

    Question 9: Explain the performance impact of React Context and when to use alternatives

    How Context Works:

    const ThemeContext = createContext();
    
    function App() {
      const [theme, setTheme] = useState('dark');
      
      return (
        <ThemeContext.Provider value={{ theme, setTheme }}>
          <Header />
          <Main />
          <Footer />
        </ThemeContext.Provider>
      );
    }
    
    function Button() {
      const { theme } = useContext(ThemeContext);
      return <button className={theme}>Click me</button>;
    }
    

    The Performance Problem:

    When context value changes, ALL components that use useContext re-render, even if they only use a small part of the value.

    Problem Example:

    function App() {
      const [theme, setTheme] = useState('dark');
      const [user, setUser] = useState(null);
      
      // ❌ This causes ALL consumers to re-render when either changes
      const value = { theme, setTheme, user, setUser };
      
      return (
        <AppContext.Provider value={value}>
          <ThemeButton />  {/* Re-renders on user change */}
          <UserProfile />  {/* Re-renders on theme change */}
        </AppContext.Provider>
      );
    }
    

    Solution 1: Split Contexts

    // ✅ Separate concerns
    const ThemeContext = createContext();
    const UserContext = createContext();
    
    function App() {
      const [theme, setTheme] = useState('dark');
      const [user, setUser] = useState(null);
      
      return (
        <ThemeContext.Provider value={{ theme, setTheme }}>
          <UserContext.Provider value={{ user, setUser }}>
            <ThemeButton />  {/* Only re-renders on theme change */}
            <UserProfile />  {/* Only re-renders on user change */}
          </UserContext.Provider>
        </ThemeContext.Provider>
      );
    }
    

    Solution 2: Memoize Context Value

    function App() {
      const [theme, setTheme] = useState('dark');
      
      // ✅ Prevent unnecessary re-renders of provider
      const value = useMemo(() => ({ theme, setTheme }), [theme]);
      
      return (
        <ThemeContext.Provider value={value}>
          <Children />
        </ThemeContext.Provider>
      );
    }
    

    Solution 3: Use Selectors (Custom Hook)

    // Not preventing re-renders, but making them cheap
    const AppContext = createContext();
    
    function useTheme() {
      const context = useContext(AppContext);
      return context.theme; // Only get what you need
    }
    
    function useUser() {
      const context = useContext(AppContext);
      return context.user;
    }
    
    // Components still re-render, but can be memoized
    const ThemeButton = memo(function ThemeButton() {
      const theme = useTheme();
      return <button className={theme}>Click</button>;
    });
    

    When Context Performance Becomes a Problem:

    1. Context value changes frequently (multiple times per second)
    2. Many components (50+) consume the context
    3. Components consuming context are expensive to render

    Alternatives to Consider:

    1. Component Composition (Best for UI state)

    // Instead of context:
    function App() {
      const [theme, setTheme] = useState('dark');
      
      return (
        <Layout theme={theme}>
          <Header theme={theme} onThemeChange={setTheme} />
          <Content theme={theme} />
        </Layout>
      );
    }
    
    // Use children to avoid prop drilling:
    function App() {
      const [theme, setTheme] = useState('dark');
      
      return (
        <Layout>
          <Header>
            <ThemeButton theme={theme} onChange={setTheme} />
          </Header>
          <Content theme={theme} />
        </Layout>
      );
    }
    

    2. State Management Libraries (For complex state)

    // Zustand - only re-renders components that use changed state
    import create from 'zustand';
    
    const useStore = create((set) => ({
      theme: 'dark',
      user: null,
      setTheme: (theme) => set({ theme }),
      setUser: (user) => set({ user })
    }));
    
    function ThemeButton() {
      // Only subscribes to theme
      const theme = useStore((state) => state.theme);
      const setTheme = useStore((state) => state.setTheme);
      return <button onClick={() => setTheme('light')}>{theme}</button>;
    }
    
    function UserProfile() {
      // Only subscribes to user
      const user = useStore((state) => state.user);
      return <div>{user?.name}</div>;
    }
    

    3. Jotai/Recoil (Atomic state)

    import { atom, useAtom } from 'jotai';
    
    const themeAtom = atom('dark');
    const userAtom = atom(null);
    
    function ThemeButton() {
      const [theme, setTheme] = useAtom(themeAtom);
      // Only re-renders when themeAtom changes
      return <button onClick={() => setTheme('light')}>{theme}</button>;
    }
    

    Performance Comparison:

    Approach Re-renders Boilerplate Use Case
    Context All consumers Low Simple, infrequent updates
    Split Context Only relevant Medium Multiple independent values
    Zustand Only subscribers Low Frequent updates, many consumers
    Jotai Only atom users Medium Fine-grained reactivity
    Composition Only prop recipients None Simple component trees

    When Context is Fine:

    // ✅ Good use of context: Infrequent changes
    const AuthContext = createContext();
    
    function App() {
      const [user, setUser] = useState(null);
      
      // User only changes on login/logout
      return (
        <AuthContext.Provider value={{ user, setUser }}>
          <AppContent />
        </AuthContext.Provider>
      );
    }
    
    // ✅ Good use of context: Theme that rarely changes
    const ThemeContext = createContext();
    

    When to Use Alternatives:

    // ❌ Bad for context: High frequency updates
    const MouseContext = createContext();
    
    function App() {
      const [pos, setPos] = useState({ x: 0, y: 0 });
      
      // Updates on every mouse move!
      useEffect(() => {
        const handler = (e) => setPos({ x: e.clientX, y: e.clientY });
        window.addEventListener('mousemove', handler);
        return () => window.removeEventListener('mousemove', handler);
      }, []);
      
      // This will re-render EVERY consumer on EVERY mouse move
      return (
        <MouseContext.Provider value={pos}>
          <App />
        </MouseContext.Provider>
      );
    }
    
    // ✅ Better: Use Zustand or local state
    

    Part 4: System Design for Frontends

    Question 10: Design a frontend architecture for a real-time collaborative document editor (like Google Docs)

    Requirements Analysis:

    1. Multiple users editing simultaneously
    2. Real-time updates (low latency)
    3. Conflict resolution
    4. Offline support
    5. History/undo functionality
    6. Scalability (large documents, many users)

    High-Level Architecture:

    ┌─────────────┐
    │   Browser   │
    │             │
    │  ┌───────┐  │
    │  │ Editor│  │
    │  └───┬───┘  │
    │      │      │
    │  ┌───▼───┐  │
    │  │ OT/   │  │
    │  │ CRDT  │  │
    │  └───┬───┘  │
    │      │      │
    │  ┌───▼───┐  │
    │  │ Local │  │
    │  │ Store │  │
    │  └───┬───┘  │
    └──────┼──────┘
           │ WebSocket
           │
    ┌──────▼──────┐
    │  WebSocket  │
    │   Gateway   │
    └──────┬──────┘
           │
    ┌──────▼──────┐
    │   Message   │
    │   Broker    │
    │  (Redis)    │
    └──────┬──────┘
           │
    ┌──────▼──────┐
    │  Document   │
    │   Service   │
    └──────┬──────┘
           │
    ┌──────▼──────┐
    │  Database   │
    │ (PostgreSQL)│
    └─────────────┘
    

    Component 1: Editor Layer (Frontend)

    // Document editor component
    function CollaborativeEditor({ documentId }) {
      const [doc, setDoc] = useState(null);
      const [users, setUsers] = useState([]);
      const editorRef = useRef(null);
      const wsRef = useRef(null);
      
      useEffect(() => {
        // Initialize CRDT document
        const yDoc = new Y.Doc();
        const yText = yDoc.getText('content');
        
        // Connect to WebSocket
        const ws = new WebSocket(`wss://api.example.com/doc/${documentId}`);
        wsRef.current = ws;
        
        // Sync provider for real-time updates
        const provider = new WebsocketProvider(
          'wss://api.example.com/doc',
          documentId,
          yDoc
        );
        
        // Handle incoming changes
        yText.observe(event => {
          // Update editor with remote changes
          applyRemoteChanges(event.changes);
        });
        
        // Handle local changes
        editorRef.current.on('change', (delta) => {
          // Convert editor delta to CRDT operations
          applyLocalChanges(yText, delta);
        });
        
        // Awareness for cursor positions
        provider.awareness.on('change', changes => {
          const states = Array.from(provider.awareness.getStates().values());
          setUsers(states);
        });
        
        return () => {
          provider.destroy();
          ws.close();
        };
      }, [documentId]);
      
      return (
        <div className="editor-container">
          <Toolbar />
          <UserCursors users={users} />
          <Editor ref={editorRef} />
        </div>
      );
    }
    

    Component 2: Conflict Resolution (CRDT Implementation)

    // Using Yjs CRDT for conflict-free replicated data
    class DocumentState {
      constructor() {
        this.yDoc = new Y.Doc();
        this.yText = this.yDoc.getText('content');
        this.undoManager = new Y.UndoManager(this.yText);
      }
      
      // Apply local operation
      insert(index, text) {
        this.yText.insert(index, text);
        // CRDT automatically handles conflicts
        // Each character has a unique ID (clientID + clock)
      }
      
      delete(index, length) {
        this.yText.delete(index, length);
      }
      
      // Get current state
      toString() {
        return this.yText.toString();
      }
      
      // Sync with remote
      applyUpdate(update) {
        Y.applyUpdate(this.yDoc, update);
      }
      
      // Get updates to send
      getUpdate() {
        return Y.encodeStateAsUpdate(this.yDoc);
      }
      
      undo() {
        this.undoManager.undo();
      }
      
      redo() {
        this.undoManager.redo();
      }
    }
    

    Alternative: Operational Transform (OT)

    // OT requires server authority but has smaller payloads
    class OTEngine {
      constructor() {
        this.revision = 0;
        this.pendingOps = [];
      }
      
      // Transform operation against concurrent operations
      transform(op1, op2) {
        if (op1.type === 'insert' && op2.type === 'insert') {
          if (op1.position < op2.position) {
            return op2; // No change needed
          } else if (op1.position > op2.position) {
            return { ...op2, position: op2.position + op1.text.length };
          } else {
            // Same position - use client ID to break tie
            return op1.clientId < op2.clientId ? op2 : 
              { ...op2, position: op2.position + op1.text.length };
          }
        }
        // Handle other operation type combinations...
      }
      
      // Apply operation with transformation
      applyOperation(op) {
        // Transform against pending operations
        let transformedOp = op;
        for (const pending of this.pendingOps) {
          transformedOp = this.transform(transformedOp, pending);
        }
        
        // Apply to document
        this.applyToDocument(transformedOp);
        
        return transformedOp;
      }
    }
    

    Component 3: Real-time Sync Layer

    // WebSocket manager with reconnection
    class DocumentSync {
      constructor(documentId, onUpdate) {
        this.documentId = documentId;
        this.onUpdate = onUpdate;
        this.ws = null;
        this.reconnectAttempts = 0;
        this.maxReconnectDelay = 30000;
        this.pendingOperations = [];
        
        this.connect();
      }
      
      connect() {
        this.ws = new WebSocket(
          `wss://api.example.com/doc/${this.documentId}`
        );
        
        this.ws.onopen = () => {
          console.log('Connected');
          this.reconnectAttempts = 0;
          
          // Send any pending operations
          this.flushPendingOperations();
          
          // Request current state
          this.ws.send(JSON.stringify({
            type: 'sync-request',
            revision: this.currentRevision
          }));
        };
        
        this.ws.onmessage = (event) => {
          const message = JSON.parse(event.data);
          
          switch (message.type) {
            case 'update':
              this.handleUpdate(message);
              break;
            case 'sync-response':
              this.handleSyncResponse(message);
              break;
            case 'acknowledgment':
              this.handleAck(message);
              break;
          }
        };
        
        this.ws.onclose = () => {
          console.log('Disconnected');
          this.reconnect();
        };
        
        this.ws.onerror = (error) => {
          console.error('WebSocket error:', error);
        };
      }
      
      reconnect() {
        const delay = Math.min(
          1000 * Math.pow(2, this.reconnectAttempts),
          this.maxReconnectDelay
        );
        
        this.reconnectAttempts++;
        
        setTimeout(() => {
          this.connect();
        }, delay);
      }
      
      sendOperation(operation) {
        const message = {
          type: 'operation',
          documentId: this.documentId,
          operation,
          clientId: this.clientId,
          timestamp: Date.now()
        };
        
        if (this.ws.readyState === WebSocket.OPEN) {
          this.ws.send(JSON.stringify(message));
        } else {
          // Queue for later
          this.pendingOperations.push(message);
        }
      }
      
      flushPendingOperations() {
        while (this.pendingOperations.length > 0) {
          const op = this.pendingOperations.shift();
          this.ws.send(JSON.stringify(op));
        }
      }
      
      handleUpdate(message) {
        this.onUpdate(message.operation);
      }
    }
    

    Component 4: Offline Support

    // IndexedDB for offline storage
    class OfflineStorage {
      constructor(documentId) {
        this.documentId = documentId;
        this.db = null;
        this.init();
      }
      
      async init() {
        return new Promise((resolve, reject) => {
          const request = indexedDB.open('CollabEditor', 1);
          
          request.orupgrade = (event) => {
            const db = event.target.result;
            
            // Store for document content
            if (!db.objectStoreNames.contains('documents')) {
              db.createObjectStore('documents', { keyPath: 'id' });
            }
            
            // Store for pending operations
            if (!db.objectStoreNames.contains('pendingOps')) {
              const store = db.createObjectStore('pendingOps', { 
                keyPath: 'id', 
                autoIncrement: true 
              });
              store.createIndex('documentId', 'documentId', { unique: false });
              store.createIndex('timestamp', 'timestamp', { unique: false });
            }
          };
          
          request.onsuccess = (event) => {
            this.db = event.target.result;
            resolve();
          };
          
          request.onerror = reject;
        });
      }
      
      // Save document state
      async saveDocument(content) {
        const tx = this.db.transaction(['documents'], 'readwrite');
        const store = tx.objectStore('documents');
        
        await store.put({
          id: this.documentId,
          content,
          lastModified: Date.now()
        });
      }
      
      // Load document from cache
      async loadDocument() {
        const tx = this.db.transaction(['documents'], 'readonly');
        const store = tx.objectStore('documents');
        
        return new Promise((resolve, reject) => {
          const request = store.get(this.documentId);
          request.onsuccess = () => resolve(request.result);
          request.onerror = reject;
        });
      }
      
      // Queue operation for later sync
      async queueOperation(operation) {
        const tx = this.db.transaction(['pendingOps'], 'readwrite');
        const store = tx.objectStore('pendingOps');
        
        await store.add({
          documentId: this.documentId,
          operation,
          timestamp: Date.now()
        });
      }
      
      // Get all pending operations
      async getPendingOperations() {
        const tx = this.db.transaction(['pendingOps'], 'readonly');
        const store = tx.objectStore('pendingOps');
        const index = store.index('documentId');
        
        return new Promise((resolve, reject) => {
          const request = index.getAll(this.documentId);
          request.onsuccess = () => resolve(request.result);
          request.onerror = reject;
        });
      }
      
      // Clear synced operations
      async clearPendingOperations() {
        const tx = this.db.transaction(['pendingOps'], 'readwrite');
        const store = tx.objectStore('pendingOps');
        const index = store.index('documentId');
        
        const request = index.openCursor(this.documentId);
        request.onsuccess = (event) => {
          const cursor = event.target.result;
          if (cursor) {
            cursor.delete();
            cursor.continue();
          }
        };
      }
    }
    

    Component 5: Presence & Awareness

    // Track cursor positions and user presence
    class AwarenessManager {
      constructor(provider) {
        this.provider = provider;
        this.awareness = provider.awareness;
        this.localState = {
          user: null,
          cursor: null,
          selection: null,
          color: this.generateColor()
        };
      }
      
      setUser(user) {
        this.localState.user = user;
        this.updateAwareness();
      }
      
      updateCursor(position) {
        this.localState.cursor = position;
        this.updateAwareness();
      }
      
      updateSelection(range) {
        this.localState.selection = range;
        this.updateAwareness();
      }
      
      updateAwareness() {
        this.awareness.setLocalState(this.localState);
      }
      
      // Get all remote user states
      getRemoteStates() {
        const states = [];
        this.awareness.getStates().forEach((state, clientId) => {
          if (clientId !== this.awareness.clientID) {
            states.push({ ...state, clientId });
          }
        });
        return states;
      }
      
      generateColor() {
        const colors = [
          '#FF6B6B', '#4ECDC4', '#45B7D1', 
          '#FFA07A', '#98D8C8', '#F7DC6F'
        ];
        return colors[Math.floor(Math.random() * colors.length)];
      }
    }
    
    // Render remote cursors
    function RemoteCursors({ awareness }) {
      const [cursors, setCursors] = useState([]);
      
      useEffect(() => {
        const updateCursors = () => {
          const states = Array.from(awareness.getStates().values())
            .filter(state => state.cursor !== null);
          setCursors(states);
        };
        
        awareness.on('change', updateCursors);
        updateCursors();
        
        return () => awareness.off('change', updateCursors);
      }, [awareness]);
      
      return (
        <>
          {cursors.map(state => (
            <Cursor
              key={state.clientId}
              position={state.cursor}
              color={state.color}
              user={state.user}
            />
          ))}
        </>
      );
    }
    

    Component 6: Performance Optimizations

    // Virtual scrolling for large documents
    function LargeDocumentEditor({ doc }) {
      const [visibleRange, setVisibleRange] = useState({ start: 0, end: 100 });
      const containerRef = useRef();
      
      // Only render visible lines
      const visibleLines = useMemo(() => {
        return doc.lines.slice(visibleRange.start, visibleRange.end);
      }, [doc, visibleRange]);
      
      // Update visible range on scroll
      useEffect(() => {
        const container = containerRef.current;
        
        const handleScroll = throttle(() => {
          const scrollTop = container.scrollTop;
          const lineHeight = 20; // pixels
          const viewportHeight = container.clientHeight;
          
          const start = Math.floor(scrollTop / lineHeight) - 10; // buffer
          const end = Math.ceil((scrollTop + viewportHeight) / lineHeight) + 10;
          
          setVisibleRange({
            start: Math.max(0, start),
            end: Math.min(doc.lines.length, end)
          });
        }, 100);
        
        container.addEventListener('scroll', handleScroll);
        return () => container.removeEventListener('scroll', handleScroll);
      }, [doc.lines.length]);
      
      return (
        <div ref={containerRef} style={{ height: '100vh', overflow: 'auto' }}>
          <div style={{ height: doc.lines.length * 20 }}>
            <div style={{ transform: `translateY(${visibleRange.start * 20}px)` }}>
              {visibleLines.map((line, i) => (
                <Line key={visibleRange.start + i} content={line} />
              ))}
            </div>
          </div>
        </div>
      );
    }
    
    // Debounce sync operations
    function useDebouncedSync(syncFn, delay = 300) {
      const timeoutRef = useRef();
      const pendingOpsRef = useRef([]);
      
      return useCallback((operation) => {
        pendingOpsRef.current.push(operation);
        
        clearTimeout(timeoutRef.current);
        
        timeoutRef.current = setTimeout(() => {
          if (pendingOpsRef.current.length > 0) {
            syncFn(pendingOpsRef.current);
            pendingOpsRef.current = [];
          }
        }, delay);
      }, [syncFn, delay]);
    }
    

    Backend Architecture (Brief Overview):

    // WebSocket server (Node.js)
    const WebSocket = require('ws');
    const Redis = require('redis');
    
    class CollaborationServer {
      constructor() {
        this.wss = new WebSocket.Server({ port: 8080 });
        this.redis = Redis.createClient();
        this.rooms = new Map(); // documentId -> Set of clients
        
        this.wss.on('connection', (ws, req) => {
          this.handleConnection(ws, req);
        });
      }
      
      handleConnection(ws, req) {
        const documentId = this.extractDocumentId(req.url);
        
        // Add client to room
        if (!this.rooms.has(documentId)) {
          this.rooms.set(documentId, new Set());
        }
        this.rooms.get(documentId).add(ws);
        
        // Handle messages
        ws.on('message', async (data) => {
          const message = JSON.parse(data);
          
          switch (message.type) {
            case 'operation':
              await this.handleOperation(documentId, message, ws);
              break;
            case 'sync-request':
              await this.handleSyncRequest(documentId, message, ws);
              break;
          }
        });
        
        // Cleanup on disconnect
        ws.on('close', () => {
          this.rooms.get(documentId).delete(ws);
        });
      }
      
      async handleOperation(documentId, message, sender) {
        // Save to Redis for persistence
        await this.redis.lpush(
          `doc:${documentId}:ops`,
          JSON.stringify(message.operation)
        );
        
        // Broadcast to all clients in room except sender
        const clients = this.rooms.get(documentId);
        clients.forEach(client => {
          if (client !== sender && client.readyState === WebSocket.OPEN) {
            client.send(JSON.stringify({
              type: 'update',
              operation: message.operation
            }));
          }
        });
        
        // Send acknowledgment
        sender.send(JSON.stringify({
          type: 'acknowledgment',
          operationId: message.operation.id
        }));
      }
      
      async handleSyncRequest(documentId, message, client) {
        // Get operations since client's revision
        const ops = await this.redis.lrange(
          `doc:${documentId}:ops`,
          message.revision,
          -1
        );
        
        client.send(JSON.stringify({
          type: 'sync-response',
          operations: ops.map(op => JSON.parse(op))
        }));
      }
    }
    

    Scalability Considerations:

    1. Sharding by Document: Different documents on different servers
    2. Redis Pub/Sub: For broadcasting across multiple server instances
    3. Operation Compaction: Periodically compress operation history
    4. Snapshot Storage: Save full document state every N operations
    // Load balancing with sticky sessions
    // nginx config
    upstream collaboration_servers {
      ip_hash; # Sticky sessions based on IP
      server server1:8080;
      server server2:8080;
      server server3:8080;
    }
    
    // Or use Redis for session sharing
    class DistributedCollaboration {
      constructor() {
        this.pubsub = Redis.createClient();
        this.subscriber = Redis.createClient();
        
        // Subscribe to document channels
        this.subscriber.on('message', (channel, message) => {
          const documentId = channel.replace('doc:', '');
          this.broadcastToLocalClients(documentId, message);
        });
      }
      
      async handleOperation(documentId, operation) {
        // Publish to Redis for other servers
        await this.pubsub.publish(
          `doc:${documentId}`,
          JSON.stringify(operation)
        );
        
        // Also broadcast to local clients
        this.broadcastToLocalClients(documentId, operation);
      }
    }
    

    Key Design Decisions:

    1. CRDT vs OT: CRDT chosen for:

      • No server authority needed
      • Better offline support
      • Simpler conflict resolution
      • Trade-off: Larger payload size
    2. WebSocket vs HTTP polling:

      • WebSocket for real-time, bi-directional
      • Fallback to long-polling for old browsers
    3. Operation-based vs State-based sync:

      • Operation-based for efficiency
      • Periodic state snapshots for recovery
    4. Client-side vs Server-side rendering:

      • Client-side for editor (needs interactivity)
      • Server-side for initial load and SEO

    Question 11: How would you design a frontend monitoring and observability system?

    Requirements:

    1. Performance metrics (Core Web Vitals)
    2. Error tracking and reporting
    3. User session replay
    4. Real-time alerts
    5. Custom business metrics
    6. Privacy compliance

    Architecture Overview:

    // Core monitoring SDK
    class FrontendMonitor {
      constructor(config) {
        this.config = config;
        this.sessionId = this.generateSessionId();
        this.userId = null;
        this.queue = [];
        this.flushInterval = 10000; // 10 seconds
        
        this.initializeMonitoring();
      }
      
      initializeMonitoring() {
        this.trackPerformance();
        this.trackErrors();
        this.trackUserInteractions();
        this.trackNetworkRequests();
        
        // Flush queue periodically
        setInterval(() => this.flush(), this.flushInterval);
        
        // Flush on page unload
        window.addEventListener('beforeunload', () => this.flush(true));
      }
      
      // 1. Performance Monitoring
      trackPerformance() {
        // Core Web Vitals
        this.trackLCP(); // Largest Contentful Paint
        this.trackFID(); // First Input Delay
        this.trackCLS(); // Cumulative Layout Shift
        this.trackFCP(); // First Contentful Paint
        this.trackTTFB(); // Time to First Byte
      }
      
      trackLCP() {
        const observer = new PerformanceObserver((list) => {
          const entries = list.getEntries();
          const lastEntry = entries[entries.length - 1];
          
          this.record({
            type: 'performance',
            metric: 'LCP',
            value: lastEntry.renderTime || lastEntry.loadTime,
            timestamp: Date.now(),
            url: window.location.href,
            sessionId: this.sessionId
          });
        });
        
        observer.observe({ entryTypes: ['largest-contentful-paint'] });
      }
      
      trackFID() {
        const observer = new PerformanceObserver((list) => {
          const entry = list.getEntries()[0];
          
          this.record({
            type: 'performance',
            metric: 'FID',
            value: entry.processingStart - entry.startTime,
            timestamp: Date.now(),
            sessionId: this.sessionId
          });
        });
        
        observer.observe({ entryTypes: ['first-input'] });
      }
      
      trackCLS() {
        let clsScore = 0;
        
        const observer = new PerformanceObserver((list) => {
          for (const entry of list.getEntries()) {
            if (!entry.hadRecentInput) {
              clsScore += entry.value;
            }
          }
          
          this.record({
            type: 'performance',
            metric: 'CLS',
            value: clsScore,
            timestamp: Date.now(),
            sessionId: this.sessionId
          });
        });
        
        observer.observe({ entryTypes: ['layout-shift'] });
      }
      
      // Navigation timing
      trackPageLoad() {
        window.addEventListener('load', () => {
          setTimeout(() => {
            const timing = performance.getEntriesByType('navigation')[0];
            
            this.record({
              type: 'performance',
              metric: 'page-load',
              data: {
                dns: timing.domainLookupEnd - timing.domainLookupStart,
                tcp: timing.connectEnd - timing.connectStart,
                ttfb: timing.responseStart - timing.requestStart,
                download: timing.responseEnd - timing.responseStart,
                domInteractive: timing.domInteractive - timing.fetchStart,
                domComplete: timing.domComplete - timing.fetchStart,
                loadComplete: timing.loadEventEnd - timing.fetchStart
              },
              timestamp: Date.now(),
              sessionId: this.sessionId
            });
          }, 0);
        });
      }
      
      // 2. Error Tracking
      trackErrors() {
        // JavaScript errors
        window.addEventListener('error', (event) => {
          this.record({
            type: 'error',
            subtype: 'javascript',
            message: event.message,
            stack: event.error?.stack,
            filename: event.filename,
            lineno: event.lineno,
            colno: event.colno,
            timestamp: Date.now(),
            sessionId: this.sessionId,
            userId: this.userId,
            url: window.location.href,
            userAgent: navigator.userAgent
          });
        });
        
        // Unhandled promise rejections
        window.addEventListener('unhandledrejection', (event) => {
          this.record({
            type: 'error',
            subtype: 'unhandled-rejection',
            message: event.reason?.message || String(event.reason),
            stack: event.reason?.stack,
            timestamp: Date.now(),
            sessionId: this.sessionId
          });
        });
        
        // React error boundaries (if using React)
        this.setupReactErrorBoundary();
      }
      
      setupReactErrorBoundary() {
        // Hook into React error boundary
        if (window.React) {
          const originalError = console.error;
          console.error = (...args) => {
            if (args[0]?.includes?.('React')) {
              this.record({
                type: 'error',
                subtype: 'react',
                message: args.join(' '),
                timestamp: Date.now(),
                sessionId: this.sessionId
              });
            }
            originalError.apply(console, args);
          };
        }
      }
      
      // 3. User Interaction Tracking
      trackUserInteractions() {
        // Click tracking
        document.addEventListener('click', (event) => {
          const target = event.target;
          
          this.record({
            type: 'interaction',
            subtype: 'click',
            element: this.getElementIdentifier(target),
            x: event.clientX,
            y: event.clientY,
            timestamp: Date.now(),
            sessionId: this.sessionId
          });
        }, true);
        
        // Form submissions
        document.addEventListener('submit', (event) => {
          const form = event.target;
          
          this.record({
            type: 'interaction',
            subtype: 'form-submit',
            formId: form.id || form.name,
            action: form.action,
            timestamp: Date.now(),
            sessionId: this.sessionId
          });
        }, true);
        
        // Page visibility
        document.addEventListener('visibilitychange', () => {
          this.record({
            type: 'interaction',
            subtype: 'visibility',
            hidden: document.hidden,
            timestamp: Date.now(),
            sessionId: this.sessionId
          });
        });
      }
      
      // 4. Network Request Tracking
      trackNetworkRequests() {
        const observer = new PerformanceObserver((list) => {
          for (const entry of list.getEntries()) {
            if (entry.initiatorType === 'fetch' || entry.initiatorType === 'xmlhttprequest') {
              this.record({
                type: 'network',
                url: entry.name,
                method: entry.initiatorType,
                duration: entry.duration,
                transferSize: entry.transferSize,
                encodedBodySize: entry.encodedBodySize,
                decodedBodySize: entry.decodedBodySize,
                timestamp: Date.now(),
                sessionId: this.sessionId
              });
            }
          }
        });
        
        observer.observe({ entryTypes: ['resource'] });
        
        // Intercept fetch
        const originalFetch = window.fetch;
        window.fetch = async (...args) => {
          const startTime = performance.now();
          
          try {
            const response = await originalFetch(...args);
            const duration = performance.now() - startTime;
            
            this.record({
              type: 'api-call',
              url: args[0],
              method: args[1]?.method || 'GET',
              status: response.status,
              duration,
              timestamp: Date.now(),
              sessionId: this.sessionId
            });
            
            return response;
          } catch (error) {
            const duration = performance.now() - startTime;
            
            this.record({
              type: 'api-error',
              url: args[0],
              method: args[1]?.method || 'GET',
              error: error.message,
              duration,
              timestamp: Date.now(),
              sessionId: this.sessionId
            });
            
            throw error;
          }
        };
      }
      
      // 5. Custom Metrics
      recordCustomMetric(name, value, metadata = {}) {
        this.record({
          type: 'custom-metric',
          name,
          value,
          metadata,
          timestamp: Date.now(),
          sessionId: this.sessionId,
          userId: this.userId
        });
      }
      
      // 6. Session Replay (Simplified)
      startSessionReplay() {
        const events = [];
        
        // Capture DOM mutations
        const observer = new MutationObserver((mutations) => {
          const simplified = mutations.map(m => ({
            type: m.type,
            target: this.getElementIdentifier(m.target),
            timestamp: Date.now()
          }));
          
          events.push(...simplified);
          
          // Limit size
          if (events.length > 1000) {
            this.sendReplayChunk(events.splice(0, 500));
          }
        });
        
        observer.observe(document.body, {
          childList: true,
          subtree: true,
          attributes: true,
          attributeOldValue: false,
          characterData: true
        });
        
        // Capture initial state
        this.record({
          type: 'replay-start',
          html: this.sanitizeHTML(document.documentElement.outerHTML),
          viewport: {
            width: window.innerWidth,
            height: window.innerHeight
          },
          sessionId: this.sessionId,
          timestamp: Date.now()
        });
      }
      
      // Helper methods
      getElementIdentifier(element) {
        if (element.id) return `#${element.id}`;
        if (element.className) return `.${element.className.split(' ')[0]}`;
        return element.tagName.toLowerCase();
      }
      
      sanitizeHTML(html) {
        // Remove sensitive data (passwords, tokens, etc.)
        return html
          .replace(/password="[^"]*"/g, 'password="***"')
          .replace(/token="[^"]*"/g, 'token="***"')
          .replace(/<input[^>]*type="password"[^>]*>/g, '<input type="password" value="***">');
      }
      
      generateSessionId() {
        return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
      }
      
      record(data) {
        // Add common fields
        const enriched = {
          ...data,
          projectId: this.config.projectId,
          environment: this.config.environment,
          version: this.config.version,
          page: {
            url: window.location.href,
            referrer: document.referrer,
            title: document.title
          },
          device: {
            userAgent: navigator.userAgent,
            viewport: {
              width: window.innerWidth,
              height: window.innerHeight
            },
            connection: navigator.connection?.effectiveType
          }
        };
        
        this.queue.push(enriched);
        
        // Immediate flush for critical events
        if (data.type === 'error' || data.type === 'api-error') {
          this.flush();
        }
      }
      
      async flush(sync = false) {
        if (this.queue.length === 0) return;
        
        const batch = this.queue.splice(0, this.queue.length);
        
        const payload = {
          batch,
          timestamp: Date.now(),
          sessionId: this.sessionId
        };
        
        if (sync) {
          // Use sendBeacon for reliable delivery on page unload
          navigator.sendBeacon(
            this.config.endpoint,
            JSON.stringify(payload)
          );
        } else {
          try {
            await fetch(this.config.endpoint, {
              method: 'POST',
              headers: { 'Content-Type': 'application/json' },
              body: JSON.stringify(payload),
              keepalive: true
            });
          } catch (error) {
            // Failed to send - could retry or log locally
            console.error('Failed to send monitoring data:', error);
          }
        }
      }
      
      // Public API
      setUserId(userId) {
        this.userId = userId;
      }
      
      addContext(key, value) {
        this.record({
          type: 'context',
          key,
          value,
          timestamp: Date.now()
        });
      }
    }
    
    // Initialize monitoring
    const monitor = new FrontendMonitor({
      projectId: 'my-app',
      environment: 'production',
      version: '1.2.3',
      endpoint: 'https://monitoring.example.com/ingest'
    });
    

    React Integration:

    // Error Boundary with monitoring
    class MonitoredErrorBoundary extends React.Component {
      constructor(props) {
        super(props);
        this.state = { hasError: false, error: null };
      }
      
      static getDerivedStateFromError(error) {
        return { hasError: true, error };
      }
      
      componentDidCatch(error, errorInfo) {
        // Send to monitoring system
        monitor.record({
          type: 'error',
          subtype: 'react-boundary',
          message: error.message,
          stack: error.stack,
          componentStack: errorInfo.componentStack,
          timestamp: Date.now()
        });
      }
      
      render() {
        if (this.state.hasError) {
          return (
            <div>
              <h1>Something went wrong</h1>
              <button onClick={() => this.setState({ hasError: false })}>
                Try again
              </button>
            </div>
          );
        }
        
        return this.props.children;
      }
    }
    
    // Custom hook for tracking
    function useMonitoring() {
      return {
        trackEvent: (name, properties) => {
          monitor.recordCustomMetric(name, properties);
        },
        
        trackTiming: (name, duration) => {
          monitor.recordCustomMetric(`timing.${name}`, duration);
        },
        
        setUser: (userId) => {
          monitor.setUserId(userId);
        }
      };
    }
    
    // Track component render performance
    function useRenderTracking(componentName) {
      useEffect(() => {
        const startTime = performance.now();
        
        return () => {
          const duration = performance.now() - startTime;
          monitor.recordCustomMetric('component-render', duration, {
            component: componentName
          });
        };
      });
    }
    

    Backend Processing Pipeline:

    // Ingest service (Node.js)
    class MonitoringIngestService {
      constructor() {
        this.kafka = new Kafka({
          brokers: ['kafka:9092']
        });
        this.producer = this.kafka.producer();
      }
      
      async handleIngest(req, res) {
        const { batch } = req.body;
        
        // Validate and enrich
        const enriched = batch.map(event => ({
          ...event,
          receivedAt: Date.now(),
          ip: req.ip,
          geo: this.lookupGeo(req.ip)
        }));
        
        // Send to Kafka for processing
        await this.producer.send({
          topic: 'frontend-events',
          messages: enriched.map(event => ({
            key: event.sessionId,
            value: JSON.stringify(event)
          }))
        });
        
        res.status(200).json({ success: true });
      }
      
      lookupGeo(ip) {
        // GeoIP lookup
        return { country: 'US', city: 'New York' };
      }
    }
    
    // Processing worker
    class EventProcessor {
      constructor() {
        this.kafka = new Kafka({ brokers: ['kafka:9092'] });
        this.consumer = this.kafka.consumer({ groupId: 'event-processor' });
        this.timescale = new TimescaleDB();
        this.redis = new Redis();
      }
      
      async start() {
        await this.consumer.subscribe({ topic: 'frontend-events' });
        
        await this.consumer.run({
          eachMessage: async ({ message }) => {
            const event = JSON.parse(message.value);
            
            // Route by type
            switch (event.type) {
              case 'error':
                await this.processError(event);
                break;
              case 'performance':
                await this.processPerformance(event);
                break;
              case 'interaction':
                await this.processInteraction(event);
                break;
            }
          }
        });
      }
      
      async processError(event) {
        // Store in TimescaleDB
        await this.timescale.query(`
          INSERT INTO errors (
            session_id, user_id, message, stack, url, timestamp
          ) VALUES ($1, $2, $3, $4, $5, $6)
        `, [
          event.sessionId,
          event.userId,
          event.message,
          event.stack,
          event.url,
          event.timestamp
        ]);
        
        // Check for alerts
        const errorCount = await this.redis.incr(
          `errors:${event.message}:${Date.now() / 60000}`
        );
        
        if (errorCount > 10) {
          await this.sendAlert({
            type: 'error-spike',
            message: `Error spike detected: ${event.message}`,
            count: errorCount
          });
        }
        
        // Group similar errors
        const errorHash = this.hashError(event);
        await this.redis.hincrby('error-groups', errorHash, 1);
      }
      
      async processPerformance(event) {
        // Store metric
        await this.timescale.query(`
          INSERT INTO performance_metrics (
            session_id, metric, value, url, timestamp
          ) VALUES ($1, $2, $3, $4, $5)
        `, [
          event.sessionId,
          event.metric,
          event.value,
          event.url,
          event.timestamp
        ]);
        
        // Update real-time aggregates
        await this.redis.zadd(
          `perf:${event.metric}:recent`,
          event.timestamp,
          JSON.stringify({ value: event.value, url: event.url })
        );
        
        // Check thresholds
        if (event.metric === 'LCP' && event.value > 2500) {
          await this.sendAlert({
            type: 'performance-degradation',
            metric: 'LCP',
            value: event.value,
            threshold: 2500,
            url: event.url
          });
        }
      }
      
      hashError(event) {
        const crypto = require('crypto');
        return crypto
          .createHash('md5')
          .update(event.message + event.stack?.split('\n')[0])
          .digest('hex');
      }
      
      async sendAlert(alert) {
        // Send to alerting system (PagerDuty, Slack, etc.)
        await fetch('https://hooks.slack.com/services/XXX', {
          method: 'POST',
          body: JSON.stringify({
            text: `⚠️ ${alert.message}`
          })
        });
      }
    }
    

    Dashboard & Query API:

    // Query service
    class MonitoringQueryService {
      constructor() {
        this.timescale = new TimescaleDB();
      }
      
      // Get performance metrics over time
      async getPerformanceMetrics(metric, startTime, endTime) {
        const results = await this.timescale.query(`
          SELECT 
            time_bucket('5 minutes', timestamp) AS bucket,
            percentile_cont(0.5) WITHIN GROUP (ORDER BY value) AS p50,
            percentile_cont(0.75) WITHIN GROUP (ORDER BY value) AS p75,
            percentile_cont(0.95) WITHIN GROUP (ORDER BY value) AS p95,
            percentile_cont(0.99) WITHIN GROUP (ORDER BY value) AS p99
          FROM performance_metrics
          WHERE metric = $1
            AND timestamp >= $2
            AND timestamp <= $3
          GROUP BY bucket
          ORDER BY bucket
        `, [metric, startTime, endTime]);
        
        return results.rows;
      }
      
      // Get error rate
      async getErrorRate(startTime, endTime) {
        const results = await this.timescale.query(`
          SELECT 
            time_bucket('5 minutes', timestamp) AS bucket,
            COUNT(*) as error_count
          FROM errors
          WHERE timestamp >= $1 AND timestamp <= $2
          GROUP BY bucket
          ORDER BY bucket
        `, [startTime, endTime]);
        
        return results.rows;
      }
      
      // Get top errors
      async getTopErrors(limit = 10) {
        const results = await this.timescale.query(`
          SELECT 
            message,
            COUNT(*) as count,
            COUNT(DISTINCT session_id) as affected_sessions,
            MAX(timestamp) as last_seen
          FROM errors
          WHERE timestamp > NOW() - INTERVAL '24 hours'
          GROUP BY message
          ORDER BY count DESC
          LIMIT $1
        `, [limit]);
        
        return results.rows;
      }
      
      // User journey analysis
      async getUserJourney(sessionId) {
        const results = await this.timescale.query(`
          SELECT type, subtype, url, timestamp, data
          FROM events
          WHERE session_id = $1
          ORDER BY timestamp
        `, [sessionId]);
        
        return results.rows;
      }
    }
    

    Privacy & Compliance:

    // Data sanitization for GDPR/CCPA
    class PrivacyManager {
      sanitizeEvent(event) {
        // Remove PII
        const sanitized = { ...event };
        
        // Mask email addresses
        if (sanitized.message) {
          sanitized.message = sanitized.message.replace(
            /[\w.-]+@[\w.-]+\.\w+/g,
            '***@***.***'
          );
        }
        
        // Remove query parameters with sensitive data
        if (sanitized.url) {
          const url = new URL(sanitized.url);
          const sensitiveParams = ['token', 'key', 'password', 'email'];
          
          sensitiveParams.forEach(param => {
            if (url.searchParams.has(param)) {
              url.searchParams.set(param, '***');
            }
          });
          
          sanitized.url = url.toString();
        }
        
        // Hash IP addresses
        if (sanitized.ip) {
          sanitized.ip = this.hashIP(sanitized.ip);
        }
        
        return sanitized;
      }
      
      hashIP(ip) {
        const crypto = require('crypto');
        return crypto.createHash('sha256').update(ip).digest('hex').substr(0, 16);
      }
      
      // GDPR: Delete user data
      async deleteUserData(userId) {
        await this.timescale.query(`
          DELETE FROM events WHERE user_id = $1
        `, [userId]);
        
        await this.timescale.query(`
          DELETE FROM errors WHERE user_id = $1
        `, [userId]);
      }
      
      // GDPR: Export user data
      async exportUserData(userId) {
        const events = await this.timescale.query(`
          SELECT * FROM events WHERE user_id = $1
        `, [userId]);
        
        return events.rows;
      }
    }
    

    Key Design Decisions:

    1. Client-side sampling: Only send 10% of performance metrics to reduce load
    2. Batching: Group events to reduce network requests
    3. Priority queuing: Errors flush immediately, metrics batch
    4. Privacy-first: Sanitize before sending, hash IPs, mask PII
    5. Time-series DB: TimescaleDB for efficient metric storage
    6. Real-time processing: Kafka for stream processing
    7. Retention policies: 90 days raw data, 1 year aggregates

    Question 12: Design a micro-frontend architecture for a large e-commerce platform

    Requirements:

    1. Multiple teams working independently
    2. Different tech stacks per team
    3. Shared design system
    4. Performance (no massive bundle)
    5. Gradual migration from monolith
    6. SEO-friendly

    Architecture Patterns:

    Pattern 1: Build-Time Integration (Not Recommended)

    // ❌ Compile-time integration - loses team autonomy
    import ProductList from '@team-catalog/product-list';
    import Checkout from '@team-checkout/checkout';
    
    function App() {
      return (
        <>
          <ProductList />
          <Checkout />
        </>
      );
    }
    

    Problems:

    • All teams must use same framework
    • Coordinated deployments
    • Version conflicts

    Pattern 2: Run-Time Integration via Module Federation (Recommended)

    // Webpack Module Federation
    // Host application (shell)
    module.exports = {
      plugins: [
        new ModuleFederationPlugin({
          name: 'host',
          remotes: {
            productCatalog: 'productCatalog@http://localhost:3001/remoteEntry.js',
            checkout: 'checkout@http://localhost:3002/remoteEntry.js',
            userProfile: 'userProfile@http://localhost:3003/remoteEntry.js'
          },
          shared: {
            react: { singleton: true, requiredVersion: '^18.0.0' },
            'react-dom': { singleton: true, requiredVersion: '^18.0.0' }
          }
        })
      ]
    };
    
    // Shell application
    import React, { lazy, Suspense } from 'react';
    
    const ProductCatalog = lazy(() => import('productCatalog/ProductList'));
    const Checkout = lazy(() => import('checkout/CheckoutFlow'));
    const UserProfile = lazy(() => import('userProfile/ProfilePage'));
    
    function App() {
      return (
        <BrowserRouter>
          <Layout>
            <Suspense fallback={<Spinner />}>
              <Routes>
                <Route path="/products" element={<ProductCatalog />} />
                <Route path="/checkout" element={<Checkout />} />
                <Route path="/profile" element={<UserProfile />} />
              </Routes>
            </Suspense>
          </Layout>
        </BrowserRouter>
      );
    }
    
    // Remote application (Product Catalog)
    module.exports = {
      plugins: [
        new ModuleFederationPlugin({
          name: 'productCatalog',
          filename: 'remoteEntry.js',
          exposes: {
            './ProductList': './src/components/ProductList',
            './ProductDetail': './src/components/ProductDetail'
          },
          shared: {
            react: { singleton: true },
            'react-dom': { singleton: true }
          }
        })
      ]
    };
    

    Benefits:

    • Independent deployments
    • Load on demand
    • Share dependencies
    • Can use same framework

    Drawbacks:

    • Still tied to webpack
    • Shared dependencies must match versions
    • Complex configuration

    Pattern 3: Web Components (Framework Agnostic)

    // Product Catalog (React)
    import React from 'react';
    import ReactDOM from 'react-dom/client';
    
    class ProductCatalogElement extends HTMLElement {
      connectedCallback() {
        const mountPoint = document.createElement('div');
        this.attachShadow({ mode: 'open' }).appendChild(mountPoint);
        
        const root = ReactDOM.createRoot(mountPoint);
        root.render(<ProductCatalog 
          apiUrl={this.getAttribute('api-url')}
          onProductClick={this.handleProductClick}
        />);
      }
      
      handleProductClick = (productId) => {
        this.dispatchEvent(new CustomEvent('product-selected', {
          detail: { productId },
          bubbles: true
        }));
      };
    }
    
    customElements.define('product-catalog', ProductCatalogElement);
    
    // Checkout (Vue)
    import { createApp } from 'vue';
    import CheckoutApp from './Checkout.vue';
    
    class CheckoutElement extends HTMLElement {
      connectedCallback() {
        const mountPoint = document.createElement('div');
        this.attachShadow({ mode: 'open' }).appendChild(mountPoint);
        
        createApp(CheckoutApp, {
          cartId: this.getAttribute('cart-id')
        }).mount(mountPoint);
      }
    }
    
    customElements.define('checkout-flow', CheckoutElement);
    
    // Host application (any framework or vanilla)
    <html>
      <body>
        <div id="app">
          <navigation-bar></navigation-bar>
          
          <product-catalog 
            api-url="https://api.example.com/products">
          </product-catalog>
          
          <checkout-flow 
            cart-id="abc123">
          </checkout-flow>
        </div>
        
        <script src="https://cdn.example.com/product-catalog/1.2.3/bundle.js"></script>
        <script src="https://cdn.example.com/checkout/2.0.1/bundle.js"></script>
      </body>
    </html>
    

    Benefits:

    • True framework independence
    • Shadow DOM encapsulation
    • Standard web platform

    Drawbacks:

    • No shared React context
    • Styling isolation (good and bad)
    • Each micro-frontend loads full framework

    Pattern 4: iFrame (Simple but Limited)

    // Host
    function App() {
      return (
        <div>
          <iframe 
            src="https://products.example.com"
            style={{ border: 'none', width: '100%', height: '600px' }}
            sandbox="allow-scripts allow-same-origin"
          />
        </div>
      );
    }
    
    // Communication via postMessage
    window.parent.postMessage({
      type: 'product-selected',
      productId: '123'
    }, '*');
    

    Benefits:

    • Complete isolation
    • Simple to implement
    • Zero version conflicts

    Drawbacks:

    • Performance overhead
    • SEO challenges
    • Complex communication
    • Awkward routing

    Recommended Hybrid Architecture:

    ┌─────────────────────────────────────────────┐
    │          Shell Application (Host)           │
    │  - Routing                                  │
    │  - Authentication                           │
    │  - Shared UI components                     │
    │  - Global state management                  │
    └─────────────────┬───────────────────────────┘
                      │
            ┌─────────┼─────────┐
            │         │         │
    ┌───────▼──┐ ┌───▼─────┐ ┌─▼────────┐
    │ Product  │ │Checkout │ │  User    │
    │ Catalog  │ │  Flow   │ │ Profile  │
    │ (React)  │ │  (Vue)  │ │ (Svelte) │
    └──────────┘ └─────────┘ └──────────┘
    

    Implementation:

    // 1. Shell Application
    import { createBrowserRouter } from 'react-router-dom';
    
    const router = createBrowserRouter([
      {
        path: '/',
        element: <RootLayout />,
        children: [
          {
            path: 'products/*',
            lazy: () => loadMicroFrontend('productCatalog', '/remoteEntry.js')
          },
          {
            path: 'checkout/*',
            lazy: () => loadMicroFrontend('checkout', '/remoteEntry.js')
          },
          {
            path: 'profile/*',
            lazy: () => loadMicroFrontend('userProfile', '/remoteEntry.js')
          }
        ]
      }
    ]);
    
    // Dynamic micro-frontend loader
    async function loadMicroFrontend(name, entry) {
      // Load remote entry
      await loadScript(`https://${name}.example.com${entry}`);
      
      // Get the container
      const container = window[name];
      await container.init(__webpack_share_scopes__.default);
      
      // Get the module
      const factory = await container.get('./App');
      const Module = factory();
      
      return {
        Component: Module.default
      };
    }
    
    // 2. Shared Event Bus
    class MicroFrontendEventBus {
      constructor() {
        this.listeners = new Map();
      }
      
      on(event, callback) {
        if (!this.listeners.has(event)) {
          this.listeners.set(event, []);
        }
        this.listeners.get(event).push(callback);
      }
      
      off(event, callback) {
        const callbacks = this.listeners.get(event);
        if (callbacks) {
          const index = callbacks.indexOf(callback);
          if (index > -1) {
            callbacks.splice(index, 1);
          }
        }
      }
      
      emit(event, data) {
        const callbacks = this.listeners.get(event);
        if (callbacks) {
          callbacks.forEach(callback => callback(data));
        }
      }
    }
    
    // Global event bus
    window.microFrontendBus = new MicroFrontendEventBus();
    
    // Usage in micro-frontends
    // Product Catalog
    window.microFrontendBus.emit('product:added-to-cart', {
      productId: '123',
      quantity: 1
    });
    
    // Checkout
    window.microFrontendBus.on('product:added-to-cart', (data) => {
      updateCart(data);
    });
    
    // 3. Shared State Management
    class SharedStateManager {
      constructor() {
        this.state = {};
        this.subscribers = new Map();
      }
      
      setState(key, value) {
        this.state[key] = value;
        this.notify(key, value);
      }
      
      getState(key) {
        return this.state[key];
      }
      
      subscribe(key, callback) {
        if (!this.subscribers.has(key)) {
          this.subscribers.set(key, []);
        }
        this.subscribers.get(key).push(callback);
        
        // Return unsubscribe function
        return () => {
          const callbacks = this.subscribers.get(key);
          const index = callbacks.indexOf(callback);
          if (index > -1) {
            callbacks.splice(index, 1);
          }
        };
      }
      
      notify(key, value) {
        const callbacks = this.subscribers.get(key);
        if (callbacks) {
          callbacks.forEach(callback => callback(value));
        }
      }
    }
    
    window.sharedState = new SharedStateManager();
    
    // Usage
    // Set user in shell
    window.sharedState.setState('user', { id: '123', name: 'John' });
    
    // Subscribe in micro-frontend
    const unsubscribe = window.sharedState.subscribe('user', (user) => {
      console.log('User updated:', user);
    });
    
    // 4. Shared Design System
    // Published as npm package and imported by all micro-frontends
    import { Button, Input, Card } from '@company/design-system';
    
    // Or loaded as web components
    <ds-button variant="primary">Click me</ds-button>
    
    // 5. API Gateway Pattern
    class APIClient {
      constructor(baseURL) {
        this.baseURL = baseURL;
        this.token = null;
      }
      
      setToken(token) {
        this.token = token;
      }
      
      async request(endpoint, options = {}) {
        const url = `${this.baseURL}${endpoint}`;
        const headers = {
          'Content-Type': 'application/json',
          ...(this.token && { 'Authorization': `Bearer ${this.token}` }),
          ...options.headers
        };
        
        const response = await fetch(url, {
          ...options,
          headers
        });
        
        if (!response.ok) {
          throw new Error(`API Error: ${response.statusText}`);
        }
        
        return response.json();
      }
    }
    
    // Shared API client
    window.apiClient = new APIClient('https://api.example.com');
    
    // Usage in micro-frontends
    const products = await window.apiClient.request('/products');
    

    Performance Optimizations:

    // 1. Lazy loading with preloading
    function preloadMicroFrontend(name) {
      const link = document.createElement('link');
      link.rel = 'prefetch';
      link.href = `https://${name}.example.com/remoteEntry.js`;
      document.head.appendChild(link);
    }
    
    // Preload on hover
    <Link 
      to="/products"
      onMouseEnter={() => preloadMicroFrontend('productCatalog')}
    >
      Products
    </Link>
    
    // 2. Service Worker for caching
    self.addEventListener('fetch', (event) => {
      if (event.request.url.includes('remoteEntry.js')) {
        event.respondWith(
          caches.match(event.request).then(response => {
            return response || fetch(event.request).then(fetchResponse => {
              return caches.open('micro-frontends-v1').then(cache => {
                cache.put(event.request, fetchResponse.clone());
                return fetchResponse;
              });
            });
          })
        );
      }
    });
    
    // 3. Code splitting within micro-frontends
    // Product Catalog micro-frontend
    const ProductDetail = lazy(() => import('./ProductDetail'));
    const ProductList = lazy(() => import('./ProductList'));
    
    // 4. Shared chunk optimization
    // webpack config
    optimization: {
      splitChunks: {
        cacheGroups: {
          vendor: {
            test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
            name: 'vendor',
            chunks: 'all'
          }
        }
      }
    }
    

    Deployment Strategy:

    // Version management
    const MICRO_FRONTEND_VERSIONS = {
      productCatalog: {
        stable: '1.5.2',
        canary: '1.6.0-beta.1'
      },
      checkout: {
        stable: '2.1.0',
        canary: '2.2.0-rc.1'
      }
    };
    
    // Feature flags for gradual rollout
    function getMicroFrontendVersion(name, userId) {
      const versions = MICRO_FRONTEND_VERSIONS[name];
      
      // 10% canary deployment
      if (isInCanaryGroup(userId, 0.1)) {
        return versions.canary;
      }
      
      return versions.stable;
    }
    
    function loadMicroFrontend(name, userId) {
      const version = getMicroFrontendVersion(name, userId);
      return loadScript(`https://${name}.example.com/${version}/remoteEntry.js`);
    }
    

    Testing Strategy:

    // 1. Contract testing between shell and micro-frontends
    // micro-frontend-contracts.test.js
    describe('Product Catalog Contract', () => {
      it('should expose ProductList component', async () => {
        const module = await import('productCatalog/ProductList');
        expect(module.default).toBeDefined();
        expect(typeof module.default).toBe('function');
      });
      
      it('should emit product-selected event', () => {
        const callback = jest.fn();
        window.microFrontendBus.on('product:selected', callback);
        
        // Trigger event
        window.microFrontendBus.emit('product:selected', { id: '123' });
        
        expect(callback).toHaveBeenCalledWith({ id: '123' });
      });
    });
    
    // 2. Integration testing
    describe('Shell Integration', () => {
      it('should load and render micro-frontend', async () => {
        render(<Shell />);
        
        await waitFor(() => {
          expect(screen.getByTestId('product-catalog')).toBeInTheDocument();
        });
      });
    });
    
    // 3. E2E testing across boundaries
    // cypress/integration/checkout-flow.spec.js
    describe('Checkout Flow', () => {
      it('should complete purchase from product to payment', () => {
        cy.visit('/products');
        cy.get('[data-testid="product-item"]').first().click();
        cy.get('[data-testid="add-to-cart"]').click();
        cy.get('[data-testid="checkout-button"]').click();
        
        // Now in checkout micro-frontend
        cy.get('[data-testid="payment-form"]').should('be.visible');
        cy.get('[data-testid="complete-purchase"]').click();
        
        cy.url().should('include', '/confirmation');
      });
    });
    

    Monitoring & Observability:

    // Track micro-frontend loading performance
    window.microFrontendBus.on('mfe:loaded', (data) => {
      window.monitor.recordCustomMetric('mfe-load-time', data.duration, {
        name: data.name,
        version: data.version
      });
    });
    
    // Track errors per micro-frontend
    window.addEventListener('error', (event) => {
      const mfeName = identifyMicroFrontend(event.filename);
      
      window.monitor.record({
        type: 'error',
        microFrontend: mfeName,
        message: event.message,
        stack: event.error?.stack
      });
    });
    
    function identifyMicroFrontend(filename) {
      if (filename.includes('productCatalog')) return 'productCatalog';
      if (filename.includes('checkout')) return 'checkout';
      if (filename.includes('userProfile')) return 'userProfile';
      return 'shell';
    }
    

    Key Design Decisions Summary:

    1. Module Federation for same framework: Best balance of performance and autonomy
    2. Web Components for different frameworks: When teams need total independence
    3. Event bus for communication: Loose coupling between micro-frontends
    4. Shared state manager: For truly global state (auth, user)
    5. Design system as shared dependency: Ensures UI consistency
    6. Version management: Canary deployments, gradual rollouts
    7. API Gateway: Centralized authentication and routing
    8. Contract testing: Prevent breaking changes

    Part 5: Advanced Questions

    Question 13: Explain the differences between memoization strategies: React.memo, useMemo, useCallback, and manual memoization

    React.memo (Component Memoization)

    // Without memo - re-renders on every parent render
    function ExpensiveChild({ data, onAction }) {
      console.log('Rendering ExpensiveChild');
      return <div>{expensiveCalculation(data)}</div>;
    }
    
    // With memo - only re-renders when props change
    const MemoizedChild = React.memo(ExpensiveChild);
    
    // Custom comparison function
    const MemoizedChildCustom = React.memo(
      ExpensiveChild,
      (prevProps, nextProps) => {
        // Return true if props are equal (skip re-render)
        return prevProps.data.id === nextProps.data.id;
      }
    );
    

    When React.memo helps:

    • Component is expensive to render
    • Component receives same props frequently
    • Component is a pure function of its props

    When React.memo doesn't help:

    • Props change every render (new objects/functions)
    • Component is cheap to render
    • Component has children that change

    Common Pitfall:

    function Parent() {
      const [count, setCount] = useState(0);
      
      // ❌ New object every render - memo useless
      const data = { value: 'hello' };
      
      // ❌ New function every render - memo useless
      const handleClick = () => console.log('clicked');
      
      return <MemoizedChild data={data} onClick={handleClick} />;
    }
    
    // ✅ Fix with useMemo and useCallback
    function Parent() {
      const [count, setCount] = useState(0);
      
      const data = useMemo(() => ({ value: 'hello' }), []);
      const handleClick = useCallback(() => console.log('clicked'), []);
      
      return <MemoizedChild data={data} onClick={handleClick} />;
    }
    

    useMemo (Value Memoization)

    function ProductList({ products, filter }) {
      // ❌ Expensive calculation runs every render
      const filtered = products
        .filter(p => p.category === filter)
        .sort((a, b) => b.rating - a.rating);
      
      // ✅ Only recalculates when dependencies change
      const filtered = useMemo(() => {
        return products
          .filter(p => p.category === filter)
          .sort((a, b) => b.rating - a.rating);
      }, [products, filter]);
      
      return <div>{filtered.map(p => <Product key={p.id} {...p} />)}</div>;
    }
    

    When useMemo helps:

    • Expensive calculations (filtering, sorting large arrays)
    • Creating objects/arrays passed to memoized children
    • Avoiding unnecessary re-renders due to referential inequality

    When useMemo doesn't help:

    • Simple calculations (addition, string concatenation)
    • Values that change every render anyway
    • Over-optimization without measurement

    Cost-Benefit Analysis:

    // ❌ Premature optimization
    const sum = useMemo(() => a + b, [a, b]);
    // Cost of useMemo > cost of calculation
    
    // ✅ Worth it
    const sortedAndFiltered = useMemo(() => {
      return data
        .filter(item => item.active)
        .sort((a, b) => a.value - b.value)
        .map(item => ({ ...item, computed: heavyFunction(item) }));
    }, [data]);
    

    useCallback (Function Memoization)

    function ProductGrid({ products }) {
      const [selectedId, setSelectedId] = useState(null);
      
      // ❌ New function every render
      const handleSelect = (id) => {
        setSelectedId(id);
        analytics.track('product-selected', { id });
      };
      
      // ✅ Same function reference across renders
      const handleSelect = useCallback((id) => {
        setSelectedId(id);
        analytics.track('product-selected', { id });
      }, []); // Empty deps - function never changes
      
      // ✅ Updates when selectedId changes
      const handleToggle = useCallback((id) => {
        setSelectedId(prev => prev === id ? null : id);
      }, []); // Can use functional update, so no dep needed
      
      return products.map(p => (
        <MemoizedProduct 
          key={p.id} 
          product={p}
          onSelect={handleSelect}
        />
      ));
    }
    

    When useCallback helps:

    • Passing callbacks to memoized children
    • Callbacks used as dependencies in useEffect
    • Preventing child re-renders

    When useCallback doesn't help:

    • Callbacks not passed to memoized components
    • Callbacks change frequently anyway
    • Simple event handlers

    Common Pattern:

    function SearchBar() {
      const [query, setQuery] = useState('');
      const [results, setResults] = useState([]);
      
      // ✅ Callback is dependency in useEffect
      const fetchResults = useCallback(async (searchQuery) => {
        const data = await api.search(searchQuery);
        setResults(data);
      }, []);
      
      useEffect(() => {
        if (query) {
          fetchResults(query);
        }
      }, [query, fetchResults]); // fetchResults won't cause re-fetch
      
      return <input value={query} onChange={e => setQuery(e.target.value)} />;
    }
    

    Manual Memoization (useRef + Custom Logic)

    function ManualMemoization() {
      const cache = useRef(new Map());
      
      function expensiveCalculation(input) {
        // Check cache
        if (cache.current.has(input)) {
          console.log('Cache hit');
          return cache.current.get(input);
        }
        
        // Compute
        console.log('Computing...');
        const result = /* expensive computation */ input * 2;
        
        // Store in cache
        cache.current.set(input, result);
        
        return result;
      }
      
      return <div>{expensiveCalculation(props.value)}</div>;
    }
    

    When manual memoization helps:

    • Complex caching logic
    • LRU cache needed
    • Caching across multiple renders with custom eviction
    • Fine-grained control over cache invalidation

    Advanced: LRU Cache Implementation

    class LRUCache {
      constructor(capacity) {
        this.capacity = capacity;
        this.cache = new Map();
      }
      
      get(key) {
        if (!this.cache.has(key)) return undefined;
        
        // Move to end (most recently used)
        const value = this.cache.get(key);
        this.cache.delete(key);
        this.cache.set(key, value);
        
        return value;
      }
      
      set(key, value) {
        // Remove if exists
        if (this.cache.has(key)) {
          this.cache.delete(key);
        }
        
        // Add to end
        this.cache.set(key, value);
        
        // Evict oldest if over capacity
        if (this.cache.size > this.capacity) {
          const firstKey = this.cache.keys().next().value;
          this.cache.delete(firstKey);
        }
      }
    }
    
    function ComponentWithLRU() {
      const cache = useRef(new LRUCache(100));
      
      function compute(input) {
        let result = cache.current.get(input);
        
        if (result === undefined) {
          result = expensiveComputation(input);
          cache.current.set(input, result);
        }
        
        return result;
      }
      
      return <div>{compute(props.data)}</div>;
    }
    

    Comparison Table:

    Technique Memoizes Use Case Performance Cost
    React.memo Component Prevent re-renders Props comparison
    useMemo Value Expensive calculations Dependency check
    useCallback Function Stable callback references Dependency check
    Manual (useRef) Custom Complex caching logic Your implementation

    Decision Tree:

    Do you need to prevent a component re-render?
    ├─ Yes → Use React.memo
    │   └─ Are props objects/functions?
    │       └─ Yes → Also use useMemo/useCallback for props
    └─ No → Is it an expensive calculation?
        ├─ Yes → Use useMemo
        └─ No → Is it a function passed to memoized child?
            ├─ Yes → Use useCallback
            └─ No → Don't memoize
    

    Question 14: How do you handle race conditions in React applications?

    Problem: Multiple Async Operations

    // ❌ Race condition: Last request wins, not latest request
    function SearchResults({ query }) {
      const [results, setResults] = useState([]);
      
      useEffect(() => {
        fetch(`/api/search?q=${query}`)
          .then(r => r.json())
          .then(data => setResults(data));
          // If query changes quickly, old responses can overwrite new ones
      }, [query]);
      
      return <div>{results.map(r => <Result key={r.id} {...r} />)}</div>;
    }
    

    Solution 1: Cleanup Flag

    function SearchResults({ query }) {
      const [results, setResults] = useState([]);
      
      useEffect(() => {
        let cancelled = false;
        
        fetch(`/api/search?q=${query}`)
          .then(r => r.json())
          .then(data => {
            if (!cancelled) {
              setResults(data);
            }
          });
        
        return () => {
          cancelled = true;
        };
      }, [query]);
      
      return <div>{results.map(r => <Result key={r.id} {...r} />)}</div>;
    }
    

    Solution 2: AbortController

    function SearchResults({ query }) {
      const [results, setResults] = useState([]);
      
      useEffect(() => {
        const controller = new AbortController();
        
        fetch(`/api/search?q=${query}`, {
          signal: controller.signal
        })
          .then(r => r.json())
          .then(data => setResults(data))
          .catch(err => {
            if (err.name !== 'AbortError') {
              console.error('Search failed:', err);
            }
          });
        
        return () => {
          controller.abort();
        };
      }, [query]);
      
      return <div>{results.map(r => <Result key={r.id} {...r} />)}</div>;
    }
    

    Solution 3: Request ID/Version

    function SearchResults({ query }) {
      const [results, setResults] = useState([]);
      const requestIdRef = useRef(0);
      
      useEffect(() => {
        const requestId = ++requestIdRef.current;
        
        fetch(`/api/search?q=${query}`)
          .then(r => r.json())
          .then(data => {
            // Only update if this is still the latest request
            if (requestId === requestIdRef.current) {
              setResults(data);
            }
          });
      }, [query]);
      
      return <div>{results.map(r => <Result key={r.id} {...r} />)}</div>;
    }
    

    Solution 4: Custom Hook with Race Condition Prevention

    function useAsyncData(fetchFn, deps) {
      const [data, setData] = useState(null);
      const [loading, setLoading] = useState(false);
      const [error, setError] = useState(null);
      
      useEffect(() => {
        let cancelled = false;
        const controller = new AbortController();
        
        async function fetchData() {
          setLoading(true);
          setError(null);
          
          try {
            const result = await fetchFn(controller.signal);
            
            if (!cancelled) {
              setData(result);
              setLoading(false);
            }
          } catch (err) {
            if (!cancelled && err.name !== 'AbortError') {
              setError(err);
              setLoading(false);
            }
          }
        }
        
        fetchData();
        
        return () => {
          cancelled = true;
          controller.abort();
        };
      }, deps);
      
      return { data, loading, error };
    }
    
    // Usage
    function SearchResults({ query }) {
      const { data, loading, error } = useAsyncData(
        async (signal) => {
          const response = await fetch(`/api/search?q=${query}`, { signal });
          return response.json();
        },
        [query]
      );
      
      if (loading) return <Spinner />;
      if (error) return <Error message={error.message} />;
      
      return <div>{data.map(r => <Result key={r.id} {...r} />)}</div>;
    }
    

    Race Condition in Optimistic Updates:

    function TodoItem({ todo }) {
      const [isCompleted, setIsCompleted] = useState(todo.completed);
      
      // ❌ Race condition with rapid clicks
      async function toggleComplete() {
        setIsCompleted(!isCompleted); // Optimistic update
        
        try {
          await api.updateTodo(todo.id, { completed: !isCompleted });
        } catch (error) {
          setIsCompleted(isCompleted); // Revert
        }
      }
      
      // ✅ Fixed with request tracking
      const pendingRequestRef = useRef(null);
      
      async function toggleComplete() {
        // Cancel previous request
        if (pendingRequestRef.current) {
          pendingRequestRef.current.abort();
        }
        
        const controller = new AbortController();
        pendingRequestRef.current = controller;
        
        const newState = !isCompleted;
        setIsCompleted(newState);
        
        try {
          await api.updateTodo(
            todo.id, 
            { completed: newState },
            { signal: controller.signal }
          );
          pendingRequestRef.current = null;
        } catch (error) {
          if (error.name !== 'AbortError') {
            setIsCompleted(!newState); // Revert
          }
        }
      }
      
      return (
        <div>
          <input 
            type="checkbox" 
            checked={isCompleted}
            onChange={toggleComplete}
          />
          {todo.title}
        </div>
      );
    }
    

    Race Conditions in Parallel Requests:

    // Problem: Need data from multiple sources
    function UserDashboard({ userId }) {
      const [profile, setProfile] = useState(null);
      const [orders, setOrders] = useState([]);
      const [reviews, setReviews] = useState([]);
      
      useEffect(() => {
        // ❌ Three separate effects = three race conditions
        fetch(`/api/users/${userId}`).then(r => r.json()).then(setProfile);
        fetch(`/api/orders/${userId}`).then(r => r.json()).then(setOrders);
        fetch(`/api/reviews/${userId}`).then(r => r.json()).then(setReviews);
      }, [userId]);
      
      // ✅ Solution: Single effect with Promise.all
      useEffect(() => {
        let cancelled = false;
        
        Promise.all([
          fetch(`/api/users/${userId}`).then(r => r.json()),
          fetch(`/api/orders/${userId}`).then(r => r.json()),
          fetch(`/api/reviews/${userId}`).then(r => r.json())
        ]).then(([profileData, ordersData, reviewsData]) => {
          if (!cancelled) {
            setProfile(profileData);
            setOrders(ordersData);
            setReviews(reviewsData);
          }
        });
        
        return () => {
          cancelled = true;
        };
      }, [userId]);
    }
    

    Advanced: Debouncing to Prevent Race Conditions

    function useDebounce(value, delay) {
      const [debouncedValue, setDebouncedValue] = useState(value);
      
      useEffect(() => {
        const timer = setTimeout(() => {
          setDebouncedValue(value);
        }, delay);
        
        return () => {
          clearTimeout(timer);
        };
      }, [value, delay]);
      
      return debouncedValue;
    }
    
    function SearchResults({ query }) {
      const debouncedQuery = useDebounce(query, 300);
      const [results, setResults] = useState([]);
      
      useEffect(() => {
        if (!debouncedQuery) return;
        
        const controller = new AbortController();
        
        fetch(`/api/search?q=${debouncedQuery}`, {
          signal: controller.signal
        })
          .then(r => r.json())
          .then(setResults)
          .catch(err => {
            if (err.name !== 'AbortError') {
              console.error(err);
            }
          });
        
        return () => controller.abort();
      }, [debouncedQuery]);
      
      return <div>{results.map(r => <Result key={r.id} {...r} />)}</div>;
    }
    

    Conclusion

    These interview questions cover the cutting edge of frontend development in 2025:

    1. React Hooks - Deep understanding of closures, memoization, and rules
    2. Server Components - New paradigm shifting SSR and data fetching
    3. Performance - Real tradeoffs in code splitting, scrolling, and Context
    4. System Design - Architecting complex systems like collaborative editors
    5. Micro-frontends - Building scalable applications with multiple teams
    6. Observability - Production monitoring and debugging
    7. Race Conditions - Handling async complexity correctly

    Key Takeaways:

    • Understand not just the "what" but the "why" and "when"
    • Performance optimization requires measurement, not guessing
    • Every architectural decision has tradeoffs
    • Real-world complexity requires systematic approaches
    • Testing and monitoring are essential, not optional

    Further Study:

    • React documentation on Server Components
    • Web performance APIs (PerformanceObserver, etc.)
    • Distributed systems concepts (CRDT, OT)
    • Browser internals and rendering pipeline
    • Modern JavaScript features (Temporal, Pattern Matching)

    Remember: The best engineers don't just know the answers—they understand the tradeoffs and can explain their reasoning clearly.

    Topics and tags

    Continue from this topic

    Practice next

    Related quizzes

    No related quizzes are attached to this article yet.

    Discussion

    Comments (0)

    Keep comments specific so learners can benefit from the discussion.

    No comments yet.

    Start the discussion with a question or a study insight.

    Quick facts

    Use this article as

    Primary topicWeb Development
    Read time50 minutes
    Comments0
    UpdatedOctober 13, 2025

    Author

    RC
    R.S. Chauhan
    Published October 13, 2025

    Tagged with

    javascript
    web development
    React
    NextJS
    Browse library