GAP·MAP
← all posts
July 11, 2026questions

"Frontend interview questions: JS internals, CSS, performance"

A frontend loop looks broad and turns out narrow. The same clusters come back: a JavaScript internals round that filters on mechanism, a CSS round that filters on the box and cascade models, a performance round graded on how you diagnose, and an accessibility pass that now decides senior offers. This list comes from the coverage maps behind our modules and real loop reports.

Where each round tends to land:

ClusterTypical questionThe lesson
JS internals"what does this log?" (micro vs macrotask order)Event loop
JS internalsvar-in-loop, then "fix it three ways"Closures
JS internalsextract a method, call it, predict thisthis & binding
JS internals0 == "0", [] == ![], the falsy listType coercion
CSSwhy z-index: 999 does nothingCSS layout
CSS@layer vs !important vs inlineSpecificity & cascade
CSSwhy flex: 1 items refuse to shrinkFlexbox & Grid
Performance"the page feels janky, find out why"Rendering performance
a11ybuild an accessible dialog / menuAccessibility

The JavaScript internals cluster

This round decides your band faster than any other. Interviewers plant an output puzzle and listen for whether you can name the machine behind the result.

"What does this log?"

The ordering question hinges on one rule: .then callbacks are microtasks that drain completely before the next macrotask (setTimeout) runs.

console.log("A");
setTimeout(() => console.log("B"), 0);
Promise.resolve().then(() => console.log("C"));
console.log("D");
// A D C B

Follow-up "where does requestAnimationFrame fit, and does await Promise.resolve() yield to a macrotask?" It does not: awaiting an already-resolved promise costs one microtask, so a loop of them never lets a click through.

Study Event loop and Promises & async.

The var loop and the closure that pins memory

for (var i...) shares one binding across every callback, so three timers all log 3; let mints a fresh binding per iteration. The senior version is memory: a live handler keeps its whole captured scope reachable, which is how detached DOM nodes leak.

Trap "closures copy the value at creation time." They reference live bindings, so a variable reassigned after capture reads the new value.

Study Closures & scope.

Extract a method, lose this

this is set at the call site, not where the function was written. Pull a method off its object and the dot is gone.

"use strict";
const user = { name: "Ann", hi() { console.log(this.name); } };
setTimeout(user.hi, 0);              // WRONG: this is undefined, TypeError
setTimeout(() => user.hi(), 0);      // RIGHT: wrapper reads user at call time
setTimeout(user.hi.bind(user), 0);   // RIGHT: context frozen at bind

The follow-up that separates middles: an arrow method on an object literal (bad: () => this.x) sees the enclosing scope, never the object, and .bind() on an arrow is silently ignored. Prototype methods are the sibling topic here, since class methods live unbound on the prototype and new wires the chain.

Study this & binding and Prototypes & classes.

Coercion as a filter

== converts before comparing, which is why 0 == "0" is true, null == undefined is true, and null == 0 is false (null and undefined skip numeric conversion). The point they grade is the ToPrimitive chain behind [] + {}. TypeScript is the next question: types erase at runtime, so as converts nothing and these JS rules still govern the values.

Study Type coercion and TypeScript essentials.


CSS: the box model and the cascade

CSS rounds fail candidates who treat the language as a bag of properties. Two mental models carry the whole round.

"Why does z-index: 999 do nothing?"

Because z-index only competes among siblings inside the same stacking context, and an ancestor with opacity < 1, a transform, or will-change created a new context that traps the element. Same model, another bug: z-index: 0 and z-index: auto paint identically, but 0 creates a stacking context and quietly fences its descendants.

Study CSS layout model.

Specificity is step three, not step one

Candidates reach for specificity math first. The real sort is origin and importance, then @layer order, then specificity, then source order. :is() takes the specificity of its most specific argument; :where() is always zero. !important only compares against other !important declarations.

Red flags "raise the specificity until it wins" as a first instinct, and not knowing that unlayered author styles beat every layered one.

Study Specificity & the cascade.

The min-width: auto overflow trap

Trap a flex: 1 item that refuses to shrink below its content and blows the row out.

.item { flex: 1; }               /* WRONG: default min-width:auto floors it at min-content */
.item { flex: 1; min-width: 0; } /* RIGHT: now it can shrink past a long URL or <pre> */

The grid twin: 1fr is really minmax(auto, 1fr), so equal columns that must ignore content need minmax(0, 1fr). When to reach for grid (tracks first, both axes align) versus flex (one axis, content-out) is the judgment they probe.

Study Flexbox & Grid.


Performance: graded on diagnosis

The strongest signal here is naming the phase before naming a fix. Vague answers ("add memo, lazy-load stuff") read as habit.

A loading question turns on Core Web Vitals at p75 of real users: LCP under 2.5s, INP under 200ms, CLS under 0.1. The wound they wait for is loading="lazy" on the LCP hero, which defers the fetch until layout confirms it is on-screen. A runtime question turns on the main thread: a task over 50ms is a long task, and INP is input delay plus processing plus the next frame's presentation, so you diagnose the phase first. Both sit on one pipeline, where reading offsetWidth after a write forces synchronous layout and interleaving reads and writes in a loop thrashes it.

Follow-up "you virtualized the list and INP is still bad." Virtualization moves DOM work, not handler work; if the cost is in the keypress handler, windowing changes nothing there.

Study Loading performance, Rendering performance, and the pipeline underneath both, the Critical rendering path.


Accessibility and security, the rising axes

Two topics that used to be nice-to-have now gate senior frontend offers.

Accessibility questions start from one rule: a role is a promise about what gets announced, nothing else. <div role="button"> still owes you Tab focus, Enter and Space handling, and a visible focus ring. The build task is usually a dialog (focus in, trapped, Escape closes, focus restored to the invoker) or a menu with roving tabindex. Security runs parallel: CORS is the server opting into relaxed reads and is not a CSRF defense, XSS lives in dangerouslySetInnerHTML and javascript: hrefs, and token storage trades localStorage (XSS theft) against httpOnly cookies (CSRF returns).

Study Accessibility & ARIA and Web security.

Red flags across the loop: predicting output without naming microtasks, raising specificity instead of reading the cascade, "fixing" jank by sprinkling useMemo before measuring a phase, and shipping <div onclick> with no keyboard path. Any one of them caps the round regardless of how fluent the rest sounded.

Every module linked above runs from the junior floor to senior signals, with a diagnostic that tells you which band you clear today.

was this useful?

beta

The interviewer part is in the works.

The diagnostic, personal maps, and AI mock interviews are being finished right now. The notes stay free either way. Leave an email and you'll get the first-cohort invite, plus a month of Pro when it opens.