Target role: Mid-level Frontend Engineer | Duration: 90 min (pick 4–5 tasks)
| # | Task | Difficulty |
|---|---|---|
| 1 | Fix a bug | Easy |
| 2 | Search input with typing delay | Medium |
| 3 | Prevent unnecessary re-renders | Medium |
| 4 | Designing a reusable Select component | Medium |
| 5 | Discriminated unions for async state | Medium |
| 6 | useReducer for a notification system |
Medium |
| 7 | Accessible modal | Medium |
| 8 | Next.js Server Components inside Client Components | Medium |
| 9 | Infinite scroll with Intersection Observer | Medium–Hard |
| 10 | Error Boundary | Medium–Hard |
| 11 | Context performance problem | Hard |
"This counter should increment by 1 every second. It works once and then stops. What's wrong? Fix it."
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setCount(count + 1); // reads count from initial render closure
}, 1000);
return () => clearInterval(id);
}, []); // count never in deps
return <p>Count: {count}</p>;
}
"Build a search input that calls
onSearchonly after the user stops typing for 500 ms. Then extract that timing logic into a reusable custom hook."
"Every
Itemin this list re-renders whenever any item is deleted, even items that didn't change. Why? Fix it."
function Parent() {
const [items, setItems] = useState(['apple', 'banana', 'cherry']);
const handleDelete = (item: string) => {
setItems(prev => prev.filter(i => i !== item));
};
return (
<ul>
{items.map(item => (
<Item key={item} name={item} onDelete={handleDelete} />
))}
</ul>
);
}
function Item({ name, onDelete }: { name: string; onDelete: (s: string) => void }) {
console.log(`render: ${name}`);
return <li>{name} <button onClick={() => onDelete(name)}>×</button></li>;
}
"You need to build a
Selectcomponent that can be reused across the app with different data shapes — sometimes a list of users, sometimes a list of countries, sometimes something else entirely. How would you design its API so it doesn't need to know anything about the object shape it receives?"
"You're storing async state with three separate booleans:
loading,error, anddata. What is the problem with this approach? Refactor it using a TypeScript discriminated union."
// Before — problematic
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [data, setData] = useState<User | null>(null);
useReducer for a Notification System"Build a notification system using
useReducer. Notifications have an id, message, and type (success | error | info). They should auto-dismiss after 3 seconds. Exposeaddanddismissfunctions."
"This modal works visually but has several accessibility problems. List them and fix as many as you can."
function Modal({ isOpen, onClose, children }) {
if (!isOpen) return null;
return (
<div style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,.5)' }}>
<div style={{ background: '#fff', padding: 24 }}>
{children}
<button onClick={onClose}>Close</button>
</div>
</div>
);
}
"
ThemeWrapperneedsuseStateso it must be a Client Component.UserProfilefetches directly from the database so it must stay a Server Component. The current code silently breaks that rule. Explain what's wrong and fix it."
// UserProfile.tsx — intended to be a Server Component
async function UserProfile({ userId }: { userId: string }) {
const user = await db.users.findById(userId); // direct DB call — server only
return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
);
}
// ThemeWrapper.tsx — Client Component
'use client';
import { useState } from 'react';
import UserProfile from './UserProfile';
export default function ThemeWrapper({ userId }: { userId: string }) {
const [theme, setTheme] = useState<'light' | 'dark'>('light');
return (
<div data-theme={theme}>
<button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>
Toggle theme
</button>
<UserProfile userId={userId} />
</div>
);
}
// page.tsx
import ThemeWrapper from './ThemeWrapper';
export default function Page() {
return <ThemeWrapper userId="123" />;
}
"The code below has three bugs spread across the hook and the component. The list either loads forever, loads the same page repeatedly, or crashes silently depending on which bugs trigger first. Find all three and fix them. Then place the sentinel element correctly."
// Simulates a paginated API — do not modify
async function fetchPage(page: number): Promise<string[]> {
await new Promise(r => setTimeout(r, 600));
if (page >= 5) return [];
return Array.from({ length: 10 }, (_, i) => `Item ${page * 10 + i + 1}`);
}
// ---- fix the bugs in this hook ----
function useInfiniteScroll(onLoadMore: () => void, enabled: boolean) {
const sentinelRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!enabled) return;
const el = sentinelRef.current;
if (!el) return;
const observer = new IntersectionObserver(
([entry]) => { if (entry.isIntersecting) onLoadMore(); }
);
observer.observe(el);
}, []);
return sentinelRef;
}
function ItemList() {
const [items, setItems] = useState<string[]>([]);
const [page, setPage] = useState(0);
const [loading, setLoading] = useState(false);
const [hasMore, setHasMore] = useState(true);
async function loadMore() {
if (loading || !hasMore) return;
setLoading(true);
const next = await fetchPage(page);
if (next.length === 0) {
setHasMore(false);
} else {
setItems(prev => [...prev, ...next]);
setPage(p => p + 1);
}
setLoading(false);
}
const sentinelRef = useInfiniteScroll(loadMore, hasMore);
return (
<div>
{items.map(item => <div key={item} style={{ padding: 12 }}>{item}</div>)}
{loading && <p>Loading...</p>}
{/* TODO: place the sentinel ref here — think about when it should be rendered */}
</div>
);
}
"A third-party component sometimes throws during render. Wrap it so the rest of the page still works and a friendly fallback is shown instead. The fallback should include a 'Try again' button."
"This app stores
userandthemein a single context. Components that only care about the user still re-render every time the theme changes. Diagnose the problem and fix it without reaching for an external library."
type AppCtx = { user: User; theme: string; setTheme: (t: string) => void };
const AppContext = createContext<AppCtx | null>(null);
function App() {
const [user] = useState<User>({ name: 'Alice' });
const [theme, setTheme] = useState('light');
return (
<AppContext.Provider value={{ user, theme, setTheme }}>
<UserCard />
<ThemeToggle />
</AppContext.Provider>
);
}