Complete guide to acing frontend technical interviews. This comprehensive guide covers JavaScript fundamentals, React concepts, algorithm questions, system design basics, and hands-on coding challenges with real examples and solutions.
Frontend technical interviews typically consist of several components:
Phone/Video Screening (30-45 mins)
Technical Round 1 (45-60 mins)
Technical Round 2 (45-60 mins)
System Design (45-60 mins)
Final Round (30-45 mins)
// Common closure question
function createCounter() {
let count = 0;
return function() {
return ++count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
// Interview question: Fix this loop
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // Prints 3, 3, 3
}
// Solution 1: Use let
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // Prints 0, 1, 2
}
// Solution 2: Use closure
for (var i = 0; i < 3; i++) {
(function(j) {
setTimeout(() => console.log(j), 100);
})(i);
}
// What will this code output?
console.log(a); // undefined (not ReferenceError)
console.log(b); // ReferenceError: Cannot access 'b' before initialization
console.log(c); // ReferenceError: c is not defined
var a = 1;
let b = 2;
// Function hoisting
sayHello(); // "Hello!" - works due to hoisting
function sayHello() {
console.log("Hello!");
}
// But this won't work:
sayGoodbye(); // TypeError: sayGoodbye is not a function
var sayGoodbye = function() {
console.log("Goodbye!");
};
// Interview question: What's the output order?
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');
// Output: 1, 4, 3, 2
// Explanation: Synchronous code first, then microtasks (Promises), then macrotasks (setTimeout)
// Async/Await patterns
async function fetchUserData(id) {
try {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
throw new Error('User not found');
}
return await response.json();
} catch (error) {
console.error('Error fetching user:', error);
throw error;
}
}
// Error handling with Promise.all
async function fetchMultipleUsers(ids) {
try {
const users = await Promise.all(
ids.map(id => fetchUserData(id))
);
return users;
} catch (error) {
// If any request fails, this will catch it
console.error('One or more requests failed:', error);
}
}
const obj = {
name: 'John',
greet: function() {
console.log(`Hello, ${this.name}`);
},
greetArrow: () => {
console.log(`Hello, ${this.name}`); // 'this' refers to global object
}
};
obj.greet(); // "Hello, John"
obj.greetArrow(); // "Hello, undefined"
// Call, Apply, Bind
function introduce(age, city) {
console.log(`Hi, I'm ${this.name}, ${age} years old from ${city}`);
}
const person = { name: 'Alice' };
introduce.call(person, 25, 'NYC'); // Hi, I'm Alice, 25 years old from NYC
introduce.apply(person, [25, 'NYC']); // Same as above
const boundIntroduce = introduce.bind(person, 25);
boundIntroduce('NYC'); // Hi, I'm Alice, 25 years old from NYC
// Prototype chain
function Person(name) {
this.name = name;
}
Person.prototype.greet = function() {
return `Hello, I'm ${this.name}`;
};
function Developer(name, language) {
Person.call(this, name);
this.language = language;
}
// Set up inheritance
Developer.prototype = Object.create(Person.prototype);
Developer.prototype.constructor = Developer;
Developer.prototype.code = function() {
return `${this.name} codes in ${this.language}`;
};
const dev = new Developer('John', 'JavaScript');
console.log(dev.greet()); // "Hello, I'm John"
console.log(dev.code()); // "John codes in JavaScript"
// ES6 Classes (syntactic sugar)
class PersonES6 {
constructor(name) {
this.name = name;
}
greet() {
return `Hello, I'm ${this.name}`;
}
}
class DeveloperES6 extends PersonES6 {
constructor(name, language) {
super(name);
this.language = language;
}
code() {
return `${this.name} codes in ${this.language}`;
}
}
// Common interview question: Implement a custom hook for data fetching
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const abortController = new AbortController();
async function fetchData() {
try {
setLoading(true);
const response = await fetch(url, {
signal: abortController.signal
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
setData(result);
} catch (err) {
if (err.name !== 'AbortError') {
setError(err.message);
}
} finally {
setLoading(false);
}
}
fetchData();
return () => abortController.abort();
}, [url]);
return { data, loading, error };
}
// Usage
function UserProfile({ userId }) {
const { data: user, loading, error } = useFetch(`/api/users/${userId}`);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
if (!user) return <div>User not found</div>;
return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
);
}
// Interview question: Implement a todo app with useReducer
const todoReducer = (state, action) => {
switch (action.type) {
case 'ADD_TODO':
return {
...state,
todos: [
...state.todos,
{
id: Date.now(),
text: action.payload,
completed: false
}
]
};
case 'TOGGLE_TODO':
return {
...state,
todos: state.todos.map(todo =>
todo.id === action.payload
? { ...todo, completed: !todo.completed }
: todo
)
};
case 'DELETE_TODO':
return {
...state,
todos: state.todos.filter(todo => todo.id !== action.payload)
};
default:
return state;
}
};
function TodoApp() {
const [state, dispatch] = useReducer(todoReducer, { todos: [] });
const [input, setInput] = useState('');
const addTodo = () => {
if (input.trim()) {
dispatch({ type: 'ADD_TODO', payload: input });
setInput('');
}
};
return (
<div>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && addTodo()}
/>
<button onClick={addTodo}>Add</button>
<ul>
{state.todos.map(todo => (
<li key={todo.id}>
<span
style={{
textDecoration: todo.completed ? 'line-through' : 'none'
}}
onClick={() => dispatch({ type: 'TOGGLE_TODO', payload: todo.id })}
>
{todo.text}
</span>
<button
onClick={() => dispatch({ type: 'DELETE_TODO', payload: todo.id })}
>
Delete
</button>
</li>
))}
</ul>
</div>
);
}
// Interview question: Optimize this component
import React, { useState, useMemo, useCallback, memo } from 'react';
// Child component that should only re-render when props change
const TodoItem = memo(({ todo, onToggle, onDelete }) => {
console.log('Rendering TodoItem:', todo.text);
return (
<li>
<span
style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
onClick={() => onToggle(todo.id)}
>
{todo.text}
</span>
<button onClick={() => onDelete(todo.id)}>Delete</button>
</li>
);
});
function OptimizedTodoApp() {
const [todos, setTodos] = useState([]);
const [filter, setFilter] = useState('all');
const [input, setInput] = useState('');
// Memoize filtered todos
const filteredTodos = useMemo(() => {
return todos.filter(todo => {
if (filter === 'active') return !todo.completed;
if (filter === 'completed') return todo.completed;
return true;
});
}, [todos, filter]);
// Memoize callbacks to prevent unnecessary re-renders
const handleToggle = useCallback((id) => {
setTodos(prevTodos =>
prevTodos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
);
}, []);
const handleDelete = useCallback((id) => {
setTodos(prevTodos => prevTodos.filter(todo => todo.id !== id));
}, []);
const addTodo = useCallback(() => {
if (input.trim()) {
setTodos(prevTodos => [
...prevTodos,
{ id: Date.now(), text: input, completed: false }
]);
setInput('');
}
}, [input]);
return (
<div>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && addTodo()}
/>
<button onClick={addTodo}>Add Todo</button>
<div>
<button onClick={() => setFilter('all')}>All</button>
<button onClick={() => setFilter('active')}>Active</button>
<button onClick={() => setFilter('completed')}>Completed</button>
</div>
<ul>
{filteredTodos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onToggle={handleToggle}
onDelete={handleDelete}
/>
))}
</ul>
</div>
);
}
/* Specificity calculation */
#header .nav ul li a { /* specificity: 0,1,1,3 */
color: blue;
}
.nav a { /* specificity: 0,0,1,1 */
color: red;
}
/* The first rule wins due to higher specificity */
/* Box model */
.box {
width: 300px;
padding: 20px;
border: 5px solid black;
margin: 10px;
box-sizing: border-box; /* Total width = 300px */
/* Without box-sizing: border-box, total width = 350px */
}
/* Flexbox interview question: Center content */
.flex-container {
display: flex;
justify-content: center; /* horizontal centering */
align-items: center; /* vertical centering */
height: 100vh;
}
/* Grid layout */
.grid-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1rem;
padding: 1rem;
}
/* Responsive grid */
.responsive-grid {
display: grid;
grid-template-columns: 1fr;
}
@media (min-width: 768px) {
.responsive-grid {
grid-template-columns: 1fr 1fr;
}
}
@media (min-width: 1024px) {
.responsive-grid {
grid-template-columns: repeat(3, 1fr);
}
}
/* Position interview scenarios */
.parent {
position: relative;
width: 400px;
height: 300px;
}
.child-absolute {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%); /* Perfect centering */
}
.sticky-header {
position: sticky;
top: 0;
background: white;
z-index: 100;
}
/* Z-index stacking context */
.modal-backdrop {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 1000;
}
.modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
z-index: 1001;
}
// Question: Remove duplicates from array
function removeDuplicates(arr) {
// Method 1: Using Set
return [...new Set(arr)];
// Method 2: Using filter and indexOf
return arr.filter((item, index) => arr.indexOf(item) === index);
// Method 3: Using reduce
return arr.reduce((unique, item) => {
return unique.includes(item) ? unique : [...unique, item];
}, []);
}
// Question: Find the intersection of two arrays
function intersection(arr1, arr2) {
const set1 = new Set(arr1);
return arr2.filter(item => set1.has(item));
}
// Question: Flatten nested array
function flattenArray(arr) {
// Method 1: Using built-in flat()
return arr.flat(Infinity);
// Method 2: Recursive approach
function flatten(arr) {
const result = [];
for (let item of arr) {
if (Array.isArray(item)) {
result.push(...flatten(item));
} else {
result.push(item);
}
}
return result;
}
return flatten(arr);
}
// Question: Check if string is a palindrome
function isPalindrome(str) {
const cleaned = str.toLowerCase().replace(/[^a-z0-9]/g, '');
return cleaned === cleaned.split('').reverse().join('');
}
// Question: Find longest substring without repeating characters
function longestUniqueSubstring(str) {
let maxLength = 0;
let start = 0;
const seen = new Map();
for (let end = 0; end < str.length; end++) {
const char = str[end];
if (seen.has(char) && seen.get(char) >= start) {
start = seen.get(char) + 1;
}
seen.set(char, end);
maxLength = Math.max(maxLength, end - start + 1);
}
return maxLength;
}
// Question: Implement debounce
function debounce(func, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(this, args), delay);
};
}
// Usage
const debouncedSearch = debounce((query) => {
console.log('Searching for:', query);
}, 300);
// Question: Deep clone an object
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (obj instanceof Date) {
return new Date(obj.getTime());
}
if (obj instanceof Array) {
return obj.map(item => deepClone(item));
}
if (typeof obj === 'object') {
const cloned = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = deepClone(obj[key]);
}
}
return cloned;
}
}
// Question: Group array of objects by property
function groupBy(array, key) {
return array.reduce((groups, item) => {
const value = item[key];
if (!groups[value]) {
groups[value] = [];
}
groups[value].push(item);
return groups;
}, {});
}
// Usage
const users = [
{ name: 'John', department: 'Engineering' },
{ name: 'Jane', department: 'Marketing' },
{ name: 'Bob', department: 'Engineering' }
];
const groupedUsers = groupBy(users, 'department');
Question: Design a reusable Modal component
Considerations:
- Portal rendering (render outside parent DOM)
- Focus management
- Keyboard accessibility (ESC key)
- Background scroll prevention
- Animation/transitions
- Multiple modal stacking
Implementation approach:
// Modal System Design
import ReactDOM from 'react-dom';
import { useEffect, useRef } from 'react';
function Modal({ isOpen, onClose, children, className }) {
const modalRef = useRef(null);
const previousActiveElement = useRef(null);
useEffect(() => {
if (isOpen) {
// Store currently focused element
previousActiveElement.current = document.activeElement;
// Prevent body scroll
document.body.style.overflow = 'hidden';
// Focus modal
modalRef.current?.focus();
}
return () => {
// Restore body scroll
document.body.style.overflow = 'unset';
// Restore focus
if (previousActiveElement.current) {
previousActiveElement.current.focus();
}
};
}, [isOpen]);
useEffect(() => {
const handleEscape = (e) => {
if (e.key === 'Escape') {
onClose();
}
};
if (isOpen) {
document.addEventListener('keydown', handleEscape);
}
return () => {
document.removeEventListener('keydown', handleEscape);
};
}, [isOpen, onClose]);
if (!isOpen) return null;
return ReactDOM.createPortal(
<div
className="modal-backdrop"
onClick={(e) => {
if (e.target === e.currentTarget) {
onClose();
}
}}
>
<div
ref={modalRef}
className={`modal ${className || ''}`}
role="dialog"
aria-modal="true"
tabIndex={-1}
>
<button
className="modal-close"
onClick={onClose}
aria-label="Close modal"
>
×
</button>
{children}
</div>
</div>,
document.body
);
}
Question: Design state management for a large e-commerce application
Consider:
- User authentication state
- Shopping cart state
- Product catalog state
- UI state (modals, notifications)
- Caching strategy
- Optimistic updates
Architecture approach:
- Context API for global state
- Custom hooks for business logic
- React Query for server state
- Local state for component-specific data
// State Management Design
const AppContext = createContext();
function AppProvider({ children }) {
// Authentication state
const [user, setUser] = useState(null);
// Shopping cart state
const [cart, setCart] = useState([]);
// UI state
const [notifications, setNotifications] = useState([]);
const addToCart = useCallback((product) => {
setCart(prevCart => {
const existingItem = prevCart.find(item => item.id === product.id);
if (existingItem) {
return prevCart.map(item =>
item.id === product.id
? { ...item, quantity: item.quantity + 1 }
: item
);
}
return [...prevCart, { ...product, quantity: 1 }];
});
}, []);
const value = {
user,
setUser,
cart,
addToCart,
notifications,
setNotifications
};
return (
<AppContext.Provider value={value}>
{children}
</AppContext.Provider>
);
}
// Custom hooks for different domains
function useAuth() {
const { user, setUser } = useContext(AppContext);
const login = async (credentials) => {
try {
const response = await authAPI.login(credentials);
setUser(response.user);
return response;
} catch (error) {
throw error;
}
};
const logout = async () => {
await authAPI.logout();
setUser(null);
};
return { user, login, logout };
}
function useCart() {
const { cart, addToCart } = useContext(AppContext);
const removeFromCart = useCallback((productId) => {
setCart(prevCart => prevCart.filter(item => item.id !== productId));
}, []);
const getTotalPrice = useMemo(() => {
return cart.reduce((total, item) => total + item.price * item.quantity, 0);
}, [cart]);
return {
cart,
addToCart,
removeFromCart,
totalPrice: getTotalPrice
};
}
Question: How would you optimize a large data table component?
Considerations:
- Virtual scrolling for large datasets
- Memoization of expensive calculations
- Debounced search/filtering
- Pagination vs infinite scroll
- Column sorting and filtering
- Export functionality
Key techniques:
1. React.memo for preventing unnecessary re-renders
2. useMemo for expensive calculations
3. useCallback for stable function references
4. Code splitting for reducing bundle size
5. Lazy loading for images and components
6. Service workers for caching
// Question: Build a search component with debouncing and suggestions
function SearchComponent({ onSearch, getSuggestions }) {
const [query, setQuery] = useState('');
const [suggestions, setSuggestions] = useState([]);
const [showSuggestions, setShowSuggestions] = useState(false);
const [loading, setLoading] = useState(false);
// Debounce search queries
const debouncedQuery = useDebounce(query, 300);
useEffect(() => {
if (debouncedQuery) {
setLoading(true);
getSuggestions(debouncedQuery)
.then(results => {
setSuggestions(results);
setShowSuggestions(true);
})
.catch(console.error)
.finally(() => setLoading(false));
} else {
setSuggestions([]);
setShowSuggestions(false);
}
}, [debouncedQuery, getSuggestions]);
const handleSubmit = (e) => {
e.preventDefault();
if (query.trim()) {
onSearch(query);
setShowSuggestions(false);
}
};
const handleSuggestionClick = (suggestion) => {
setQuery(suggestion);
onSearch(suggestion);
setShowSuggestions(false);
};
return (
<div className="search-container">
<form onSubmit={handleSubmit}>
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search..."
onFocus={() => suggestions.length > 0 && setShowSuggestions(true)}
onBlur={() => setTimeout(() => setShowSuggestions(false), 150)}
/>
<button type="submit" disabled={!query.trim()}>
Search
</button>
</form>
{showSuggestions && (
<div className="suggestions">
{loading ? (
<div>Loading...</div>
) : (
suggestions.map((suggestion, index) => (
<div
key={index}
className="suggestion-item"
onClick={() => handleSuggestionClick(suggestion)}
>
{suggestion}
</div>
))
)}
</div>
)}
</div>
);
}
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => setDebouncedValue(value), delay);
return () => clearTimeout(handler);
}, [value, delay]);
return debouncedValue;
}
// Question: Implement infinite scroll with loading states
function InfiniteScrollList({ fetchData, renderItem }) {
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(false);
const [hasMore, setHasMore] = useState(true);
const [page, setPage] = useState(1);
const observer = useRef();
const lastItemRef = useCallback(node => {
if (loading) return;
if (observer.current) observer.current.disconnect();
observer.current = new IntersectionObserver(entries => {
if (entries[0].isIntersecting && hasMore) {
setPage(prevPage => prevPage + 1);
}
});
if (node) observer.current.observe(node);
}, [loading, hasMore]);
useEffect(() => {
setLoading(true);
fetchData(page)
.then(response => {
setItems(prevItems => [...prevItems, ...response.data]);
setHasMore(response.hasMore);
})
.catch(console.error)
.finally(() => setLoading(false));
}, [page, fetchData]);
return (
<div className="infinite-scroll-container">
{items.map((item, index) => {
const isLast = index === items.length - 1;
return (
<div
key={item.id}
ref={isLast ? lastItemRef : null}
className="list-item"
>
{renderItem(item)}
</div>
);
})}
{loading && (
<div className="loading-indicator">
Loading more items...
</div>
)}
{!hasMore && items.length > 0 && (
<div className="end-message">
No more items to load
</div>
)}
</div>
);
}
// Usage
function App() {
const fetchPosts = async (page) => {
const response = await fetch(`/api/posts?page=${page}&limit=10`);
return response.json();
};
const renderPost = (post) => (
<div>
<h3>{post.title}</h3>
<p>{post.excerpt}</p>
</div>
);
return (
<InfiniteScrollList
fetchData={fetchPosts}
renderItem={renderPost}
/>
);
}
Question: "Tell me about a time you had to make a technical decision that impacted the team."
STAR Framework Answer:
Question: "Describe a challenging bug you encountered and how you solved it."
Example Answer:
Question: "Tell me about a time you disagreed with a design decision."
Example Answer:
== and ===?let, const, and var?this work in arrow functions vs regular functions?useCallback vs useMemo?Frontend technical interviews require a combination of strong fundamentals, problem-solving skills, and effective communication. Key strategies for success:
Remember that interviews are conversations, not just coding tests. Show your passion for frontend development, your ability to work in a team, and your commitment to writing clean, maintainable code.
Good luck with your frontend interviews! 🚀