Frontend Interview Questions:

kiran ashokkiran ashok
4 min read

Identify issues in the following code

Question1 :

The Code:

async function fetchData(url: string): Promise<any> {
  const response = await fetch(url);
  const { data } = await response.json();
  return { data };
}

const result = fetchData('https://api.example.com/data');
console.log(result.data);

Issues Identified:

  • Missing error handling - Network requests can fail, but there's no try-catch block

  • Logging undefined data - The result is a Promise, not the actual data(await is missing)

Improved Version:

async function fetchData(url: string): Promise<any> {
  try {
    const response = await fetch(url);

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const { data } = await response.json();
    return { data };
  } catch (error) {
    console.error('Failed to fetch data:', error);
    throw error; // Re-throw to allow caller to handle
  }
}

// Proper usage
const result = await fetchData('https://api.example.com/data'); // AWAIT MISSING
console.log(result.data);

Question 2:

The Code:

import { useEffect, useState } from 'react';

function DataList() {
  const [data, setData] = useState([]);

  useEffect(() => {
    fetch('/api/data')
      .then(response => response.json())
      .then(data => setData(data));
  }, [data]); 

  return (
    <ul>
      {data.map((item, index) => (
        <li key={index}>{item.name}</li>
      ))}
    </ul>
  );
}

Critical Issues:

  • Infinite re-render loop - Including data in the dependency array causes the effect to run every time data changes

  • Poor key prop - Using array index as key can cause rendering issues

Fixed Version:

import { useEffect, useState } from 'react';

function DataList() {
  const [data, setData] = useState([]);

  useEffect(() => {
    fetch('/api/data')
      .then(response => response.json())
      .then(data => setData(data));
  }, []); 

  return (
    <ul>
      {data.map((item, index) => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}

Question 3:

The Code:

let data = [];

function fetchData() {
  for (let i = 0; i < 1000000; i++) {
    data.push({ id: i, value: `Item ${i}` });
  }
}

function processData() {
  data.forEach(item => {
    console.log(item.value);
  });
}

fetchData();
processData();

Major Issues:

  • Memory leak - Global array keeps growing indefinitely

  • Blocking main thread - Synchronous processing of 1M items

  • Inefficient console.log - Logging massive amounts of data

Optimized Solutions:

Approach 1: Chunked Processing

function fetchData() {
  // Return data instead of storing globally
  const data = [];
  for (let i = 0; i < 1000000; i++) {
    data.push({ id: i, value: `Item ${i}` });
  }
  return data;
}

function processDataInChunks(data, chunkSize = 1000) {
  return new Promise((resolve) => {
    let index = 0;

    function processChunk() {
      const chunk = data.slice(index, index + chunkSize);

      chunk.forEach(item => {
        // Process item (avoid console.log for performance)
        // Do actual work here
      });

      index += chunkSize;

      if (index < data.length) {
        // Use requestIdleCallback for better performance
        requestIdleCallback(processChunk);
      } else {
        resolve();
      }
    }

    processChunk();
  });
}

// Usage
const data = fetchData();
processDataInChunks(data).then(() => {
  console.log('Processing complete');
  // Clear data when done
  data.length = 0;
});

Question 4:

The Code:

import React, { useState } from 'react';

const ComplexComponent = () => {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('');

  const handleClick = () => {
    setCount(count + 1);
  };

  const handleChange = (e) => {
    setText(e.target.value);
  };

  return (
    <div>
      <button onClick={handleClick}>Increment</button>
      <input type="text" value={text} onChange={handleChange} />
      <ExpensiveComponent count={count} />
    </div>
  );
};

const ExpensiveComponent = ({ count }) => {
  console.log('Expensive component rendered');
  return <div>Count: {count}</div>;
};

Issues:

  • Unnecessary re-renders - ExpensiveComponent re-renders when text or count changes

  • Function recreation - Event handlers are recreated on every render

Optimized Version:

import React, { useState, useCallback, memo } from 'react';

const ComplexComponent = () => {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('');

  const handleClick = useCallback(() => {
    setCount(prev => prev + 1);
  }, []);

  const handleChange = useCallback((e) => {
    setText(e.target.value);
  }, []);

  return (
    <div>
      <button onClick={handleClick}>Increment</button>
      <input type="text" value={text} onChange={handleChange} />
      <ExpensiveComponent count={count} />
    </div>
  );
};

// Memoize the expensive component
const ExpensiveComponent = memo(({ count }) => {
  console.log('Expensive component rendered');
  return <div>Count: {count}</div>;
});

Final Thoughts

These interview questions weren't just about finding bugs—they were designed to test understanding of fundamental performance concepts that directly impact user experience. The key is not just knowing these patterns, but understanding why they cause issues and when to apply specific optimizations.

Remember: premature optimization is the root of all evil, but understanding these patterns helps you write performant code from the start and avoid common pitfalls that can seriously impact your application's performance.

What performance bottlenecks have you encountered in your frontend work? Share your experiences in the comments below!


If you found this helpful, follow me for more frontend development insights and interview preparation tips.

20
Subscribe to my newsletter

Read articles from kiran ashok directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

kiran ashok
kiran ashok