Frontend Interview Prep: JavaScript, React, UI Coding, or Algorithms?
By interviewDB Editorial Team
Published Updated
Quick summary
Summarize this blog with AI
A frontend interview invitation rarely tells you how to divide preparation between JavaScript, React, UI building, and algorithms. Start with the work expected in that particular round.
Recent public discussions capture this uncertainty. One August 2026 discussion asks how algorithm practice fits alongside component building. Another August 2026 discussion describes resource overload and little study time after work. A September discussion asks which track fits a vague frontend-coding invitation. These individual experiences do not establish a universal employer pattern.
Start with these three actions:
- Confirm whether the round asks for functions, a browser interface, React work, algorithms, or a design discussion.
- Attempt one small task in the permitted environment and identify the first skill that blocks you.
- Use that result to choose your next practice session, then repeat with a changed requirement.
Clarify the round before choosing a study track
Ask the recruiter for the format and permitted tools, without asking for the actual interview problem. Use a short message:
Could you confirm whether this round involves general algorithm questions, JavaScript exercises, building or debugging a UI, or frontend design discussion? For the coding portion, which languages or frameworks are allowed, and will there be a starter repository and browser preview? I would also appreciate the guidance on documentation, external libraries, and AI tools.
If the reply is vague, narrow the practical uncertainty: “Should I expect to produce a working browser interface, or write functions in a coding editor?” Also confirm the duration and whether tests are expected. A portal label such as “frontend coding” does not establish the answer.
Record confirmed facts separately from assumptions: a React requirement in the job description does not prove React is allowed. If clarification is unavailable, prepare a small UI task and JavaScript problem in the advertised environment, and retain some algorithm practice.
Match practice to the format you have confirmed
| Round format | Main practice | Evidence of readiness |
|---|---|---|
| JavaScript function exercises | Scope, closures, arrays, objects, promises, and explicit function behavior. | You can implement and test a small utility, explain edge cases, and change its requirements. |
| Working UI or component | HTML, CSS, interactions, state, async data, and keyboard use. | A user can complete the task, including empty and failure paths. |
| React implementation | State ownership, rendering, event handlers, lists, and justified Effects. | You can explain which values are stored, which are derived, and why updates behave correctly. |
| Algorithm screen | Data structures, problem decomposition, complexity, and testing in the permitted language. | You can solve an unfamiliar variation rather than reproduce a remembered answer. |
| Frontend design discussion | UI requirements, component boundaries, data flow, loading, accessibility, and performance tradeoffs. | You can connect a proposed design to the actual user workflow and constraints. |
Allocate the largest practice block to the confirmed task. Repair the weaknesses it exposes: a UI exercise still needs sound JavaScript, and a React component still needs usable HTML.
Practice JavaScript through observable behavior
For a JavaScript round, practice explaining behavior before writing it. Define the matching and error behavior of a searchable list before choosing array operations or asynchronous code. The worked example below makes those decisions observable.
Be able to explain why a callback can access variables from its enclosing scope. That helps explain how asynchronous callbacks retain the values they need; MDN's closure guide provides the underlying language model. Also practice immutable array transformations, appropriate use of maps and sets, and promise error handling with small examples you can trace.
When an exercise involves browser data, test the request boundary. A fulfilled fetch() promise does not guarantee a successful HTTP status: check response.ok, and account for body parsing failing. Use AbortController when cancellation is needed, and distinguish intentional cancellation from a failure the user should see. MDN documents these behaviors. Avoid treating every error as an empty result.
For TypeScript, practice representing the data you actually receive and narrowing optional values. A declared type does not validate a server response at runtime. If the prompt guarantees a response shape, state that assumption; otherwise explain where validation belongs.
Make React state decisions explicit
Start a React exercise by naming the source of truth. In a local searchable list, the source items and query are inputs; the filtered list can normally be calculated during rendering. Storing that list separately and synchronizing it with an Effect adds another value that can drift. React's Effect guidance explains this distinction between calculation and synchronization.
Know that an event handler sees the state snapshot for the render that created it. If an update depends on previous state, explain when a functional updater is appropriate. Practice predicting repeated updates before reaching for debugging guesses; React's state snapshot guide makes the behavior concrete.
Give list items stable keys based on their identity, especially when filtering, insertion, or reordering changes positions. Explain why an array index may fail to preserve the intended identity in a changing list. See React's list rendering guidance.
For remote search, describe what happens when an older request finishes after a newer one. Use an approach that prevents obsolete responses from replacing current results, and explain cleanup when the query changes or the component unmounts. React's Effect guidance demonstrates ignoring stale responses. A debounce reduces request frequency; it does not by itself solve out-of-order completion.
Build an interface people can use
For a UI round, include semantic structure and keyboard behavior in the first working version. Use a real button for an action and a link for navigation. Adding role="button" to another element does not supply native keyboard behavior; MDN recommends the native button element when possible.
Associate inputs with meaningful labels and preserve visible focus. W3C's form-label guidance explains how labels identify controls. Test the interface using only Tab, Shift+Tab, Enter, and Space. Check whether the current task remains understandable at a narrow viewport.
Practice CSS against behavior: long product names should wrap without covering controls, a small screen should avoid unnecessary horizontal scrolling, and a result count should remain readable when content grows. Explain the layout choice rather than decorating until the timer ends.
Make loading, empty, and failed states distinguishable. A blank list does not tell someone whether there are no matches or the request broke. Explain the next action, such as changing the query or retrying. For dynamically updated feedback, an appropriate live region can communicate a status without moving focus; follow W3C's notification guidance. Do not announce every keystroke or use urgent announcements for routine updates.
Run a frontend-specific timed practice
Build a searchable product list with a labeled query field, result count, and clear no-match state. Every action should work with a keyboard. Use the complete solution below to review your attempt, then change one requirement and rebuild without looking.
For a 45-minute session, try this adjustable budget:
- Minutes 0–5: confirm matching rules, initial state, supported interactions, and whether data is local or remote.
- Minutes 5–25: implement the core workflow with simple structure and explicit state ownership.
- Minutes 25–35: handle empty and failure states; check keyboard access, labels, and a narrow viewport.
- Minutes 35–45: test behavior and explain tradeoffs. In the async version, make an older request finish last and confirm it cannot overwrite the current result.
Change one requirement on the next attempt: add sorting, keep a selected item when results change, or preserve the query in navigation. This exposes whether you understand the implementation. Do not add every extension to the first session.
A complete browser example
Save the following block as frontend-practice.html and open it in a modern browser. It uses fictional fruit data and mocked asynchronous requests, with no network connection or dependencies. Matching trims whitespace and ignores case. The fail-next checkbox deliberately breaks one request so you can test recovery.
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Searchable list practice</title>
<style>
body { font: 1rem/1.5 system-ui; max-width: 36rem; margin: 2rem auto; padding: 1rem; }
input, button { font: inherit; padding: .4rem; }
input[type="search"] { max-width: 100%; box-sizing: border-box; }
li { overflow-wrap: anywhere; }
:focus-visible { outline: 3px solid #2345a0; outline-offset: 3px; }
</style>
<main>
<h1>Search products</h1>
<form id="search-form">
<label for="query">Product name</label>
<input id="query" type="search">
<button type="submit">Search</button>
<button id="clear" type="button">Clear</button>
</form>
<p><label><input id="fail-next" type="checkbox"> Fail next mock request</label></p>
<p id="status" role="status"></p>
<button id="retry" type="button" hidden>Retry search</button>
<section id="results-region" aria-label="Search results" aria-busy="false">
<ul id="results"></ul>
</section>
</main>
<script>
const products = ["Apple", "Apricot", "Banana"];
const query = document.getElementById("query");
const status = document.getElementById("status");
const results = document.getElementById("results");
const region = document.getElementById("results-region");
const retry = document.getElementById("retry");
const failNext = document.getElementById("fail-next");
let requestVersion = 0;
function mockSearch(term, shouldFail) {
return new Promise((resolve, reject) => {
// Make "a" slower than "ap" to expose out-of-order responses.
setTimeout(() => {
if (shouldFail) reject(new Error("Mock request failed"));
else resolve(products.filter(name => name.toLowerCase().includes(term)));
}, term === "a" ? 300 : 50);
});
}
async function runSearch() {
const version = ++requestVersion;
const term = query.value.trim().toLowerCase();
const shouldFail = failNext.checked;
failNext.checked = false;
retry.hidden = true;
region.setAttribute("aria-busy", "true");
results.replaceChildren();
status.textContent = "Loading products…";
try {
const matches = await mockSearch(term, shouldFail);
if (version !== requestVersion) return;
const items = matches.map(name => {
const item = document.createElement("li");
item.textContent = name;
return item;
});
results.replaceChildren(...items);
status.textContent = matches.length
? `${matches.length} product${matches.length === 1 ? "" : "s"}.`
: "No matches.";
} catch (error) {
if (version !== requestVersion) return;
status.textContent = "Could not load products. Retry.";
retry.hidden = false;
} finally {
// An obsolete request must not end the current request's loading state.
if (version === requestVersion) region.setAttribute("aria-busy", "false");
}
}
query.addEventListener("input", runSearch);
document.getElementById("search-form").addEventListener("submit", event => {
event.preventDefault();
runSearch();
});
document.getElementById("clear").addEventListener("click", () => {
query.value = "";
query.focus();
runSearch();
});
retry.addEventListener("click", () => {
query.focus();
runSearch();
});
runSearch();
</script>
</html>
Each search gets a version number. A response can change results, errors, or loading state only if it still belongs to the latest search. The guard in finally matters: an older request finishing must not announce that a newer request has stopped loading. Product names are inserted with textContent, rather than interpreted as HTML.
Sample input and expected behavior
| Action | Expected result after completion |
|---|---|
| Open the file or clear the query | All three products; “3 products.” |
Enter AP | Apple and Apricot; “2 products.” |
Enter zz | No items; “No matches.” |
Check fail-next, then search ap | No items; an explicit error and Retry search button. |
| Activate Retry search | The same query succeeds; Apple and Apricot return. |
Tests that expose frontend failures
- Late success: enter
a, then quicklyap. The 50 msapresponse arrives before the 300 msaresponse. Two results must remain after both finish. - Loading ownership: enter
b, then quicklya. When the older 50 ms request finishes, the current 300 ms request must still show loading. - Late failure: check fail-next, enter
b, then quicklya. The obsolete failure must not show Retry or replace the current loading status. - Usability: use Tab and Enter to activate Clear and Retry. Clear restores all results and focuses the query. Retry keeps the query and returns focus there. Repeat at a narrow viewport.
Choose the next practice session from evidence
After a mock, record the first failure that blocked progress. Was it JavaScript behavior, React state, browser tooling, CSS layout, accessibility, or the algorithm itself? Reproduce that failure in a small exercise, fix it, and repeat the original task with one changed requirement.
For supporting explanation drills, use the JavaScript questions for language and browser concepts and the React JS questions for state and rendering decisions. Attempt a question before reading its answer and check whether you can explain the behavior without hints. Free-preview questions expose their answers; other full answers require member access and may show only an opening preview. The worked example in this article is available in full. If the confirmed round starts from an existing repository, the guide to practical coding interviews adds a broader build, test, and debug workflow.
Keep the confirmed format visible and use each timed attempt to choose the next useful hour. Re-test the weakness you repaired before adding another resource.