Interview Prep
Frontend Interview Questions
HTML, CSS, JavaScript, the browser, and the frameworks built on them.
12 of 12 Frontend questions shown. Answers are hidden by default — try each one before revealing it.
1.What is the output order when this code runs in a browser? console.log("A"); setTimeout(() => console.log("B"), 0); Promise.resolve().then(() => console.log("C")); console.log("D");
MediumShow answer and explanation
Synchronous statements run first and in order, so A and D log immediately. Once the call stack is empty, the event loop drains the entire microtask queue before touching the next macrotask: the resolved promise's then callback is a microtask, so C logs next. Only after the microtask queue is empty does the loop move to the task (macrotask) queue and run the setTimeout callback, logging B last. The tempting wrong answer, A, D, B, C, assumes a zero-delay timer fires before a pending promise callback, but microtasks always drain before the next macrotask regardless of the timer's delay value.
javascriptevent-loopasync
2.Given three CSS rules that all match the same element, <button id="submit" class="btn primary">: #submit { color: blue; } .btn.primary { color: green; } button { color: red; } Which color wins?
EasyShow answer and explanation
Specificity is compared as a tuple of (ID count, class/attribute/pseudo-class count, type/pseudo-element count), compared left to right. #submit scores (1,0,0), .btn.primary scores (0,2,0), and button scores (0,0,1). A single ID always outranks any number of classes, because the comparison is positional, not a total point sum, so blue wins regardless of source order. Source order, which the red option leans on, only breaks ties when two rules have identical specificity, which is not the case here, and no rule needs !important to resolve an already-unambiguous specificity comparison.
cssspecificitycascade
3.const counter = { count: 0, incrementRegular: function () { setTimeout(function () { this.count++; console.log(this.count); }, 0); }, incrementArrow: function () { setTimeout(() => { this.count++; console.log(this.count); }, 0); }, }; counter.incrementRegular(); counter.incrementArrow(); What happens when both methods run (plain non-strict-mode script, in a browser)?
HardShow answer and explanation
A plain function's this is determined by how it is called, not by where it is defined. setTimeout invokes its callback as a bare function call, so in non-strict mode this inside that callback is the global object, meaning this.count++ operates on the global object's count property, not counter.count, producing NaN (undefined incremented) and leaving counter.count at 0. Arrow functions have no this of their own; they close over this from the enclosing lexical scope, which here is incrementArrow's own this (counter, because incrementArrow was itself called as counter.incrementArrow()), so the arrow callback correctly updates counter.count to 1. The TypeError option describes what would happen in strict mode or inside a class body, where the default this is undefined instead of the global object, which is a common source of confusion but does not match this plain script.
javascriptthisclosuresarrow-functions
4.A div has width: 200px; padding: 20px; border: 5px solid black; and the default box-sizing: content-box. What is the div's total rendered width, from outer border edge to outer border edge?
EasyShow answer and explanation
With the default content-box, the width property sizes only the content area; padding and border are added on top of it, not carved out of it. Total width = 200 content + 20 left padding + 20 right padding + 5 left border + 5 right border = 250px. 200px would be correct only if box-sizing were border-box instead, in which case the specified width would already include the padding and border.
cssbox-modellayout
5.for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0); } What gets logged?
MediumShow answer and explanation
var is function-scoped (or global-scoped here), so there is a single i binding shared by every iteration and every closure created in the loop. The loop runs to completion synchronously before any setTimeout callback gets a chance to run, so by the time the three callbacks actually fire, i has already reached 3. All three arrow functions close over that same, final value. Declaring the loop variable with let instead of var creates a fresh binding per iteration, which would produce 0, 1, 2, the tempting wrong answer.
javascriptclosuresevent-loop
6.In a React list rendered with .map(), why is a stable, unique id generally recommended as the key prop instead of the array index?
MediumShow answer and explanation
React uses the key to match each rendered element to its previous version during reconciliation. If the key is just the position in the array and the array's order changes, an item can end up matched against a different previous element than the one it logically corresponds to, so component state or DOM node state (an uncontrolled input's value, for example) can appear attached to the wrong row after the list changes. React silently coerces numeric keys to strings, so that is not an issue, keys are purely an internal reconciliation hint and are never transmitted anywhere, and there is no special hashing cost tied to numeric keys. Index keys are safe only for lists that are static and never reorder, filter, or splice.
reactkeysreconciliation
7.Explain the CSS box model: what are its four parts, and how does the box-sizing property change how width and height are calculated?
EasyShow model answer
Every rendered element in CSS is a rectangular box made of four layered regions, counted from the inside out: the content box, where text or child elements sit and whose size is driven by the width and height properties; padding, transparent space between the content and the border that is still part of the element's own background; the border, a line (visible or not) drawn around the padding; and the margin, transparent space outside the border that separates the element from its neighbors and is never painted with the element's background. The box-sizing property controls what width and height actually measure. The default value, content-box, means width and height set only the content region, so padding and border are added on top of that figure: a div with width: 200px, 20px of padding, and a 5px border ends up 250px wide on screen. box-sizing: border-box instead makes width and height include the padding and border, so a border-box div with the same 200px width, padding, and border still renders at exactly 200px wide, with the content area shrinking to make room. Margin is excluded from the width calculation in both modes, since margin is about spacing between boxes rather than the size of the box itself. Because content-box's growth is easy to get wrong the moment padding or a border is added to a fixed-width component, many teams apply a global reset such as setting box-sizing: border-box on every element so that width and height stay predictable.
A strong answer touches
- content, padding, border, margin, from inside out
- content-box (default) adds padding and border on top of the specified width
- border-box includes padding and border inside the specified width
- margin is excluded from the width calculation in both modes
- why teams commonly reset box-sizing to border-box globally
This is one genuine answer, not the only correct one — compare it with your own reasoning rather than matching it word for word.
cssbox-model
8.Explain how the browser's event loop handles asynchronous code. What is the difference between the microtask queue and the macrotask (task) queue, and which one runs first?
MediumShow model answer
JavaScript runs on a single thread, so the browser uses an event loop to interleave synchronous execution with asynchronous work instead of blocking on it. When the engine hits something asynchronous, such as a setTimeout, a DOM event, a fetch resolving, or a promise settling, it does not run the associated callback immediately; it schedules that callback and keeps running the current synchronous code to completion. Once the call stack is empty, the event loop looks for the next thing to run, and it treats two kinds of scheduled work differently. Microtasks, which include promise then, catch, and finally callbacks, queueMicrotask calls, and the continuation of an async function after an await, go into the microtask queue. Macrotasks, which include setTimeout and setInterval callbacks, DOM events, and I/O completions, go into the task queue. After each macrotask finishes, including the initial script itself, the event loop fully drains the microtask queue, including any new microtasks that get added while draining, before it is allowed to render a frame or pick up the next macrotask. Only when the microtask queue is completely empty does the loop move on. That ordering is why a promise callback scheduled after a setTimeout(fn, 0) still logs before the timeout callback: the promise callback is a microtask and always gets priority over the next macrotask, no matter how small the timer's delay is set to.
A strong answer touches
- JavaScript is single-threaded; async work is scheduled rather than run inline
- microtask queue: promise callbacks, queueMicrotask, code after await
- macrotask/task queue: setTimeout, setInterval, DOM events, I/O
- the microtask queue is fully drained (including newly queued microtasks) after each macrotask, before the next one runs
- a zero-delay setTimeout still runs after any pending promise callbacks
This is one genuine answer, not the only correct one — compare it with your own reasoning rather than matching it word for word.
javascriptevent-looppromises
9.When would you reach for CSS Grid instead of Flexbox, and when is Flexbox the better fit? Give a concrete layout example for each.
EasyShow model answer
The core distinction is dimensionality. Flexbox is a one-dimensional layout model: items are laid out along a single axis, row or column, and it excels at distributing and aligning items along that one line, with wrapped items forming a new line that is not coordinated with the previous one. Grid is two-dimensional: rows and columns are defined together as a single layout, and items can be placed and aligned against both axes at once, including spanning multiple rows or columns. A useful test is whether you care about alignment across two dimensions simultaneously. A navbar with a logo on the left, links in the middle, and a login button on the right is a one-dimensional problem, a single row where items need to be spaced out and vertically centered, so Flexbox with justify-content and align-items handles it directly with no extra markup. A full page layout with a header spanning the width, a sidebar down one side, a main content area, and a footer spanning the width is a two-dimensional problem, since rows and columns need to line up together, so Grid, defining grid-template-columns and grid-template-areas once, expresses that structure more directly than nesting several flex containers to fake it. In practice the two are often combined: Grid for the page or card-layout skeleton, and Flexbox for aligning the contents inside one grid cell, such as centering an icon and a label inside a button.
A strong answer touches
- flexbox is one-dimensional (single row or column); grid is two-dimensional (rows and columns together)
- flexbox example: a toolbar or navbar aligned along one axis
- grid example: a full page skeleton with header, sidebar, main, and footer
- grid natively supports items spanning multiple rows or columns; flexbox does not
- the two are commonly combined, grid for the outer layout and flexbox inside a cell
This is one genuine answer, not the only correct one — compare it with your own reasoning rather than matching it word for word.
cssgridflexboxlayout
10.Explain what a closure is in JavaScript, and give an example of a practical problem it solves.
MediumShow model answer
A closure is what happens when a function keeps access to the variables from the lexical scope it was defined in, even after the outer function that created that scope has already finished running. Every JavaScript function retains a reference to its surrounding scope from the moment it is created, not just while it is executing, so if an inner function is returned or handed off elsewhere and invoked later, it can still read and update the variables from the scope that enclosed it, and those variables stay alive in memory for as long as the closure that references them exists. A practical use is building private state without exposing a mutable variable at module or global scope. Consider a function createCounter that declares a local variable count set to 0, and returns an object with an increment method that adds one to count and a value method that reads it. Calling createCounter returns two functions that both close over the same count variable, and nothing outside those two functions can read or write count directly; the only way to change it is by calling increment, and each separate call to createCounter produces its own independent count. This pattern is the original basis for encapsulation in JavaScript, before classes gained private fields, and the same idea underlies memoized functions, event handler factories that remember configuration, and, conceptually, how a hook like React's useState keeps a piece of state tied to one component instance rather than a global variable.
A strong answer touches
- a closure is a function plus the lexical scope it was defined in, retained after the outer function returns
- captured variables are kept by reference, so later reads and writes see the current value
- each invocation of the outer function creates a fresh, independent closure
- practical example: private state or encapsulation, such as a counter, without a global mutable variable
This is one genuine answer, not the only correct one — compare it with your own reasoning rather than matching it word for word.
javascriptclosuresscope
11.What is event delegation, and why would you use it instead of attaching a click handler to every item in a list?
MediumShow model answer
Event delegation relies on event bubbling: when an event fires on an element, it also fires on that element's ancestors as it travels up the DOM tree. Instead of attaching a separate listener to every individual item, you attach one listener to a shared ancestor, such as the ul that contains a list of li items, and inspect event.target inside that single handler to work out which specific child was actually interacted with, often with something like event.target.closest('li'). This beats per-item listeners for a couple of concrete reasons. First, it reduces the number of listeners the browser has to track: a list with a thousand rows needs one listener instead of a thousand, which matters more as the list grows. Second, and usually more important in practice, it handles dynamic content automatically. If items are added to or removed from the list later, a delegated listener on the parent still catches clicks on the new items immediately, because the listener was never tied to the element that did not exist yet. Per-item listeners would have to be reattached to every new element manually, which is easy to forget and a common source of the bug where a click handler stops working after a list re-renders. The trade-off is that the delegated handler has to do a little more work to identify the real target and guard against clicks bubbling up from unrelated descendants, but for lists, tables, and any UI with many repeated interactive children, that cost is small compared to the simplicity it buys.
A strong answer touches
- relies on event bubbling: a listener on an ancestor also receives events from its descendants
- one listener on a shared parent instead of one per child element
- identify the actual target inside the handler, typically with event.target.closest(...)
- automatically covers dynamically added or removed children without rebinding listeners
- trade-off: the handler must filter or identify the real target itself
This is one genuine answer, not the only correct one — compare it with your own reasoning rather than matching it word for word.
javascriptdomevents
12.Explain how JavaScript determines the value of this inside a regular function. Cover the different ways a function can be called, and how arrow functions differ.
HardShow model answer
Unlike a variable, this inside a regular JavaScript function is not fixed by where the function is defined; it is determined by how the function is called, and it is re-evaluated on every call. There are four rules, useful to think of in order of how directly they set this. Default binding: a plain function called with no receiving object, such as doStuff(), gets this set to the global object in non-strict mode, or to undefined in strict mode. Implicit binding: a function called as a method of an object, such as obj.doStuff(), gets this set to that object at call time, which is why the same function can behave differently depending on which object it is called through, and why pulling a method off an object into a standalone variable and calling it separately loses that binding and falls back to default binding, a common source of bugs such as passing this.handleClick as a callback without binding it first. Explicit binding: call, apply, and bind let code set this directly regardless of how the function is later invoked, with call and apply invoking immediately with a chosen this, and bind returning a new function permanently bound to that value. New binding: calling a function with the new keyword creates a fresh object, sets this to it inside the function body, and returns that object automatically unless the function explicitly returns a different object. Arrow functions opt out of all four rules entirely: they have no this of their own, so this inside an arrow function is simply whatever this was in the enclosing lexical scope at the moment the arrow function was defined, and it cannot be changed later by call, apply, bind, or the way the arrow function is invoked. That is why arrow functions are commonly used for callbacks defined inside a method, since they keep this pointing at the enclosing object instead of resetting it.
A strong answer touches
- this is determined by the call site, not the definition site, for regular functions
- default binding (global object or undefined) versus implicit binding via obj.method()
- explicit binding with call, apply, and bind
- new binding creates a fresh object and sets this to it
- arrow functions have no this of their own; they inherit it lexically and it cannot be reassigned
This is one genuine answer, not the only correct one — compare it with your own reasoning rather than matching it word for word.
javascriptthisarrow-functionsscope
Put this into practice
Connect the idea to a worked lesson, a practical task or your interview preparation.
- LessonJavaScript Functions: Declaring, Calling, and Returning →Learn JavaScript functions from scratch - function declarations, arrow functions, parameters and default values, return values, and local versus global scope.
- Intermediate projectTo-do list that remembers →A single-page task list with filters that survives a reload, built with no framework.
- Learning roadmapFrontend Developer roadmap →Markup and styling first, then JavaScript, then types, and only then a framework — with the browser and network facts underneath them.
- Concept guideReact and Next.js →A library for building UIs out of components, and the framework that adds routing, rendering, and backend glue around it.