Interview track
Frontend Developer interview prep
A spaced-repetition deck of 121+ Frontend Developer interview questions — organised by topic and difficulty, and resurfaced right before you'd forget. Preview a few cards below, then sign in to study the whole track on an Anki-style SM-2 schedule.
Free · sign in with GitHub · your progress stays yours.
What's covered
Every topic in this track, grouped the way you'd study it.
Behavioral
35 cardsHTML & CSS
12 cardsJavaScript
14 cardsTypeScript
10 cardsReact & Frameworks
14 cardsBrowser & Performance
10 cardsAccessibility
8 cardsTooling, Build & Testing
10 cardsFrontend System Design
8 cardsSample questions
A few cards from the deck — reveal each answer, then sign in to study the full set on a schedule.
What is semantic HTML and why does it matter (accessibility, SEO, maintainability)?
What is semantic HTML and why does it matter (accessibility, SEO, maintainability)?
Short answer: Semantic HTML means using tags for their meaning (<nav>, <article>, <button>) instead of <div> for everything. Browsers and assistive tech understand the document structure, which buys you accessibility, SEO, and readable code.
In depth:
- Accessibility (a11y) — screen readers build the accessibility tree from tags:
<nav>,<main>,<h1>enable landmark and heading navigation. Div soup is announced as unstructured text. - SEO — search engines treat
<article>,<h1>–<h6>,<time>as structure and content-importance signals. - Maintainability —
<header>/<footer>/<aside>self-document the markup; you don't read class names to learn a block's role. - Free behavior —
<button>is focusable and reacts to Enter/Space,<a>navigates,<details>toggles — no JS required.
<!-- ❌ div soup -->
<div class="nav"><div class="link" onclick="go()">Home</div></div>
<!-- ✅ semantic -->
<header>
<nav aria-label="Primary">
<a href="/">Home</a>
</nav>
</header>
<main>
<article>
<h1>Article title</h1>
<time datetime="2026-06-30">June 30, 2026</time>
</article>
</main>
⚠️ Common mistake: making a clickable <div onclick> instead of a <button> — you lose focus, keyboard handling, and the screen-reader role.
Explain the CSS box model and how box-sizing: border-box changes sizing.
Explain the CSS box model and how box-sizing: border-box changes sizing.
Short answer: Every element is nested layers: content → padding → border → margin. By default (content-box) width sizes only the content, and padding plus border are added on top. border-box makes padding and border fit inside the declared width.
In depth:
- content — the content area, which
width/heightrefer to undercontent-box. - padding — inner spacing, painted with the element's background.
- border — the frame between padding and margin.
- margin — outer spacing, transparent; adjacent vertical margins collapse (margin collapse).
┌──────────── margin ────────────┐
│ ┌───────── border ─────────┐ │
│ │ ┌────── padding ─────┐ │ │
│ │ │ content │ │ │
│ │ └────────────────────┘ │ │
│ └──────────────────────────┘ │
└────────────────────────────────┘
/* content-box: actual width = 200 + 2*16 + 2*2 = 236px */
.a { box-sizing: content-box; width: 200px; padding: 16px; border: 2px solid; }
/* border-box: actual width is exactly 200px, padding/border inside */
.b { box-sizing: border-box; width: 200px; padding: 16px; border: 2px solid; }
/* common reset */
*, *::before, *::after { box-sizing: border-box; }
⚠️ Common mistake: setting width: 100% plus padding under content-box — the box overflows its container; border-box fixes it.
What's the difference between block, inline, and inline-block, and what is normal document flow?
What's the difference between block, inline, and inline-block, and what is normal document flow?
Short answer: Block elements take the full line width and accept any size/spacing; inline elements sit within a line, sized by content, and ignore width/vertical margins; inline-block sits inline but with controllable sizing. Normal flow is the order in which elements lay out top-to-bottom (block) and left-to-right (inline) without positioning.
In depth:
- block (
<div>,<p>,<section>) — starts a new line, defaults to full width, honorswidth/heightand all margins/padding. - inline (
<span>,<a>,<strong>) — flows within text;width/heightand vertical margins are ignored, horizontal ones apply. - inline-block — sits inline like inline, but with full sizing and spacing — the classic for "buttons" in a row.
- Normal flow — the default layout;
position,float, flex/grid take elements out of it.
| display | Line break | width/height | Vert. margin |
|---|---|---|---|
| block | yes | yes | yes |
| inline | no | no | no |
| inline-block | no | yes | yes |
.tag { display: inline-block; width: 80px; padding: 4px 8px; }
⚠️ Common mistake: setting width/height on a pure inline element and expecting an effect — they don't exist there; use inline-block or block.
What is hoisting, and how do var, let, and const differ in scope and TDZ?
What is hoisting, and how do var, let, and const differ in scope and TDZ?
Short answer: Hoisting is moving declarations to the top of their scope at compile time. var is hoisted and initialized to undefined (function scope), while let/const are hoisted but stay in the TDZ (temporal dead zone) until the declaration line and have block scope.
In depth:
- var — function scope, readable as
undefinedbefore declaration, re-declarable. - let — block scope, in the TDZ until declaration, reassignment allowed.
- const — like
letbut no reassignment (the object's value itself is still mutable). - TDZ — accessing a
let/constbefore declaration throwsReferenceErrorinstead of returningundefined.
console.log(a); // undefined — var hoisted
var a = 1;
console.log(b); // ReferenceError — b in TDZ
let b = 2;
| Scope | Before declaration | Reassign | |
|---|---|---|---|
| var | function | undefined |
yes |
| let | block | TDZ → error | yes |
| const | block | TDZ → error | no |
⚠️ Common mistake: believing let/const "aren't hoisted." They are — but accessing them before initialization throws because of the TDZ.
What's the difference between == and ===? Explain type coercion, truthy/falsy, and common gotchas.
What's the difference between == and ===? Explain type coercion, truthy/falsy, and common gotchas.
Short answer: === compares without coercion (strict equality), == first coerces operands to a common type (loose). Because coercions are unpredictable, code almost always uses ===. Eight values are falsy; everything else is truthy.
In depth:
- === — equal only if both type and value match.
- == — coerces types by intricate rules (number ↔ string, to-primitive, etc.).
- Falsy — exactly eight:
false,0,-0,0n,'',null,undefined,NaN. Everything else is truthy.
| Expression | Result | Why |
|---|---|---|
0 == '' |
true |
both → number 0 |
0 == '0' |
true |
'0' → 0 |
null == undefined |
true |
special rule |
null == 0 |
false |
null equals only undefined |
NaN === NaN |
false |
NaN equals nothing |
[] == ![] |
true |
![]→false→0, []→''→0 |
⚠️ Common mistake: seeing if (x == null) and assuming it's a bug. It's actually an idiom — it catches both null and undefined. It's == with numbers and strings that breeds bugs; use ===.
Why use TypeScript over JavaScript, and what is structural (duck) typing?
Why use TypeScript over JavaScript, and what is structural (duck) typing?
Short answer: TypeScript adds static type checking on top of JS: errors are caught in the editor and at build time instead of at runtime. Its typing is structural — compatibility is decided by an object's shape (its set of fields), not by the type's name or an explicit implements.
In depth:
- What TS buys you — autocompletion and navigation, safe refactoring, self-documenting contracts, catching typos and
undefinedbefore running. - Structural vs nominal — in Java/C# types match by name (nominal typing). In TS, if an object has the required fields it fits, even if it was created with no knowledge of the target type.
- Type erasure — types exist only at compile time; at runtime it is plain JS. You can't
instanceof-check an interface.
interface Point { x: number; y: number }
function len(p: Point): number {
return Math.hypot(p.x, p.y);
}
// the object never declared implements Point, but its shape fits
const v = { x: 3, y: 4, label: "v" };
len(v); // OK — structural typing: the extra label field is fine
⚠️ Common mistake: assuming TS protects you at runtime. Data from an API must be validated (zod, etc.) — the compiler takes your annotations on faith.
Ready to make it stick?
Start your first session in under a minute. Your future self, mid-interview, will thank you.