Skip to main content

Prepare · Theory

Frontend interview theory questions and answers

23 questions on HTML, CSS and JavaScript, each answered in full with the key points an interviewer is listening for. Want to rehearse saying them out loud? Open the interview practice mode.

accessibility

What would you check to make a page accessible?

Start with structure: real semantic elements, one logical heading order, and labels tied to inputs with for/id. Then keyboard: everything reachable by Tab, in a sensible order, with a visible focus ring and no traps. Then perception: sufficient colour contrast, meaningful alt text, and never colour alone to convey meaning. Reach for ARIA only when no native element does the job — a wrong ARIA role is worse than none.

  • Semantic structure and correct heading order
  • Full keyboard operability with visible focus
  • Contrast, alt text, no colour-only meaning
  • Native elements before ARIA
Read the full answer

How do you decide what to put in an image's alt attribute?

Ask what the image is doing. If it carries information, describe that information rather than the picture. If it's a link or button, describe the destination or action. If it's purely decorative, use an empty alt="" so screen readers skip it — omitting the attribute entirely is worse, because some readers then announce the filename. Don't prefix with "image of"; the element already says that.

  • Convey the image's purpose, not its appearance
  • Functional images describe the action or destination
  • Decorative images get alt="", never a missing attribute
  • No "image of" prefix
Read the full answer

array-methods

When would you use map, filter and reduce?

map transforms every item and returns a new array of the same length. filter keeps the items that pass a test, so the result is the same or shorter. reduce folds the array into a single accumulated value — a total, a grouping, or even a rebuilt object. All three return new values rather than mutating, which is what makes them safe to chain.

  • map: same length, transformed items
  • filter: subset, same items
  • reduce: many values folded into one
  • None mutate the source array
Read the full answer

What is the difference between == and ===?

=== compares type and value with no conversion. == coerces the operands to a common type first, which produces surprises like '' == 0 and '1' == 1 being true. Use === by default. The one common exception is x == null, which is a concise way to test for null or undefined together.

  • === strict: no coercion
  • == coerces, producing non-obvious truths
  • Default to ===
  • x == null is a deliberate, useful exception
Read the full answer

async

How does JavaScript run asynchronous code if it only has one thread?

The engine runs one call stack. Anything asynchronous is handed to the host (browser or Node), which does the work elsewhere and queues a callback when it finishes. Once the stack empties, the event loop drains the microtask queue first (promise callbacks, queueMicrotask), then takes one macrotask (setTimeout, I/O, events) and repeats. So JavaScript never blocks on the waiting — it blocks only while your synchronous code is on the stack.

  • Single call stack; the host handles the actual waiting
  • Microtasks (promises) drain fully before the next macrotask
  • setTimeout(fn, 0) still waits for the stack to clear
  • Long synchronous work freezes the UI — that's what blocks
Read the full answer

Why did promises replace callbacks for async work?

Callbacks nest: each dependent step goes inside the previous one, so error handling has to be repeated at every level and the code grows sideways. A promise is a value you can return, chain and pass around, so steps compose in a flat sequence with one .catch() at the end. async/await then lets that chain read like synchronous code while keeping try/catch for errors.

  • Callbacks nest and duplicate error handling
  • Promises are first-class values — returnable and composable
  • One .catch() covers the whole chain
  • async/await is syntax over the same promises
Read the full answer

box-model

Explain the CSS box model and the effect of box-sizing.

Every element is a content box wrapped in padding, then border, then margin. By default width sets only the content, so padding and border are added on top and the element renders wider than the number you wrote. box-sizing: border-box makes width include padding and border, which is why most projects apply it globally. Margins sit outside the box entirely and don't count toward its size.

  • content → padding → border → margin
  • content-box: width excludes padding/border
  • border-box: width includes them
  • Margin is always outside the box
Read the full answer

What is the difference between display block, inline and inline-block?

block takes the full available width, starts on a new line, and honours width and vertical margins. inline flows within text, ignores width/height, and only applies horizontal margins and padding for layout purposes. inline-block flows inline like text but accepts width, height and vertical spacing — which is why it was the old go-to for laying items in a row before flexbox.

  • block: full width, new line, sizing honoured
  • inline: flows in text, ignores width/height
  • inline-block: flows inline but is sizeable
  • display: none removes it from layout entirely
Read the full answer

What is the difference between em and rem?

rem is always relative to the root font size, so it stays predictable wherever the element sits. em is relative to the element's own font size, which means it compounds when nested — a common cause of text that shrinks or grows unexpectedly several levels deep. Use rem for type scales and spacing, and em when you deliberately want a value to scale with the component's own text.

  • rem: relative to root, predictable
  • em: relative to the element, compounds when nested
  • rem for global scale, em for component-relative sizing
  • Both respect the user's browser font-size setting
Read the full answer

closures

What is a closure, and where would you actually use one?

A closure is a function that keeps access to the variables of the scope it was defined in, even after that outer function has returned. Practically it's how you get private state: a counter that owns its own count, a debounce that remembers its timer, or a factory that bakes in configuration. The classic bug it explains is a var loop — all the handlers close over one shared binding, so they all report the final value.

  • Function + the scope it was created in, retained after return
  • Gives private state without a class
  • Powers debounce/throttle, memoisation, factories
  • Explains the var-in-a-loop bug; let gives a per-iteration binding
Read the full answer

What is the difference between var, let and const?

var is function-scoped and hoisted as undefined, so it's readable before its declaration and leaks out of blocks. let and const are block-scoped and sit in the temporal dead zone until declared, so reading them early throws. const additionally prevents reassignment of the binding — it does not freeze the value, so a const object's properties can still change.

  • var: function-scoped, hoisted as undefined
  • let/const: block-scoped, temporal dead zone
  • const blocks reassignment, not mutation
  • Default to const, reach for let when you must reassign
Read the full answer

What is hoisting?

Declarations are registered when a scope is created, before any code in it runs. Function declarations are fully available, so you can call them above where they're written. var is registered but initialised to undefined. let and const are registered but unusable until their line executes — that gap is the temporal dead zone, and touching them inside it throws a ReferenceError.

  • Declarations registered at scope creation, not assignments
  • Function declarations are callable before their line
  • var reads as undefined; let/const throw in the TDZ
  • Function expressions follow their variable's rules
Read the full answer

debounce

What is the difference between debounce and throttle?

Debounce waits for activity to stop: the timer restarts on every call, so the function runs once after the quiet period. Throttle guarantees a steady rate: it runs at most once per interval no matter how often it's called. Debounce suits search-as-you-type and resize-end work; throttle suits scroll handlers and anything that must keep updating while the user is still going.

  • Debounce: runs after activity stops; timer resets each call
  • Throttle: runs at a fixed maximum rate
  • Debounce → typeahead, resize end, autosave
  • Throttle → scroll, drag, pointer tracking
Read the full answer

dom-events

Explain event bubbling, and how delegation uses it.

An event first travels down from the root to the target (capturing), fires on the target, then travels back up through the ancestors (bubbling). Delegation exploits the bubbling phase: instead of a listener per item, you put one on a shared parent and read event.target to find what was actually clicked. That keeps memory flat and — crucially — keeps working for items added to the DOM later.

  • Capture down, fire on target, bubble up
  • One parent listener instead of one per child
  • Works for dynamically added elements
  • event.target is the source; currentTarget is the listener's element
Read the full answer

flexbox

When would you choose Flexbox over CSS Grid?

Flexbox lays out along one axis and sizes items from their content, so it suits a row of buttons, a nav bar, or anything that should distribute space in a single direction. Grid defines rows and columns up front, so it suits page-level and two-dimensional layouts where things must line up in both directions. They aren't rivals — grid the page, flex the components inside it.

  • Flexbox: one axis, content-driven
  • Grid: two axes, layout defined up front
  • Grid for page structure, flex for components
  • They nest together routinely
Read the full answer

perf

What causes a reflow, and why does it matter for performance?

A reflow is the browser recomputing geometry — positions and sizes — and it cascades to affected elements. Changing layout properties triggers it, and reading a measured value like offsetHeight forces it to happen immediately. The expensive pattern is alternating writes and reads in a loop, which is layout thrashing. Batch your reads, then your writes, and prefer transform and opacity, which can be composited without reflow.

  • Reflow = recompute layout geometry; repaint = redraw pixels
  • Reading offsetHeight/getBoundingClientRect forces sync layout
  • Read-then-write batching avoids thrashing
  • transform/opacity animate without reflow
Read the full answer

What is the difference between a plain script tag, defer and async?

A plain <script> blocks HTML parsing while it downloads and runs. defer downloads in parallel and runs after parsing completes, preserving document order — the right default for app code. async also downloads in parallel but runs the moment it arrives, so order isn't guaranteed; it suits independent third-party snippets like analytics.

  • Plain: blocks the parser
  • defer: parallel download, runs after parse, keeps order
  • async: parallel download, runs on arrival, order not guaranteed
  • defer for app code, async for independent third-party
Read the full answer

A page loads slowly. How do you diagnose and improve it?

Measure before changing anything — a Lighthouse run and the network waterfall tell you whether the cost is bytes, requests, or blocking work. Common wins: serve modern image formats at the right size, lazy-load below-the-fold media, defer non-critical JavaScript, subset fonts and use font-display: swap, and cache static assets aggressively. Then re-measure against a real metric like LCP rather than a feeling.

  • Measure first: Lighthouse, network waterfall
  • Images: right size, modern format, lazy below the fold
  • Defer non-critical JS; trim and subset fonts
  • Verify against LCP/CLS, not impressions
Read the full answer

position

What do the CSS position values do?

static is the default and ignores offsets. relative keeps the element in flow but shifts it visually and creates a containing block for absolute children. absolute removes it from flow and positions it against the nearest positioned ancestor. fixed positions against the viewport and doesn't scroll. sticky behaves as relative until a scroll threshold, then pins — and it needs an offset like top: 0 to do anything.

  • relative: stays in flow, becomes a positioning context
  • absolute: out of flow, relative to nearest positioned ancestor
  • fixed: relative to viewport
  • sticky: relative until threshold, then pinned — needs an offset
Read the full answer

responsive

How do you approach responsive design, and what does mobile-first mean?

Mobile-first means the base styles target the smallest screen and min-width queries add complexity as space allows, so the simplest CSS is what small devices download. Beyond queries, a lot of responsiveness needs no breakpoints at all: fluid units, max-width, clamp(), and grid's auto-fit with minmax() let layouts adapt continuously. Breakpoints should come from where your content breaks, not from device names.

  • Base styles small, min-width queries add up
  • Prefer intrinsic techniques: clamp, minmax, auto-fit
  • Breakpoints follow content, not devices
  • Needs a correct viewport meta tag
Read the full answer

semantics

Why use semantic HTML instead of divs everywhere?

Semantic elements describe what the content is, which the browser then exposes to assistive technology as real landmarks and roles — a screen reader user can jump straight to nav or main. It also gives free behaviour: a button is focusable and fires on Enter and Space, where a clickable div does none of that unless you rebuild it. Search engines and future maintainers both read the structure more reliably too.

  • Elements convey meaning, not just appearance
  • Landmarks let assistive tech navigate directly
  • Native elements bring keyboard behaviour for free
  • Better for SEO and for maintainers
Read the full answer

specificity

How does CSS decide which rule wins?

First by origin and importance, then specificity, then source order. Specificity counts inline styles, then ids, then classes/attributes/pseudo-classes, then elements — a single id outranks any number of classes. If specificity ties, the later rule wins. !important overrides the normal cascade and is usually a sign the selector should have been more specific instead.

  • Order: importance → specificity → source order
  • inline > id > class/attribute/pseudo-class > element
  • Ties broken by whichever comes last
  • !important is an escape hatch, not a tool
Read the full answer

this-binding

How is the value of `this` determined?

For a normal function it's decided by how it's called, not where it's written: called as obj.method() it's obj; called bare it's undefined in strict mode; with new it's the new instance; and call/apply/bind set it explicitly. Arrow functions have no own this — they use the enclosing scope's, which is exactly why they're the safe choice for callbacks inside a method.

  • Determined at call time for normal functions
  • Method call → the object; bare call → undefined in strict mode
  • new → the instance; call/apply/bind → explicit
  • Arrows inherit this from the enclosing scope
Read the full answer

Reading them is not the same as saying them

Interview practice mode times your spoken answer and checks it against the points above.

Practise out loud