
A practical guide to answering frontend system design interview prompts: define requirements first, derive user flows and contracts, then explain trade-offs clearly.
Frontend system-design interviews can feel unfair. You hear “Design a collaboration dashboard” and your mind immediately reaches for React state, WebSockets, caching, pagination, accessibility, API endpoints, and performance. That is too much to solve at once.
The useful starting point is simpler: before choosing technology, define what the user must be able to do. A good frontend design answer is a chain of reasoning, not a list of fashionable tools.
Imagine the prompt is: Design a team task board.
Do not begin with “I would use Redux” or “I would use WebSockets.” First narrow the product.
Ask questions such as:
Then state a clear assumption:
I will design the web task board for authenticated team members. Users can view columns, create and move tasks, see changes from teammates while the board is open, and filter tasks. Attachments, offline editing, and native mobile are out of scope.
That one statement makes the discussion concrete and protects the interview from becoming “design every feature of Jira.”
Functional requirements describe behaviour. Quality requirements describe the constraints around it.
| Functional requirement | Design concern it creates |
|---|---|
| View a board and its tasks | Initial loading, pagination, virtualization |
| Move a task between columns | Mutation, optimistic UI, rollback |
| See teammates’ changes | Realtime transport, conflict handling |
| Filter by assignee or status | URL state, query contract, cache keys |
| Show who can edit | Authorization-aware UI and API enforcement |
Keep the distinction clear in an interview. “Tasks should update quickly” is not a feature by itself; it is a performance expectation that influences the realtime approach.
For each important behaviour, describe the visible user flow before drawing architecture.
Now the technical decisions have a purpose. You need a task identifier, ordering information, a mutation contract, optimistic state, an error path, and a synchronization event.
An interview-quality client model does not need every database field. It needs the data needed to render and update the screen safely.
type Task = {
id: string;
title: string;
columnId: string;
position: number;
assignee?: { id: string; name: string };
updatedAt: string;
version: number;
};
type Board = {
id: string;
name: string;
columns: Array<{ id: string; title: string }>;
};
The version field matters when several users edit the same task. You do not have to build a full conflict-resolution engine in a 45-minute interview. You should show that you noticed the conflict and can choose a reasonable rule, such as last-write-wins with a refresh prompt when the server rejects an outdated version.
Avoid inventing APIs before knowing what the UI needs. From the flows above, a small contract emerges naturally:
GET /boards/:boardId
GET /boards/:boardId/tasks?cursor=...
PATCH /tasks/:taskId
{
"columnId": "in-progress",
"position": 4,
"version": 12
}
The server response should return the canonical task, including its new version. That gives the client one reliable source of truth after an optimistic update.
State management is not a library choice first. Ask who owns the state and how long it must survive.
This is a stronger answer than saying “I would put everything in global state.”
If the interviewer says teammates must see changes while the board is open, ask how fresh the data must be and how many users are present.
| Need | Reasonable option |
|---|---|
| Occasional refresh is acceptable | Polling after visibility or at an interval |
| Server pushes one-way updates | Server-Sent Events |
| Presence, typing, or bidirectional collaboration | WebSockets |
For this board, WebSockets may be justified only if low-latency collaboration or presence is truly required. Explain the trade-off instead of announcing the technology first.
Optimistic UI makes drag-and-drop feel instant, but it needs a rollback strategy.
async function moveTask(input: MoveTaskInput) {
const snapshot = queryClient.getQueryData<Task[]>(taskKey(input.boardId));
queryClient.setQueryData(taskKey(input.boardId), (tasks = []) =>
applyLocalMove(tasks, input),
);
try {
const task = await api.patchTask(input);
queryClient.setQueryData(taskKey(input.boardId), (tasks = []) =>
replaceTask(tasks, task),
);
} catch (error) {
queryClient.setQueryData(taskKey(input.boardId), snapshot);
throw error;
}
}
Mentioning the snapshot, rollback, and canonical server response shows the interviewer you have designed the unhappy path too.
Strong answers include the details that make a UI usable in production:
When the prompt is vague, use this order:
Frontend system design is not a contest to name React libraries, WebSockets, or micro-frontends. It is an exercise in making good decisions with incomplete information.
Start with what the user must accomplish. When requirements are clear, the data model, API, state boundaries, rendering strategy, and realtime approach become much easier to justify.
No approved comments are visible yet. New community replies may wait for moderation.