
HTML, CSS, and JavaScript are fundamental skills for any aspiring web developer, and securing a job in this field can be a challenging endeavor, especially for beginners. A critical part of the interview process is the technical interview, where your proficiency in these core web technologies is thoroughly assessed.
To help you prepare and boost your confidence, we’ve compiled a list of the top 50 essential interview questions and answers covering HTML, CSS, and JavaScript that are frequently asked in interviews. These are followed by two supplementary sections: additional HTML-focused questions that are increasingly common in modern front-end rounds, and integration questions that examine how HTML, CSS, and JavaScript interact in practice. Where relevant, questions include what interviewers look for in a strong answer, as well as nuances that are commonly overlooked in surface-level explanations.
If you're looking for additional JavaScript interview preparation materials, also check out these resources:
Hoisting refers to JavaScript's behavior of moving variable and function declarations to the top of their scope during the compilation phase. While declarations are hoisted, initializations are not.
console.log(foo); // undefinedvar foo = 1;console.log(foo); // 1
Visualized as:
var foo;console.log(foo); // undefinedfoo = 1;console.log(foo); // 1
let, const, and classThese are hoisted but remain uninitialized, leading to a ReferenceError if accessed before declaration.
console.log(bar); // ReferenceErrorlet bar = 'value';
Function declarations are fully hoisted (both declaration and definition), while function expressions are only partially hoisted (declaration without initialization).
console.log(declared()); // Worksfunction declared() {return 'Declared function';}console.log(expr); // undefinedconsole.log(expr()); // TypeError: expr is not a functionvar expr = function () {return 'Function expression';};
Import statements are hoisted, making imported modules available throughout the file.
import foo from './foo';foo.doSomething(); // Accessible
Explore the concept of "hoisting" in JavaScript on GreatFrontEnd
let, var, and const Differ?var: Function-scoped or globally scoped.let and const: Block-scoped, confined to their nearest enclosing block.function test() {var a = 1;let b = 2;const c = 3;}console.log(a); // ReferenceErrorconsole.log(b); // ReferenceErrorconsole.log(c); // ReferenceError
var and let: Can be declared without initialization.const: Must be initialized during declaration.var a;let b;const c; // SyntaxError: Missing initializer
var: Allows redeclaration in the same scope.let and const: Redeclaration is not allowed.var x = 1;var x = 2; // Validlet y = 1;let y = 2; // SyntaxError
var and let: Reassignment is allowed.const: Reassignment is not allowed.const z = 1;z = 2; // TypeError
var: Hoisted and initialized to undefined.let and const: Hoisted but not initialized, causing a ReferenceError if accessed before declaration.console.log(a); // undefinedvar a = 1;console.log(b); // ReferenceErrorlet b = 2;
Explore the differences between let, var, and const on GreatFrontEnd
== and ===?==):42 == '42'; // true0 == false; // truenull == undefined; // true
===):42 === '42'; // false0 === false; // falsenull === undefined; // false
Prefer === to avoid unexpected behavior caused by type coercion, except when comparing against null or undefined.
var value = null;console.log(value == null); // trueconsole.log(value === null); // true
Explore the difference between == and === on GreatFrontEnd
The event loop allows JavaScript to handle asynchronous tasks on a single thread, ensuring smooth execution without blocking.
setTimeout and UI events.Promise callbacks.console.log('Start');setTimeout(() => console.log('Timeout'), 0);Promise.resolve().then(() => console.log('Promise'));console.log('End');
Output:
StartEndPromiseTimeout
Explore the event loop in JavaScript on GreatFrontEnd
Event delegation uses a single event listener on a parent element to manage events on its child elements. This approach takes advantage of event bubbling, improving efficiency.
document.getElementById('parent').addEventListener('click', (event) => {if (event.target.tagName === 'BUTTON') {console.log(`Clicked ${event.target.textContent}`);}});
Explore event delegation in JavaScript on GreatFrontEnd
this Work in JavaScript?The value of this depends on how a function is invoked:
window in browsers) in non-strict mode. In strict mode — which is the default inside ES modules and class bodies — the default binding is undefined.call, apply, or bind.this from the surrounding scope.const obj = {name: 'Alice',greet() {console.log(this.name);},};obj.greet(); // Alice
Explore how this works in JavaScript on GreatFrontEnd
localStorage, and sessionStorage Differ?document.cookie = 'token=abc123; expires=Fri, 31 Dec 2025 23:59:59 GMT; path=/';console.log(document.cookie);
localStorage:localStorage.setItem('key', 'value');console.log(localStorage.getItem('key'));
sessionStorage:sessionStorage.setItem('key', 'value');console.log(sessionStorage.getItem('key'));
Explore the difference between cookies, localStorage, and sessionStorage on GreatFrontEnd
<script>, <script async>, and <script defer>?<script>:<script async>:<script defer>:<script src="main.js"></script><script async src="async.js"></script><script defer src="defer.js"></script>
Explore the difference between <script>, <script async>, and <script defer> on GreatFrontEnd
null, undefined, and Undeclared Variables Differ?null:Explicitly represents no value. Use === to check.
undefined:Indicates a variable has been declared but not assigned a value.
Variables not declared will throw a ReferenceError.
let a;console.log(a); // undefinedlet b = null;console.log(b); // null
Explore the difference between null, undefined, and undeclared variables on GreatFrontEnd
.call and .apply?.call:Accepts arguments as a comma-separated list.
.apply:Accepts arguments as an array.
function sum(a, b) {return a + b;}console.log(sum.call(null, 1, 2)); // 3console.log(sum.apply(null, [1, 2])); // 3
Explore the difference between .call and .apply on GreatFrontEnd
Function.prototype.bind and Why Is It Useful?The Function.prototype.bind method allows you to create a new function with a specific this context and optional preset arguments. It’s particularly useful for ensuring a function has the correct this context when passed to another function or used as a callback.
const john = {age: 42,getAge: function () {return this.age;},};console.log(john.getAge()); // 42const unboundGetAge = john.getAge;console.log(unboundGetAge()); // undefinedconst boundGetAge = john.getAge.bind(john);console.log(boundGetAge()); // 42const mary = { age: 21 };const boundGetAgeMary = john.getAge.bind(mary);console.log(boundGetAgeMary()); // 21
this: bind is often used to fix the this value for a method, ensuring it always refers to the intended object.bind.bind allows methods from one object to be used on another object.Explore Function.prototype.bind on GreatFrontEnd
Arrow functions automatically bind the this value to the surrounding lexical scope, which eliminates issues with context in methods. This behavior makes code more predictable and easier to debug.
const Person = function (name) {this.name = name;this.sayName1 = function () {console.log(this.name);};this.sayName2 = () => {console.log(this.name);};};const john = new Person('John');const dave = new Person('Dave');john.sayName1(); // Johnjohn.sayName2(); // Johnjohn.sayName1.call(dave); // Davejohn.sayName2.call(dave); // John
this when passed as a callback — for example, event handlers assigned to DOM elements or methods passed to third-party APIs. While modern React favors functional components with hooks, this pattern is still relevant in class-based components, Web Components, and plain JavaScript modules.Explore the advantage of using the arrow syntax for a method in a constructor on GreatFrontEnd
Prototypal inheritance allows objects to inherit properties and methods from other objects through the prototype chain.
Every JavaScript object has a prototype, which is another object from which it inherits properties.
function Person(name, age) {this.name = name;this.age = age;}Person.prototype.sayHello = function () {console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);};const john = new Person('John', 30);john.sayHello(); // Hello, my name is John and I am 30 years old.
JavaScript looks for properties and methods on the object and continues up the chain until it finds the property or reaches null.
Used with new to create objects and set their prototype.
function Animal(name) {this.name = name;}Animal.prototype.sayName = function () {console.log(`My name is ${this.name}`);};function Dog(name, breed) {Animal.call(this, name);this.breed = breed;}Dog.prototype = Object.create(Animal.prototype);Dog.prototype.bark = function () {console.log('Woof!');};const fido = new Dog('Fido', 'Labrador');fido.sayName(); // My name is Fidofido.bark(); // Woof!
Explore how prototypal inheritance works on GreatFrontEnd
function Person(){}, const person = Person(), and const person = new Person()?function Person() {} is a standard function declaration. When written in PascalCase, it conventionally represents a constructor function.
const person = Person() calls the function and executes its code but does not create a new object.
const person = new Person() creates a new object, setting its prototype to Person.prototype.
function foo() {console.log('Function declaration');}
const foo = function () {console.log('Function expression');};
Explore the differences between function declarations and expressions on GreatFrontEnd
const person = { firstName: 'John', lastName: 'Doe' };
Object() Constructor:const person = new Object();person.firstName = 'John';person.lastName = 'Doe';
Object.create():const proto = {greet() {console.log('Hello!');},};const person = Object.create(proto);person.greet(); // Hello!
class Person {constructor(name, age) {this.name = name;this.age = age;}}
Explore ways to create objects in JavaScript on GreatFrontEnd
Higher-order functions either:
function multiplier(factor) {return function (number) {return number * factor;};}const double = multiplier(2);console.log(double(5)); // 10
Explore higher-order functions on GreatFrontEnd
function Person(name) {this.name = name;}Person.prototype.greet = function () {console.log(`Hello, I’m ${this.name}`);};
class Person {constructor(name) {this.name = name;}greet() {console.log(`Hello, I’m ${this.name}`);}}
Key Differences:
extends and super.Explore ES2015 classes and ES5 constructors on GreatFrontEnd
Event bubbling is when an event starts at the target element and propagates up through its ancestors.
parent.addEventListener('click', () => console.log('Parent clicked'));child.addEventListener('click', () => console.log('Child clicked'));
Clicking the child triggers both handlers.
Explore event bubbling on GreatFrontEnd
Event capturing is when an event starts at the root and propagates down to the target element.
parent.addEventListener('click', () => console.log('Parent capturing'), true);
Explore event capturing on GreatFrontEnd
mouseenter and mouseover Events Differ in JavaScript and Browsers?mouseentermouseoverExample:
const fs = require('fs');const data = fs.readFileSync('large-file.txt', 'utf8');console.log(data); // Blocks until file is readconsole.log('End of the program');
Example:
console.log('Start of the program');fetch('https://api.example.com/data').then((response) => response.json()).then((data) => console.log(data)) // Non-blocking.catch((error) => console.error(error));console.log('End of program');
Understand the distinctions between synchronous and asynchronous functions on GreatFrontEnd
AJAX (Asynchronous JavaScript and XML) encompasses a collection of web development techniques that utilize various client-side technologies to build asynchronous web applications. Unlike traditional web applications where every user interaction results in a complete page reload, AJAX enables web apps to send and retrieve data from a server asynchronously. This allows for dynamic updates to specific parts of a web page without disrupting the overall page display and behavior.
Key Highlights:
XMLHttpRequest, though fetch() is now the preferred choice for modern web development.XMLHttpRequest APIExample:
let xhr = new XMLHttpRequest();xhr.onreadystatechange = function () {if (xhr.readyState === XMLHttpRequest.DONE) {if (xhr.status === 200) {console.log(xhr.responseText);} else {console.error('Request failed: ' + xhr.status);}}};xhr.open('GET', 'https://jsonplaceholder.typicode.com/todos/1', true);xhr.send();
XMLHttpRequest, assigns a callback to handle state changes, opens a connection to a specified URL, and sends the request.fetch() APIExample:
fetch('https://jsonplaceholder.typicode.com/todos/1').then((response) => {if (!response.ok) {throw new Error('Network response was not ok');}return response.json();}).then((data) => console.log(data)).catch((error) => console.error('Fetch error:', error));
.then() to parse JSON data, and handles errors using .catch().fetchfetch() starts an asynchronous request to obtain a resource from a given URL.
Example:
fetch('https://api.example.com/data', {method: 'GET', // or 'POST', 'PUT', 'DELETE', etc.headers: {'Content-Type': 'application/json',},});
fetch() returns a Promise that resolves to a Response object representing the server's reply.The Response object provides methods to handle the content, such as .json(), .text(), and .blob().
Example:
fetch('https://api.example.com/data').then((response) => response.json()).then((data) => console.log(data)).catch((error) => console.error('Error:', error));
fetch() operates asynchronously, allowing the browser to perform other tasks while awaiting the server's response..then(), .catch()) are processed in the microtask queue as part of the event loop.fetch() allows configuration of various request settings, including HTTP method, headers, body, credentials, and caching behavior..catch() or try/catch with async/await.Learn how to explain AJAX in detail on GreatFrontEnd
AJAX (Asynchronous JavaScript and XML) facilitates the asynchronous exchange of data between web pages and servers, enabling dynamic content updates without necessitating full page reloads.
pushState, replaceState) to keep URLs in sync with application state; otherwise, users cannot bookmark or share specific views.Explore the benefits and drawbacks of using AJAX on GreatFrontEnd
XMLHttpRequest and fetch() Differ?Both XMLHttpRequest (XHR) and fetch() facilitate asynchronous HTTP requests in JavaScript, but they vary in syntax, handling mechanisms, and features.
setRequestHeader method.send method.body property within the options parameter is used to include the request body.responseType property to manage different response formats.Response object with .then methods for accessing data.onerror event..catch method.abort() method.AbortController for canceling requests.onprogress event.Choosing Between Them: fetch() is generally favored for its cleaner syntax and Promise-based handling, though XMLHttpRequest remains useful for specific scenarios like progress tracking.
Discover the distinctions between XMLHttpRequest and fetch() on GreatFrontEnd
JavaScript encompasses a variety of data types, which are categorized into two main groups: primitive and non-primitive (reference) types.
true or false.Identifying Data Types: JavaScript is dynamically typed, meaning variables can hold different types of data at various times. The typeof operator is used to determine a variable's type.
Explore the variety of data types in JavaScript on GreatFrontEnd
Looping through object properties and array items is a fundamental task in JavaScript, and there are multiple methods to accomplish this. Below are some of the common approaches:
for...in LoopIterates over all enumerable properties of an object, including inherited ones.
for (const property in obj) {if (Object.hasOwn(obj, property)) {console.log(property);}}
Object.keys()Returns an array containing the object's own enumerable property names.
Object.keys(obj).forEach((property) => console.log(property));
Object.entries()Provides an array of the object's own enumerable string-keyed [key, value] pairs.
Object.entries(obj).forEach(([key, value]) => console.log(`${key}: ${value}`));
Object.getOwnPropertyNames()Returns an array of all properties (including non-enumerable ones) directly found on the object.
Object.getOwnPropertyNames(obj).forEach((property) => console.log(property));
for LoopA traditional loop for iterating over array elements.
for (let i = 0; i < arr.length; i++) {console.log(arr[i]);}
Array.prototype.forEach()Executes a provided function once for each array element.
arr.forEach((element, index) => console.log(element, index));
for...of LoopIterates over iterable objects like arrays.
for (let element of arr) {console.log(element);}
Array.prototype.entries()Provides both the index and value of each array element within a for...of loop.
for (let [index, elem] of arr.entries()) {console.log(index, ': ', elem);}
Introduced in ES2015, the spread syntax (...) is a powerful feature for copying and merging arrays and objects without altering the originals. It's widely used in functional programming, Redux, and RxJS.
Cloning Arrays/Objects: Creates shallow copies.
const array = [1, 2, 3];const newArray = [...array]; // [1, 2, 3]const obj = { name: 'John', age: 30 };const newObj = { ...obj, city: 'New York' }; // { name: 'John', age: 30, city: 'New York' }
Combining Arrays/Objects: Merges them into a new entity.
const arr1 = [1, 2, 3];const arr2 = [4, 5, 6];const mergedArray = [...arr1, ...arr2]; // [1, 2, 3, 4, 5, 6]const obj1 = { foo: 'bar' };const obj2 = { qux: 'baz' };const mergedObj = { ...obj1, ...obj2 }; // { foo: 'bar', qux: 'baz' }
Passing Function Arguments: Spreads array elements as individual arguments.
const numbers = [1, 2, 3];Math.max(...numbers); // Equivalent to Math.max(1, 2, 3)
Array vs. Object Spreads: Only iterables can be spread into arrays, while arrays can also be spread into objects.
const array = [1, 2, 3];const obj = { ...array }; // { 0: 1, 1: 2, 2: 3 }
The rest syntax (...) collects multiple elements into an array or object, functioning as the opposite of spread syntax.
Function Parameters: Gathers remaining arguments into an array.
function addFiveToNumbers(...numbers) {return numbers.map((x) => x + 5);}const result = addFiveToNumbers(4, 5, 6, 7); // [9, 10, 11, 12]
Array Destructuring: Collects remaining elements into a new array.
const [first, second, ...remaining] = [1, 2, 3, 4, 5];// first: 1, second: 2, remaining: [3, 4, 5]
Object Destructuring: Gathers remaining properties into a new object.
const { e, f, ...others } = { e: 1, f: 2, g: 3, h: 4 };// e: 1, f: 2, others: { g: 3, h: 4 }
Rest Parameter Rules: Must be the final parameter in a function.
function addFiveToNumbers(arg1, ...numbers, arg2) {// Error: Rest element must be last element.}
Understand the benefits of spread syntax and how it differs from rest syntax on GreatFrontEnd
Map Object Differ from a Plain Object in JavaScript?size property to easily determine the number of key-value pairs.forEach, keys(), values(), and entries().Object.keys(), Object.values(), or Object.entries() to iterate.// Mapconst map = new Map();map.set('key1', 'value1');map.set({ key: 'key2' }, 'value2');console.log(map.size); // 2 — the object reference is preserved as a distinct key// Plain Objectconst obj = { key1: 'value1' };obj[{ key: 'key2' }] = 'value2';console.log(Object.keys(obj)); // ['key1', '[object Object]']// The object key has been coerced to the string "[object Object]"
Discover the differences between a Map object and a plain object in JavaScript on GreatFrontEnd
Map/Set and WeakMap/WeakSet?The primary distinctions between Map/Set and WeakMap/WeakSet in JavaScript are outlined below:
Key Types:
Map and Set accept keys of any type, including objects, primitives, and functions.WeakMap and WeakSet exclusively use objects as keys, disallowing primitive values like strings or numbers.Memory Management:
Map and Set maintain strong references to their keys and values, preventing their garbage collection.WeakMap and WeakSet use weak references for keys (objects), allowing garbage collection if there are no other strong references.Key Enumeration:
Map and Set have enumerable keys that can be iterated over.WeakMap and WeakSet do not allow enumeration of keys, making it impossible to retrieve lists of keys or values directly.Size Property:
Map and Set provide a size property indicating the number of elements.WeakMap and WeakSet lack a size property since their size can change due to garbage collection.Use Cases:
Map and Set are suitable for general-purpose data storage and caching.WeakMap and WeakSet are ideal for storing metadata or additional object-related information without preventing the objects from being garbage collected when they are no longer needed.Learn about the differences between Map/Set and WeakMap/WeakSet on GreatFrontEnd
=> Function Syntax?One effective application of JavaScript's arrow function syntax is streamlining callback functions, especially when concise, inline function definitions are needed. Consider the following example:
Scenario: Doubling Array Elements with map
Imagine you have an array of numbers and you want to double each number using the map method.
// Traditional function syntaxconst numbers = [1, 2, 3, 4, 5];const doubledNumbers = numbers.map(function (number) {return number * 2;});console.log(doubledNumbers); // Output: [2, 4, 6, 8, 10]
By utilizing arrow function syntax, the same outcome can be achieved more succinctly:
// Arrow function syntaxconst numbers = [1, 2, 3, 4, 5];const doubledNumbers = numbers.map((number) => number * 2);console.log(doubledNumbers); // Output: [2, 4, 6, 8, 10]
Explore a use case for the new arrow => function syntax on GreatFrontEnd
In the realm of asynchronous programming, a callback function is passed as an argument to another function and is executed once a particular task completes, such as data retrieval or handling input/output operations. Here's a straightforward explanation:
function fetchData(callback) {setTimeout(() => {const data = { name: 'John', age: 30 };callback(data);}, 1000);}fetchData((data) => {console.log(data); // { name: 'John', age: 30 }});
Explore the concept of a callback function in asynchronous operations on GreatFrontEnd
Debouncing and throttling are techniques used to control the rate at which functions are executed, optimizing performance and managing event-driven behaviors in JavaScript applications.
Debouncing: Delays the execution of a function until a specified period has elapsed since its last invocation. This is particularly useful for scenarios like handling search input where you want to wait until the user has finished typing before executing a function.
function debounce(func, delay) {let timeoutId;return (...args) => {clearTimeout(timeoutId);timeoutId = setTimeout(() => func.apply(this, args), delay);};}
Throttling: Restricts a function to be executed no more than once within a given timeframe. This is beneficial for handling events that fire frequently, such as window resizing or scrolling.
function throttle(func, limit) {let inThrottle;return (...args) => {if (!inThrottle) {func.apply(this, args);inThrottle = true;setTimeout(() => (inThrottle = false), limit);}};}
These strategies help in enhancing application performance by preventing excessive function calls.
Explore the concept of debouncing and throttling on GreatFrontEnd
Destructuring assignment in JavaScript provides a concise way to extract values from arrays or properties from objects into individual variables.
// Array destructuringconst [a, b] = [1, 2];// Object destructuringconst { name, age } = { name: 'John', age: 30 };
This syntax employs square brackets for arrays and curly braces for objects, allowing for streamlined variable assignments directly from data structures.
Explore the concept of destructuring assignment for objects and arrays on GreatFrontEnd
Hoisting in JavaScript refers to the behavior where function declarations are moved to the top of their containing scope during the compilation phase. This allows functions to be invoked before their actual definition in the code. Conversely, function expressions and arrow functions must be defined prior to their invocation to avoid errors.
// Function declarationhoistedFunction(); // Works finefunction hoistedFunction() {console.log('This function is hoisted');}// Function expressionnonHoistedFunction(); // Throws an errorvar nonHoistedFunction = function () {console.log('This function is not hoisted');};
Explore the concept of hoisting with regards to functions on GreatFrontEnd
In ES2015, JavaScript introduces the class syntax with the extends keyword, enabling one class to inherit properties and methods from another. The super keyword is used to access the parent class's constructor and methods.
class Animal {constructor(name) {this.name = name;}speak() {console.log(`${this.name} makes a noise.`);}}class Dog extends Animal {constructor(name, breed) {super(name);this.breed = breed;}speak() {console.log(`${this.name} barks.`);}}const dog = new Dog('Rex', 'German Shepherd');dog.speak(); // Output: Rex barks.
In this example, the Dog class inherits from the Animal class, demonstrating how classes facilitate inheritance and method overriding in JavaScript.
Explore the concept of inheritance in ES2015 classes on GreatFrontEnd
Lexical scoping in JavaScript determines how variable names are resolved based on their location within the source code. Nested functions have access to variables from their parent scopes, enabling them to utilize and manipulate these variables.
function outerFunction() {let outerVariable = 'I am outside!';function innerFunction() {console.log(outerVariable); // 'I am outside!'}innerFunction();}outerFunction();
In this scenario, innerFunction can access outerVariable because of lexical scoping rules, which allow inner functions to access variables defined in their outer scope.
Explore the concept of lexical scoping on GreatFrontEnd
Scope in JavaScript defines the accessibility of variables and functions in different parts of the code. There are three primary types of scope:
let or const within a block (e.g., within {}) are accessible only within that block.// Global scopevar globalVar = 'I am global';function myFunction() {// Function scopevar functionVar = 'I am in a function';if (true) {// Block scopelet blockVar = 'I am in a block';console.log(blockVar); // Accessible here}// console.log(blockVar); // Throws an error}console.log(globalVar); // Accessible here// console.log(functionVar); // Throws an error
In this example, globalVar is accessible globally, functionVar is confined to myFunction, and blockVar is restricted to the if block.
Explore the concept of scope in JavaScript on GreatFrontEnd
The spread operator (...) in JavaScript allows iterable elements (like arrays or objects) to be expanded into individual elements. It's versatile and can be used for copying, merging, and passing array elements as function arguments.
// Copying an arrayconst arr1 = [1, 2, 3];const arr2 = [...arr1];// Merging arraysconst arr3 = [4, 5, 6];const mergedArray = [...arr1, ...arr3];// Copying an objectconst obj1 = { a: 1, b: 2 };const obj2 = { ...obj1 };// Merging objectsconst obj3 = { c: 3, d: 4 };const mergedObject = { ...obj1, ...obj3 };// Passing array elements as function argumentsconst sum = (x, y, z) => x + y + z;const numbers = [1, 2, 3];console.log(sum(...numbers)); // Output: 6
The spread operator simplifies operations such as copying arrays or objects, merging multiple arrays or objects into one, and spreading elements of an array as individual arguments to functions.
Explore the concept of the spread operator and its uses on GreatFrontEnd
this Binding Work in Event Handlers?In JavaScript, the this keyword refers to the object that is executing the current piece of code. Within event handlers, this typically points to the DOM element that triggered the event. However, its value can change depending on how the handler is defined and invoked. To ensure this references the intended context, techniques like using bind(), arrow functions, or explicitly setting the context are employed.
These methods help maintain the correct reference for this within event handling functions, ensuring consistent and predictable behavior across various event-driven scenarios in JavaScript applications.
Explore the concept of this binding in event handlers on GreatFrontEnd
A Block Formatting Context (BFC) is a pivotal concept in CSS that influences how block-level elements are rendered and interact on a webpage. It creates an isolated environment where block boxes are laid out, ensuring that elements like floats, absolutely positioned elements, inline-blocks, table-cells, table-captions, and those with an overflow value other than visible (except when propagated to the viewport) establish a new BFC.
Grasping how to initiate a BFC is essential because, without it, the containing box might fail to encompass floated child elements. This issue is akin to collapsing margins but is often more deceptive, causing entire boxes to collapse unexpectedly.
A BFC is formed when an HTML box satisfies at least one of the following criteria:
float property is set to a value other than none.position property is assigned a value that is neither static nor relative.display property is set to table-cell, table-caption, inline-block, flex, inline-flex, grid, or inline-grid.overflow property is set to a value other than visible.Within a BFC, each box's left outer edge aligns with the left edge of its containing block (or the right edge in right-to-left layouts). Additionally, vertical margins between adjacent block-level boxes within a BFC collapse into a single margin.
Discover Block Formatting Context (BFC) and its Operation on GreatFrontEnd
z-index and How is a Stacking Context Created?The z-index property in CSS manages the vertical stacking order of overlapping elements. It only influences positioned elements—those with a position value other than static—and their descendants or flex items.
In the absence of a z-index value, elements stack based on their order in the Document Object Model (DOM), with elements appearing later in the HTML markup rendered on top of earlier ones at the same hierarchy level. Positioned elements (those with non-static positioning) and their children will always overlay elements with default static positioning, regardless of their order in the HTML structure.
A stacking context is essentially a group of elements that share a common stacking order. Within a local stacking context, the z-index values of child elements are relative to that context rather than the entire document. Elements outside of this context—such as sibling elements of a local stacking context—cannot interpose between layers within it. For instance, if element B overlays element A, a child of element A, element C, cannot surpass element B in the stacking order even if it has a higher z-index than element B.
Each stacking context operates independently; after stacking its child elements, the entire context is treated as a single entity within the parent stacking context's order. Certain CSS properties, like an opacity less than 1, a filter that isn't none, or a transform that isn't none, can trigger the creation of a new stacking context.
Learn about z-index and Stacking Contexts on GreatFrontEnd
This topic relates to writing efficient CSS, specifically how browsers interpret and apply CSS selectors. Browsers process selectors from right to left, starting with the most specific (the key selector) and moving outward. They first identify all elements that match the rightmost part of the selector and then traverse up the DOM tree to verify if those elements meet the remaining parts of the selector.
For example, consider the selector p span. Browsers will first locate all <span> elements and then check each span's ancestor chain to determine if it is within a <p> element. Once a <p> ancestor is found for a given <span>, the browser confirms that the <span> matches the selector and ceases further traversal for that element.
The efficiency of selector matching is influenced by the length of the selector chain—the shorter the chain, the quicker the browser can verify matches.
Understand How Browsers Match CSS Selectors on GreatFrontEnd
The CSS box model is a fundamental concept that describes the rectangular boxes generated for elements in the document tree, determining how they are laid out and displayed. Each box comprises a content area (such as text or images) surrounded by optional padding, border, and margin areas.
The box model is responsible for calculating:
width, height, padding, and border.height is specified, a block element's height adjusts to its content plus padding (unless floats are involved).width is set, a non-floated block element expands to fit its parent's width minus padding, unless a max-width is specified.
table, figure, and input have inherent width values and may not expand fully.span do not have a default width and will not expand to fit.height and width are determined by its content.box-sizing: content-box), padding and border are not included in an element's width and height.Note: Margins do not contribute to the actual size of the box; they affect the space outside the box. The box's area is confined to the border and does not extend into the margin.
Understanding the box-sizing property is crucial as it alters how an element's height and width are calculated.
box-sizing: content-box: The default behavior where only the content size is considered.box-sizing: border-box: Includes padding and border in the element's total width and height, excluding margin.Many CSS frameworks adopt box-sizing: border-box globally for a more intuitive sizing approach.
Explore the Box Model and Its Control in CSS on GreatFrontEnd
display Property? Provide Examples.The display property in CSS dictates how an element is rendered in the document flow. Common values include none, block, inline, inline-block, flex, grid, table, table-row, table-cell, and list-item.
Description:
none
Hides the element; it does not occupy any space in the layout. All child elements are also hidden. The element is treated as if it does not exist in the DOM.
block
The element occupies the full width available, starting on a new line.
inline
The element does not start on a new line and only occupies as much width as necessary.
inline-block
Combines characteristics of both inline and block. The element flows with text but can have width and height set.
flex
Defines the element as a flex container, enabling the use of flexbox layout for its children.
grid
Defines the element as a grid container, allowing for grid-based layout of its children.
table
Makes the element behave like a <table> element.
table-row
Makes the element behave like a <tr> (table row) element.
table-cell
Makes the element behave like a <td> (table cell) element.
list-item
Makes the element behave like a <li> (list item) element, enabling list-specific styling such as list-style-type and list-style-position.
For a comprehensive list of display property values, refer to the CSS Display | MDN.
Understand the CSS display Property with Examples on GreatFrontEnd
relative, fixed, absolute, sticky, and static Positioning Differ?In CSS, an element's positioning is determined by its position property, which can be set to relative, fixed, absolute, sticky, or static. Here's how each behaves:
static: The default positioning. Elements flow naturally within the document. The top, right, bottom, left, and z-index properties have no effect.
relative: The element is positioned relative to its normal position. Adjustments using top, right, bottom, or left move the element without affecting the layout of surrounding elements, leaving a gap where it would have been.
absolute: The element is removed from the normal document flow and positioned relative to its nearest positioned ancestor (an ancestor with a position other than static). If no such ancestor exists, it positions relative to the initial containing block. Absolutely positioned elements do not affect the position of other elements and can have width and height specified.
fixed: Similar to absolute, but the element is positioned relative to the viewport, meaning it stays in the same place even when the page is scrolled.
sticky: A hybrid of relative and fixed. The element behaves like relative until it crosses a specified threshold (e.g., scroll position), after which it behaves like fixed, sticking to its position within its parent container.
Understanding these positioning schemes is vital for controlling the layout and behavior of elements, especially in responsive and dynamic designs.
Learn About Positioning Schemes in CSS on GreatFrontEnd
Designing and developing for multilingual websites involves various considerations to ensure accessibility and usability across different languages and cultures. This process is part of internationalization (i18n).
lang attribute on the <html> tag to specify the page's language.en_US, zh_CN).<link rel="alternate" hreflang="other_locale" href="url_for_other_locale"> to inform search engines about alternate language versions of the page.<link rel="alternate" href="url_for_fallback" hreflang="x-default" />.en-US vs. en-GB, zh-CN vs. zh-TW).Accept-Language headers and IP addresses.Dynamic Content: Instead of concatenating strings (e.g., "The date today is " + date), use template strings with parameter substitution to accommodate different grammar structures across languages.
Example:
// Englishconst message = `I will travel on ${date}`;// Chineseconst message = `我会在${date}出发`;
Understand Multilingual Design Considerations on GreatFrontEnd
block, inline, and inline-block Display Types Differ?The display property in CSS determines how elements are rendered on the page. The block, inline, and inline-block values have distinct behaviors and use cases:
| Property | block | inline-block | inline |
|---|---|---|---|
| Size | Fills up the width of its parent container. | Depends on content. | Depends on content. |
| Positioning | Start on a new line and tolerates no HTML elements next to it (except when you add float) | Flows along with other content and allows other elements beside it. | Flows along with other content and allows other elements beside it. |
Can specify width and height | Yes | Yes | No. Will ignore if being set. |
Can be aligned with vertical-align | No | Yes | Yes |
| Margins and paddings | All sides respected. | All sides respected. | Only horizontal sides respected. Vertical sides, if specified, do not affect layout. Vertical space it takes up depends on line-height, even though the border and padding appear visually around the content. |
| Float | - | - | Becomes like a block element where you can set vertical margins and paddings. |
| Use Cases | Layout elements like <div>, <p>, <section>. | Used for buttons, images, and form fields that need custom sizes but stay in line with text. | Links <a>, text formatting <span>, text styling - bold <b>, italics <i>. |
Learn the Differences Between block, inline, and inline-block on GreatFrontEnd
translate() Over Absolute Positioning, or Vice Versa?The translate() function is a part of the CSS transform property and offers a different approach to positioning compared to absolute positioning. Here's why you might choose one over the other:
Using translate():
position: relative.transform or opacity does not trigger browser reflows or repaints; instead, it initiates a composition layer. This results in smoother and more efficient animations, as translate() leverages the GPU for rendering..element {transform: translateX(50px);}
Using absolute Positioning:
.element {position: absolute;top: 20px;left: 30px;}
Why Choose translate()?
For animations and dynamic movements where performance and smoothness are critical, translate() is more efficient. It avoids the costly reflows associated with changing layout-affecting properties like top and left.
Why Choose absolute Positioning?
When you need to position elements precisely without regard to their original place in the document flow, absolute positioning is the way to go. It's essential for creating overlays, modals, and tooltips that need to appear in specific locations on the screen.
Understand When to Use translate() vs. Absolute Positioning on GreatFrontEnd
* { box-sizing: border-box; } Do and What Are Its Advantages?Applying * { box-sizing: border-box; } in your CSS ensures that all elements on the page use the border-box model for calculating their width and height.
By default, elements use box-sizing: content-box, where the width and height only account for the content area. When you set box-sizing: border-box, the width and height properties include the element's padding and border, but not the margin.
| Property | box-sizing: content-box (default) | box-sizing: border-box |
|---|---|---|
| content | Yes | Yes |
padding | No | Yes |
border | No | Yes |
margin | No | No |
padding and border within the width and height makes it easier to calculate the size of elements, aligning more closely with designers' expectations.padding or border to elements, as it doesn't alter the total size.box-sizing: border-box globally to maintain consistency and predictability in element sizing.Explore What * { box-sizing: border-box; } Does and Its Benefits on GreatFrontEnd
The following HTML-focused topics are among the most frequently asked in front-end interviews. They cover semantic markup, HTML5 features, meta tags, form validation, and link security — foundational areas where a precise answer often distinguishes a strong candidate from one with only surface-level familiarity.
Semantic HTML is the practice of using HTML elements that describe the meaning of their content, rather than relying on generic containers styled to resemble specific structures. Elements such as <header>, <nav>, <main>, <article>, <section>, <aside>, and <footer> convey the role of the content they contain, while <div> and <span> are non-semantic and carry no inherent meaning.
<!-- Non-semantic: the purpose of each container is not expressed in the markup --><div class="header"><div class="nav">...</div></div><div class="main"><div class="article">...</div></div><div class="footer">...</div><!-- Semantic: the markup itself describes the page structure --><header><nav>...</nav></header><main><article>...</article></main><footer>...</footer>
Benefits of semantic HTML include:
<nav>, <main>, or landmark regions using assistive-technology shortcuts.<button>, for example, is keyboard-accessible, submits forms, and respects the operating system's focus styles without additional JavaScript or CSS.What interviewers look for: A strong answer names concrete semantic elements and explains their impact on assistive technology and SEO, rather than describing semantic HTML as simply "cleaner code." Awareness that <div> and <span> remain the correct choice when no semantic element applies is equally important — overusing semantic tags where they do not fit is as problematic as avoiding them.
Commonly overlooked nuance: Semantic HTML is not only about choosing the right tag; it is about the accessibility tree the browser constructs from the markup. A <div role="button"> is semantically a button to assistive technology but loses native keyboard activation, focus styling, and form-submission behavior, making <button> strictly preferable whenever it applies.
HTML5 became the current W3C standard in 2014 and has continued to evolve as part of the WHATWG HTML Living Standard. It introduced several major improvements over earlier versions of HTML.
The simplified <!DOCTYPE html>
HTML5 decouples the doctype from the legacy SGML and XHTML DTD system. A single line — <!DOCTYPE html> — is all that is required at the top of every HTML5 document and instructs the browser to render the page in standards mode.
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>Document</title></head><body>...</body></html>
Key HTML5 features
<header>, <nav>, <main>, <article>, <section>, <aside>, <footer>, <figure>, <time>, and others.<audio> and <video> elements remove the need for browser plugins such as Flash.<canvas> for bitmap rendering and native SVG support for vector graphics.email, url, number, date, color, range, tel, search) and validation attributes (required, pattern, min, max).localStorage and sessionStorage for client-side key-value storage.What interviewers look for: A strong answer groups features by category rather than listing them at random, and demonstrates awareness that HTML is now a Living Standard maintained by the WHATWG rather than a versioned specification. Candidates who can tie features to practical use cases — for example, "<canvas> for interactive graphics, <video> for native playback" — demonstrate applied understanding rather than rote memorization.
Commonly overlooked nuance: The <!DOCTYPE html> declaration is case-insensitive but must still appear as the very first content in the document. Any whitespace or markup before it can trigger quirks mode in some browsers, producing subtle layout and box-model differences that are difficult to debug after the fact.
Meta tags are instructions placed within the <head> of an HTML document that describe the page to browsers, search engines, and social platforms. They are not visible to users directly but significantly influence how the page is rendered, discovered, and shared.
<head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><metaname="description"content="A concise summary of the page, typically 150-160 characters." /><meta name="robots" content="index, follow" /><!-- Open Graph for social sharing --><meta property="og:title" content="Page Title" /><metaproperty="og:description"content="Description shown when the page is shared." /><meta property="og:image" content="https://example.com/preview.png" /><meta property="og:type" content="website" /><!-- Twitter Card --><meta name="twitter:card" content="summary_large_image" /></head>
Key meta tags and their purpose:
charset="UTF-8" declares the character encoding. It must appear within the first 1024 bytes of the document, so it is conventionally the first element in the <head>.name="viewport" controls layout on mobile devices. Without it, mobile browsers fall back to a desktop-width rendering and zoom out. The canonical value is width=device-width, initial-scale=1.name="description" provides the summary that search engines may display in result listings. It does not directly influence rankings, but a compelling description improves click-through rate.name="robots" instructs crawlers whether to index the page and follow its links. Common values include index, noindex, follow, and nofollow.og:*) and Twitter Card tags control how the page appears when shared on social platforms, including the preview title, description, and image.What interviewers look for: A strong answer separates the viewport meta tag (critical for responsive rendering) from SEO-focused tags (description, robots) and social-sharing tags (Open Graph, Twitter). Distinguishing name="description" (a search-engine hint) from og:description (a social-sharing hint) demonstrates practical familiarity with the ecosystem.
Commonly overlooked nuance: The keywords meta tag has been ignored by major search engines, including Google, since 2009 — citing it as an SEO tool is a signal of outdated material. The viewport meta tag is also load-bearing for Core Web Vitals; an incorrect value can regress Cumulative Layout Shift on mobile devices even when the page appears visually correct.
HTML5 introduced native, browser-provided form validation through a combination of input types, attributes, and the Constraint Validation API. This allows basic client-side validation to be expressed declaratively in markup, with no JavaScript required for common cases.
<form><label><input type="email" name="email" required /></label><label>Password (8+ characters, at least one number)<inputtype="password"name="password"requiredminlength="8"pattern="^(?=.*\d).{8,}$" /></label><label>Age (18-100)<input type="number" name="age" min="18" max="100" required /></label><label>Website (optional)<input type="url" name="website" /></label><button type="submit">Sign up</button></form>
Native validation features include:
email, url, number, tel, date, color, range.required, minlength, maxlength, min, max, step, and pattern (a regular expression).element.checkValidity(), element.reportValidity(), and element.setCustomValidity(message) for customising error messages without replacing the native behavior.:valid, :invalid, :required, and :optional pseudo-classes, which allow styling based on validation state directly from CSS.What interviewers look for: A strong answer distinguishes between the three layers — input types (which enforce a format), constraint attributes (which enforce bounds), and the Constraint Validation API (which allows programmatic customization without sacrificing native behavior). Applying novalidate on the form and reimplementing everything in JavaScript is a common anti-pattern worth identifying.
Commonly overlooked nuance: Native validation messages are localized to the user's browser language, styled consistently with the operating system, and positioned near the offending field — benefits that are difficult to replicate with a custom JavaScript solution. Client-side validation also never replaces server-side validation; it is a user-experience enhancement, not a security measure, because tampering with the DOM trivially bypasses any client-side check.
target="_blank" Links Include rel="noopener noreferrer"?Opening a link in a new tab using target="_blank" has historically introduced both security and performance concerns. Modern best practice addresses them by adding the rel="noopener noreferrer" attribute.
<!-- Always use noopener (and typically noreferrer) with target="_blank" --><ahref="https://external-site.example"target="_blank"rel="noopener noreferrer">External site</a>
What each keyword does:
noopener prevents the newly opened page from accessing the original page through window.opener. Without it, the destination page can redirect the opener to a phishing site via window.opener.location = '...' — a technique known as reverse tabnabbing.noreferrer prevents the browser from sending the Referer header to the destination, and also implies noopener. It is useful when the originating URL should not be disclosed to the third-party site.Modern browser defaults:
All major browsers (Chrome, Firefox, Safari, and Edge) now set rel="noopener" implicitly when target="_blank" is used. This change was rolled out progressively between 2020 and 2022. However, explicitly declaring rel="noopener noreferrer" remains recommended because:
noreferrer must still be added explicitly when the Referer header should be suppressed.What interviewers look for: A strong answer clearly explains the reverse-tabnabbing attack and distinguishes the roles of noopener and noreferrer. Awareness of the modern implicit default demonstrates currency with the platform without treating the explicit attribute as optional.
Commonly overlooked nuance: rel="noopener" only affects the opener relationship. It does not prevent the destination page from performing other actions, such as opening popups, attempting fingerprinting, or tracking the user. Stronger cross-origin isolation at the tab level requires additional mechanisms, such as the Cross-Origin-Opener-Policy HTTP header.
<article>, <section>, and <div>?Although these three elements can render identically by default, they convey distinct semantic meanings that influence the accessibility tree and the document outline.
<article> represents a self-contained, independently distributable piece of content, such as a blog post, product card, or comment. A helpful test is to consider whether the content would still make sense when syndicated or displayed in isolation.<section> represents a thematic grouping of content that belongs under a heading. A <section> without an accompanying heading is almost always the wrong element.<div> is a generic container with no semantic meaning, intended as a styling or scripting hook when no semantic element applies.<article><h2>How the event loop works</h2><section><h3>The call stack</h3><p>...</p></section><section><h3>The microtask queue</h3><p>...</p></section></article>
What interviewers look for: A strong answer considers whether a <section> has an appropriate heading before using it. Using <section> in place of <nav>, or applying <article> to decorative UI, indicates the element names have been memorized without an understanding of how assistive technologies interpret them.
Commonly overlooked nuance: These elements are frequently described as interchangeable semantic containers, but a <section> without an accessible name produces a generic region with no navigational value. Nesting <article> within another <article> also implicitly demotes the inner element within the document outline.
Front-end interviews increasingly focus on how the three core technologies interact rather than on any one in isolation. The following questions examine the boundaries where HTML, CSS, and JavaScript meet — where subtle behaviors of one affect the others in ways that can surface as hard-to-diagnose bugs in production.
<link> Tags in <head> and JavaScript <script> Tags Just Before </body>?The placement of stylesheet and script tags directly affects how quickly a browser can display content to the user. The guidance traces back to the browser's rendering pipeline and the distinction between render-blocking and parser-blocking resources.
CSS in <head>
<head>, so delaying CSS also delays the visible render.JavaScript just before </body>
<script> tags are parser-blocking. When the browser encounters one during HTML parsing, it must pause parsing, download the script, execute it, and only then resume.<body> allows the entire document to be parsed and the initial paint to occur before any script runs.Modern alternatives with async and defer
<head><!-- CSS blocks rendering, so load as early as possible --><link rel="stylesheet" href="styles.css" /><!-- defer: downloads in parallel, runs after parsing completes, preserves order --><script src="app.js" defer></script><!-- async: downloads in parallel, runs as soon as available (order not guaranteed) --><script src="analytics.js" async></script></head>
With defer, scripts can safely be placed in the <head> because they are guaranteed to run only after HTML parsing completes, in document order. This is the preferred modern approach for application scripts.
What interviewers look for: A strong answer connects placement to the critical rendering path — the rationale is not stylistic, but about enabling parallel downloads and reducing first-paint time. Awareness of defer and async as modern alternatives indicates that the classic rule is a fallback for when those attributes are not an option, not an absolute requirement.
Commonly overlooked nuance: async scripts run as soon as they are downloaded, which can occur mid-parse. If one async script depends on another, their execution order is undefined. For ordered execution — for example, a library before a script that uses it — defer is the correct choice.
The critical rendering path is the sequence of steps the browser performs to convert HTML, CSS, and JavaScript into pixels on the screen. Understanding it is foundational to diagnosing performance issues, interpreting Core Web Vitals, and writing code that does not unnecessarily block rendering.
The five stages
display: none are excluded from the render tree; elements hidden with visibility: hidden remain in it.How HTML, CSS, and JavaScript each affect the path
<script> tags until each script has downloaded and executed.<head> is downloaded and parsed. CSS is also parser-blocking for any scripts that follow, because scripts may query computed styles.async and defer allow parallel downloading. defer preserves ordering and runs before DOMContentLoaded; async runs as soon as the download completes, in any order.Strategies for optimizing the critical rendering path
<link rel="preload"> or the media attribute.defer or async on scripts that do not need to run synchronously.<link rel="preconnect"> and <link rel="preload"> hints to give the browser early information about which resources will be needed.What interviewers look for: A strong answer names the five stages in order and identifies which resources block which stages. Connecting the critical rendering path to Core Web Vitals — particularly Largest Contentful Paint — demonstrates an understanding of the practical impact on measurable performance metrics.
Commonly overlooked nuance: Layout and paint are triggered not only during the initial load but on almost every JavaScript-driven style or DOM change. Reading offsetHeight immediately after every write forces a condition called layout thrashing, which can cause significant jank. Batching reads and writes, or scheduling work inside requestAnimationFrame, avoids the issue.
display: none, visibility: hidden, and opacity: 0 Differ for JavaScript APIs and Accessibility?All three techniques hide content visually, but they have substantially different effects on layout, interactivity, and assistive technology.
| Technique | Occupies layout space | Focusable via Tab | Exposed to screen readers | getBoundingClientRect dimensions |
|---|---|---|---|---|
display: none | No | No | No | All zeros |
visibility: hidden | Yes | No | No | Actual dimensions |
opacity: 0 | Yes | Yes | Yes | Actual dimensions |
aria-hidden="true"* | Yes | Yes | No | Actual dimensions |
* aria-hidden affects only the accessibility tree; it does not hide the element visually or remove it from the focus order. Combining aria-hidden="true" with tabindex="-1" is often required to fully remove an element from assistive-technology interaction.
Key implications for JavaScript and accessibility:
opacity: 0 remains fully interactive — it still receives pointer events, still enters the tab order, and is still read aloud by screen readers. Using it alone as a hiding technique is a frequent source of accessibility bugs.display: none cannot be measured through getBoundingClientRect or offsetWidth, and scroll positions cannot be computed while it is hidden. A common pattern when measurement is required before display is to temporarily apply visibility: hidden (which retains layout) rather than display: none.tabindex="-1" addresses focus only, while aria-hidden="true" addresses the accessibility tree only.What interviewers look for: Recognition that "hidden" is not a single concept. Strong answers distinguish between visual rendering, layout occupation, focusability, and accessibility-tree exposure, and select a technique that matches the intent. For transient hiding of an actionable element, the hidden attribute or display: none is usually correct; for visual-only transitions, pairing opacity with visibility at the end of the transition is safer.
Commonly overlooked nuance: Combining opacity: 0 with pointer-events: none addresses pointer input but does not remove the element from keyboard focus or screen-reader output. Accessible hiding requires attention to all four dimensions, not only visual appearance.
innerHTML Break Event Listeners Attached to Child Elements?This is a classic question at the intersection of HTML and JavaScript, and its failure mode is particularly subtle: no error is thrown, and the affected listeners simply stop firing.
When a value is assigned to innerHTML, the browser performs the following steps:
const list = document.getElementById('list');list.querySelector('.item').addEventListener('click', handleClick);// The listener is still attached and functional at this point.list.innerHTML += '<li class="item">New item</li>';// The original <li> has been destroyed and replaced; the listener no longer fires.
Recommended approaches, in order of preference:
list.addEventListener('click', (e) => {if (e.target.closest('.item')) handleClick(e);});
appendChild, insertAdjacentHTML, or replaceChildren.innerHTML update, which is fragile and generally discouraged.What interviewers look for: Recognition that reassigning innerHTML destroys existing descendants rather than simply updating the DOM. Adopting event delegation as the primary solution demonstrates senior-level understanding, whereas relying on re-attaching listeners after each update reflects a reactive rather than architectural approach.
Single-page applications, modals, toasts, and client-side route transitions all share a common challenge: the user's focus and the screen reader's reading context do not follow DOM changes automatically. They must be managed explicitly.
Three common scenarios illustrate the distinction:
Route transitions in a single-page application. Following navigation, focus should be moved to the new page's primary heading (<h1>) or to a <main tabindex="-1">, and the new page title should be announced through a live region. Without this, a screen reader user may remain positioned at the previous page's footer with no indication that the view has changed.
router.afterEach(() => {document.querySelector('main').focus();document.title = newTitle;});
Modals and dialogs. Focus should be trapped inside the dialog while it is open and returned to the triggering element when the dialog closes. The native <dialog> element manages this behavior automatically; custom modal implementations must replicate it manually.
const trigger = document.activeElement;dialog.showModal();dialog.addEventListener('close', () => trigger.focus());
Inline content insertion, such as form validation errors, search results, or toast notifications. Focus should generally remain with the user, who is often in the middle of typing. Instead, a live region (aria-live="polite" for results, role="alert" for errors) should be used to announce the update without disrupting the current interaction.
What interviewers look for: A strong answer distinguishes between types of DOM changes rather than applying a uniform rule. Moving focus on every DOM change is as disruptive as moving it on none. Senior-level responses separate navigational context changes (move focus), user-initiated overlays (trap focus and return it on close), and ambient updates such as toasts or search results (announce via a live region without moving focus).
Both concepts address the same underlying challenge — making a website usable across a range of browsers, devices, and network conditions — but they approach the problem from opposite directions.
Progressive enhancement
A bottom-up approach that starts with a minimal, functional baseline of HTML and then layers on enhancements as the browsing environment supports them.
<!-- Functional without JavaScript: the form submits via a standard GET request --><form action="/search" method="GET"><input type="search" name="q" required /><button type="submit">Search</button></form><script>// JavaScript enhances the experience with inline results, but the non-JS// baseline still works if the script fails to load or execute.document.querySelector('form').addEventListener('submit', async (e) => {e.preventDefault();const results = await fetchResults(e.target.q.value);renderResults(results);});</script>
Graceful degradation
A top-down approach that starts with a fully-featured modern experience and ensures the site remains usable when features are missing.
<noscript> blocks, or polyfills.When each approach applies
What interviewers look for: A strong answer identifies both approaches as strategies for resilience rather than ideological opposites. Candidates who can describe scenarios in which each is appropriate — rather than asserting that one is universally correct — demonstrate the judgment expected of a senior front-end engineer.
Commonly overlooked nuance: Progressive enhancement is often mischaracterized as "making the site work without JavaScript." The more useful framing is layered functionality: the CSS layer should still work if JavaScript fails, the HTML layer should still work if CSS fails, and the text content should still be readable if all styling fails. This layered view applies equally within a JavaScript-heavy single-page application, where the initial server-rendered HTML can provide a usable baseline before hydration completes.
Congratulations on reaching the end of our comprehensive collection of HTML, CSS, and JavaScript interview questions and answers! We hope this resource has equipped you with the confidence and skills needed to excel in your upcoming interviews. Remember, consistent practice is essential, so keep coding and revisiting these concepts until they become second nature.
If you prefer GitHub, our open source repo contains all +190 JavaScript interview questions. You’re welcome to explore, fork, or contribute to the collection.