Skip to the questions

Frontend Developer · Fresher to 3 years

Frontend interview questions

The 40 questions that come up most in frontend developer interviews. Each one has a short answer you can say out loud in about 20 seconds, and the full explanation, with tested code, for when you need it.

Read the quick answers

22 JavaScript · 5 HTML · 9 CSS · 4 Browser & WebUpdated 22 Oct 2026

When is your interview?

01JavaScript

0/22 got it

Q01Not marked yetFresherAsked oftenBasics

What are the data types in JavaScript?

Say this first~15 sec to say

JavaScript has 8 data types. Seven are primitives: string, number, bigint, boolean, undefined, null and symbol. The eighth is object, which covers arrays, functions, dates, maps and everything else. Primitives are immutable and copied by value. Objects are copied by reference.

Could you say it?
Full answer2 min read

Primitive vs reference

  • A primitive variable holds the value itself, so copying it gives you an independent copy.
  • An object variable holds a reference (an address) to the object in memory. Copying the variable copies the address, so both variables point to the same object.
JavaScript
// Primitives: copied by value
let a = 10;
let b = a;
b = 20;
console.log(a); // 10 — a is untouched

// Objects: copied by reference
const user1 = { name: "Asha" };
const user2 = user1;
user2.name = "Ravi";
console.log(user1.name); // "Ravi" — both variables point to the same object

What typeof returns

Valuetypeof result
"hello""string"
42, 3.14, NaN"number"
10n"bigint"
true"boolean"
undefined"undefined"
Symbol("id")"symbol"
null"object", a bug from JavaScript's first version that was kept for backward compatibility
{}, [], new Date()"object"
function () {}"function" (functions are objects, but typeof reports them separately)

Follow-ups

  • How do you check if a value is an array? Use Array.isArray(value), because typeof [] gives "object".
  • Is JavaScript statically or dynamically typed? Dynamically typed. A variable's type is decided at runtime and can change, so let x = 5; x = "five"; is valid. TypeScript adds static types on top.
  • Is JavaScript pass-by-reference? No, it's always pass-by-value. For objects, though, the value being passed is a reference. A function can change the object you pass in, but reassigning the parameter doesn't affect the caller:
JavaScript
function update(obj) {
  obj.count = 1;       // mutates the shared object → the caller sees it
  obj = { count: 99 }; // only rebinds the local parameter → the caller doesn't
}

const data = { count: 0 };
update(data);
console.log(data.count); // 1
Q02Not marked yetFresherAsked very oftenBasics

What is the difference between var, let and const?

Say this first~25 sec to say

var is function-scoped, can be redeclared, and is hoisted with the value undefined. let and const are block-scoped (limited to the nearest { }), can't be redeclared in the same scope, and throw an error if you use them before the line that declares them. const also can't be reassigned, but an object or array declared with const can still be changed internally.

Could you say it?
Full answer2 min read
varletconst
ScopeFunctionBlockBlock
Redeclare in the same scopeYesNoNo
ReassignYesYesNo
Use before the declarationundefinedReferenceErrorReferenceError
Must be initialisedNoNoYes
Becomes a window property at the top levelYesNoNo
JavaScript
if (true) {
  var x = 1;
  let y = 2;
}
console.log(x); // 1 — var ignores the block
console.log(y); // ReferenceError: y is not defined

const protects the variable, not the value

JavaScript
const user = { name: "Asha" };
user.name = "Ravi"; // allowed — we're changing the object, not the variable
user = {};          // TypeError: Assignment to constant variable.

const settings = Object.freeze({ theme: "dark" });
settings.theme = "light";    // ignored (throws a TypeError in strict mode)
console.log(settings.theme); // "dark"

What to use: const by default, let when the value has to change (counters, loop variables), and avoid var in modern code.

Follow-ups

  • The classic trap: var in a for loop with setTimeout prints the final value every time. See question 6.
  • Is Object.freeze deep? No, it's shallow. Nested objects inside a frozen object can still be changed.
Q03Not marked yetFresherAsked very oftenBasics

What is hoisting?

Say this first~30 sec to say

Before running your code, the JavaScript engine scans each scope and sets up memory for every declaration in it. That's why some things can be used before the line where they're written. Function declarations are hoisted completely. var is hoisted and set to undefined. let, const and class are hoisted but left uninitialised, so touching them before the declaration throws a ReferenceError. That gap is called the Temporal Dead Zone (TDZ).

Could you say it?
Full answer2 min read

Nothing actually moves to the top of the file. Each time a scope runs, the engine goes through two phases: a memory creation phase, where declarations are registered, and an execution phase, where the code runs line by line. Hoisting is the visible effect of the first phase.

JavaScript
console.log(sayHi()); // "Hi" — function declarations are fully hoisted
function sayHi() {
  return "Hi";
}

console.log(a); // undefined — the declaration is hoisted, the value isn't
var a = 5;

console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 10;

Function expressions follow the variable rules

JavaScript
greet(); // TypeError: greet is not a function
var greet = function () {
  console.log("Hello");
};

greet is a var, so it exists but holds undefined at the time of the call, and calling undefined throws a TypeError. With let or const you'd get a ReferenceError instead.

The Temporal Dead Zone

JavaScript
{
  // The TDZ for `city` starts at the top of the block…
  console.log(typeof city); // ReferenceError — even typeof isn't safe here
  let city = "Pune";        // …and ends here
}
DeclarationHoisted?Value before the declaration line
function foo() {}YesThe whole function
var xYesundefined
let x / const xYesUninitialised (TDZ), so you get a ReferenceError
class Foo {}YesUninitialised (TDZ), so you get a ReferenceError
var fn = function () {}Only fn, as undefinedCalling it throws a TypeError

Follow-up

  • If a function and a var share a name, which one wins during hoisting? The function declaration. typeof foo is "function" before the var foo = 1; line runs and "number" after it.
Q04Not marked yetFresherAsked very oftenBasics

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

Say this first~25 sec to say

=== (strict equality) compares both value and type and never converts anything. == (loose equality) first converts both values to a common type, which is called type coercion, and then compares them. That conversion produces surprising results, so use === by default. The one common use of == is value == null, which checks for null and undefined at the same time.

Could you say it?
Full answer2 min read
JavaScript
5 === "5";          // false — number vs string
5 == "5";           // true  — "5" is converted to 5
0 == false;         // true  — false becomes 0
"" == 0;            // true  — "" becomes 0
[] == false;        // true  — [] → "" → 0, and false → 0
null == undefined;  // true  — a special rule
null === undefined; // false
NaN == NaN;         // false — NaN isn't equal to anything, not even itself

Objects are compared by reference. Two objects are equal only if they are the same object:

JavaScript
const a = { id: 1 };
const b = { id: 1 };
a === b; // false — same content, but two different objects
a === a; // true

Coercion with other operators

JavaScript
"5" + 3;   // "53" — + with a string means string concatenation
"5" - 3;   // 2    — other maths operators convert to numbers
"5" * "2"; // 10
true + 1;  // 2
[] + {};   // "[object Object]"

Truthy and falsy

When a value is used in an if or with &&, || or !, it gets converted to a boolean. Only a handful of values are falsy. Everything else is truthy.

  • Falsy: false, 0, -0, 0n, "", null, undefined, NaN
  • Truthy (often surprising): "0", "false", " ", [], {} and every function
JavaScript
if ([]) console.log("runs"); // an empty array is truthy

Follow-ups

  • How do you check for NaN? Use Number.isNaN(x). The older global isNaN() converts its argument first, so isNaN("abc") is true while Number.isNaN("abc") is false.
  • What does Object.is() do? It works like ===, except that Object.is(NaN, NaN) is true and Object.is(0, -0) is false.
  • What do "hello" || "world" and "foo" && "bar" return? "hello" and "bar". || returns the first truthy value (or the last value if none is truthy). && returns the first falsy value (or the last value if all are truthy). Neither one returns a plain boolean.
Q05Not marked yetFresherAsked oftenBasics

What is the difference between null, undefined and undeclared?

Say this first~20 sec to say

undefined means a variable exists but hasn't been given a value, and JavaScript sets it automatically. null means "intentionally empty", and you assign it. An undeclared variable was never declared at all, so reading it throws a ReferenceError. NaN is a fourth empty-looking value: it's what failed maths returns, and its type is number.

Could you say it?
Full answer2 min read

Where undefined comes from

  • A declared variable with no value: let a;
  • A property the object doesn't have: user.middleName
  • A function parameter nobody passed
  • The return value of a function with no return
JavaScript
let a;
let b = null;

console.log(a);        // undefined — declared, never assigned
console.log(b);        // null — deliberately empty
console.log(typeof a); // "undefined"
console.log(typeof b); // "object" (the historic bug from question 1)
console.log(typeof c); // "undefined" — typeof is safe on undeclared names
console.log(c);        // ReferenceError: c is not defined
undefinednullUndeclaredNaN
MeaningNo value assigned yetIntentionally emptyNever declaredNot a valid number
Set byJavaScriptThe developerNobodyFailed maths, e.g. "abc" * 2
typeof"undefined""object""undefined""number"
In mathsundefined + 1 is NaNnull + 1 is 1ReferenceErrorAlways NaN
How to checkx === undefinedx === nulltypeof x === "undefined"Number.isNaN(x)

Handy tricks

  • x == null (or x ?? fallback) catches both null and undefined in one go.
  • JSON.stringify({ a: undefined, b: null }) gives '{"b":null}'. Properties set to undefined are dropped and null is kept, which matters when you send data to an API.

Follow-up

  • What does "use strict" change here? Without strict mode, assigning to an undeclared variable (count = 5) silently creates a global variable. In strict mode it throws a ReferenceError. Strict mode also makes this undefined in plain function calls and blocks a few error-prone features. ES modules and classes always run in strict mode.
Q06Not marked yetFresherAsked very oftenFunctions & scope

What is a closure?

Say this first~20 sec to say

A closure is a function bundled together with the variables from the scope where it was created. The inner function keeps access to those outer variables even after the outer function has finished running. Closures are how JavaScript gives you private variables, function factories, memoization, and helpers like debounce and once.

Could you say it?
Full answer5 min read

Start with lexical scope. Which variables a function can see depends on where it's written in the code, not where it's called from. When a variable isn't found locally, JavaScript looks in the enclosing scope, then the next one out, all the way to the global scope. That lookup path is the scope chain, and a closure is what keeps it alive after the outer function returns.

JavaScript
function createCounter() {
  let count = 0; // private — nothing outside can touch it directly

  return {
    increment() {
      count++;
      return count;
    },
    getCount() {
      return count;
    },
  };
}

const counter = createCounter();
counter.increment();             // 1
counter.increment();             // 2
console.log(counter.getCount()); // 2
console.log(counter.count);      // undefined — not exposed

const another = createCounter(); // a fresh closure with its own `count`
another.increment();             // 1

Where closures are used

  • Data privacy: hiding state behind functions (the module pattern).
  • Function factories: const double = multiplier(2).
  • Caching (memoization): remembering results you've already calculated.
  • Callbacks and event handlers: remembering values until later.
  • Debounce and throttle: remembering the timer between calls (question 20).
  • React hooks: each render's event handlers close over that render's state.
JavaScript
function once(fn) {
  let called = false;
  let result;
  return function (...args) {
    if (!called) {
      called = true;
      result = fn.apply(this, args);
    }
    return result;
  };
}

const initApp = once(() => console.log("App initialised"));
initApp(); // "App initialised"
initApp(); // nothing — it already ran

The classic closure interview question

JavaScript
for (var i = 1; i <= 3; i++) {
  setTimeout(() => console.log(i), 1000);
}
// Prints: 4 4 4

There's only one i, because var is function-scoped. All three callbacks close over that same variable, and by the time they run, the loop has finished and i is 4.

JavaScript
// Fix 1: let creates a new `i` for every iteration
for (let i = 1; i <= 3; i++) {
  setTimeout(() => console.log(i), 1000);
}
// Prints: 1 2 3

// Fix 2 (before ES6): an IIFE gives each iteration its own scope
for (var i = 1; i <= 3; i++) {
  (function (n) {
    setTimeout(() => console.log(n), 1000);
  })(i);
}
// Prints: 1 2 3

An IIFE (Immediately Invoked Function Expression) is a function that's defined and called in one step: (function () { … })(). Before let and modules existed, it was the standard way to create a private scope.

Follow-ups

  • What is memoization? Caching a function's results so that calling it again with the same input returns instantly. The cache lives in a closure:
JavaScript
function memoize(fn) {
  const cache = new Map();
  return function (n) {
    if (!cache.has(n)) {
      cache.set(n, fn(n)); // compute only the first time
    }
    return cache.get(n);
  };
}

const square = memoize((n) => {
  console.log("Computing…");
  return n * n;
});

square(9); // logs "Computing…" and returns 81
square(9); // returns 81 straight from the cache
  • Can closures cause memory leaks? Yes. Variables captured by a closure stay in memory for as long as the closure can be reached. For example, an event listener that's never removed keeps everything it references alive. Remove listeners and clear timers when you no longer need them.
Q07Not marked yet1–3 yrsAsked very oftenFunctions & scope

How does the this keyword work?

Say this first~30 sec to say

In a regular function, this is decided by how the function is called, not where it's written. Call it as obj.method() and this is obj. Call it with new and this is the new object. Call it through call, apply or bind and this is whatever you pass in. Call it on its own and this is undefined in strict mode (the global object otherwise). Arrow functions don't have their own this; they use the this of the surrounding code.

Could you say it?
Full answer2 min read
How the function is calledWhat this is
obj.method()obj, the object before the dot
fn() (a plain call)undefined in strict mode, window/globalThis otherwise
new Fn()The newly created object
fn.call(x), fn.apply(x), fn.bind(x)x
Arrow functionThe this of the surrounding scope, fixed when the arrow is created
DOM listener written with functionThe element the listener is attached to
JavaScript
const user = {
  username: "Asha",
  greet() {
    return `Hi, I'm ${this.username}`;
  },
};

user.greet(); // "Hi, I'm Asha" — called on user

const greet = user.greet; // copied out of the object
greet(); // "Hi, I'm undefined" (TypeError in strict mode) — nothing before the dot

const boundGreet = user.greet.bind(user);
boundGreet(); // "Hi, I'm Asha" — this is locked to user

Arrow functions: the good and the bad

JavaScript
const user = {
  username: "Asha",

  // ❌ Arrow function as a method: `this` is NOT user, it comes from the outer scope
  greetArrow: () => `Hi, I'm ${this.username}`,

  // ✅ Arrow function inside a method: it inherits `this` from greetLater
  greetLater() {
    setTimeout(() => {
      console.log(`Hi, I'm ${this.username}`); // "Hi, I'm Asha"
    }, 1000);
  },
};

Follow-ups

  • Why does setTimeout(user.greet, 1000) lose this? You're passing the function on its own, so it's later called as a plain function. Fix it with setTimeout(() => user.greet(), 1000) or setTimeout(user.greet.bind(user), 1000).
  • Can you change an arrow function's this with call or bind? No. Arrow functions ignore it.
  • What is this at the top level? window in a classic browser script, and undefined in an ES module.
Q08Not marked yet1–3 yrsAsked oftenFunctions & scope

What is the difference between call, apply and bind?

Say this first~20 sec to say

All three let you choose what this should be. call runs the function immediately, with arguments passed one by one. apply also runs it immediately, but takes the arguments as an array. bind doesn't run it. Instead it returns a new function with this (and optionally some arguments) permanently set, which you can call later.

Could you say it?
Full answer2 min read

Memory trick: Call takes Commas, Apply takes an Array, Bind is for Back later.

JavaScript
function introduce(city, role) {
  return `${this.name} from ${city}, works as ${role}`;
}

const person = { name: "Asha" };

introduce.call(person, "Pune", "Frontend Dev");    // arguments one by one
introduce.apply(person, ["Pune", "Frontend Dev"]); // arguments as an array

const introduceAsha = introduce.bind(person, "Pune"); // a new function, with city pre-filled
introduceAsha("Frontend Dev");
// All three return "Asha from Pune, works as Frontend Dev"

When to use each

  • call: to borrow a method from another object, e.g. Array.prototype.slice.call(arguments).
  • apply: when your arguments are already in an array, e.g. Math.max.apply(null, scores). Today you'd write Math.max(...scores).
  • bind: to fix this for a callback or event handler that runs later, or to pre-fill arguments (partial application).

Write your own bind (a common follow-up)

JavaScript
Function.prototype.myBind = function (context, ...boundArgs) {
  const fn = this; // the function myBind was called on
  return function (...args) {
    return fn.apply(context, [...boundArgs, ...args]);
  };
};

const introduceRavi = introduce.myBind({ name: "Ravi" }, "Delhi");
introduceRavi("Backend Dev"); // "Ravi from Delhi, works as Backend Dev"

This simple version is what interviewers expect. The real bind also handles being called with new. (Adding methods to built-in prototypes is fine in an interview answer, but avoid it in real code. See question 14.)

Follow-up

  • Can you re-bind a bound function? No. fn.bind(a).bind(b) still uses a, because the first bind wins.
Q09Not marked yetFresherAsked very oftenFunctions & scope

How are arrow functions different from regular functions?

Say this first~25 sec to say

Arrow functions are shorter to write, but the real differences are in how they behave. They have no this of their own (they use the surrounding this), no arguments object, and can't be called with new. Like any function stored in a variable, they can't be called before the line that defines them. Use them for callbacks, and avoid them for object methods and constructors.

Could you say it?
Full answer2 min read
Regular functionArrow function
thisDepends on how it's calledTaken from the surrounding scope
arguments objectYesNo, so use rest parameters (...args)
Can be called with newYesNo, it throws a TypeError
HoistingDeclarations are fully hoistedFollows the let/const/var rules
Implicit returnNoYes, for a single expression
Good as an object methodYesNo
Good as a callbackWorksIdeal, because it keeps the outer this
JavaScript
// Syntax shortcuts
const add = (a, b) => a + b;           // implicit return
const square = (n) => n * n;
const makeUser = (name) => ({ name }); // to return an object, wrap it in ()

// No `arguments`, so use rest parameters instead
function regular() {
  return arguments.length;
}
const arrow = (...args) => args.length;
regular(1, 2, 3); // 3
arrow(1, 2, 3);   // 3

// Not a constructor
const Person = (name) => {
  this.name = name;
};
new Person("Asha"); // TypeError: Person is not a constructor

Where arrow functions shine: callbacks inside methods

JavaScript
class Timer {
  seconds = 0;

  start() {
    setInterval(() => {
      this.seconds++; // `this` is still the Timer instance
    }, 1000);
  }
}

With a regular function here, this would be window (in a browser) instead of the Timer, so this.seconds would never update the timer.

Q10Not marked yetFresherAsked oftenFunctions & scope

What are higher-order functions and callbacks?

Say this first~20 sec to say

In JavaScript, functions are first-class. They're values, so you can store them in variables, pass them to other functions and return them from functions. A higher-order function takes a function as an argument, returns a function, or both. A callback is the function you pass in, which the higher-order function calls later.

Could you say it?
Full answer2 min read
JavaScript
// 1. Stored in a variable
const greet = (name) => `Hello, ${name}`;

// 2. Passed as an argument: greet is the callback, processUser is higher-order
function processUser(name, callback) {
  return callback(name);
}
processUser("Asha", greet); // "Hello, Asha"

// 3. Returned from a function: multiplier is higher-order
function multiplier(factor) {
  return (n) => n * factor;
}
const double = multiplier(2);
double(5); // 10

Built-in higher-order functions you already use: map, filter, reduce, forEach, sort, find, some, every, setTimeout, addEventListener and Promise.then.

Synchronous vs asynchronous callbacks

  • [1, 2, 3].map(cb) calls cb straight away, during the map call.
  • setTimeout(cb, 1000) and button.addEventListener("click", cb) call cb later.

Callback hell

When async steps depend on each other, nested callbacks grow sideways into a "pyramid of doom". The code becomes hard to read, and every level needs its own error handling:

JavaScript
getUser(id, (user) => {
  getOrders(user.id, (orders) => {
    getInvoice(orders[0].id, (invoice) => {
      sendEmail(invoice, () => {
        console.log("Done"); // four levels deep, with no shared error handling
      });
    });
  });
});

Promises and async/await (questions 18 and 19) flatten this.

Q11Not marked yet1–3 yrsAsked oftenFunctions & scope

What is currying?

Say this first~20 sec to say

Currying turns a function that takes several arguments, f(a, b, c), into a chain of functions that each take one argument, f(a)(b)(c). Each inner function remembers the earlier arguments through closures. It's useful for building specialised, reusable functions by filling in some arguments ahead of time.

Could you say it?
Full answer2 min read
JavaScript
// Normal
const add = (a, b, c) => a + b + c;
add(1, 2, 3); // 6

// Curried
const curriedAdd = (a) => (b) => (c) => a + b + c;
curriedAdd(1)(2)(3); // 6

// Fill in the first argument once, then reuse
const addTen = curriedAdd(10);
addTen(5)(1); // 16

A practical example

JavaScript
const log = (level) => (message) => console.log(`[${level}] ${message}`);

const logError = log("ERROR");
const logInfo = log("INFO");

logError("Payment failed"); // [ERROR] Payment failed
logInfo("User signed in");  // [INFO] User signed in

Write a generic curry function (asked at 1–3 years)

JavaScript
function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args); // enough arguments: call the original
    }
    return (...next) => curried.apply(this, [...args, ...next]); // wait for more
  };
}

const sum3 = curry((a, b, c) => a + b + c);
sum3(1)(2)(3); // 6
sum3(1, 2)(3); // 6
sum3(1)(2, 3); // 6

fn.length is the number of parameters the function declares. It stops counting at the first parameter with a default value and ignores rest parameters.

A popular puzzle: sum(1)(2)(3)()

JavaScript
const sum = (a) => (b) => (b === undefined ? a : sum(a + b));

sum(1)(2)(3)(); // 6
sum(5)(10)();   // 15

Each call adds to the running total, and the final empty call () returns it.

Currying vs partial application: currying always produces functions that take one argument each. Partial application (for example, bind with arguments) fills in some arguments and takes the rest all at once.

Q12Not marked yet1–3 yrsAsked oftenObjects & arrays

What is prototypal inheritance?

Say this first~25 sec to say

Every JavaScript object has a hidden link to another object called its prototype. When you read a property the object doesn't have, JavaScript looks for it on the prototype, then on the prototype's prototype, and so on until it reaches null. This is the prototype chain, and it's how objects share and inherit methods. ES6 class syntax is a cleaner way to write the same prototype-based system.

Could you say it?
Full answer3 min read
JavaScript
const animal = {
  eat() {
    return `${this.name} is eating`;
  },
};

const dog = Object.create(animal); // dog's prototype is animal
dog.name = "Bruno";

dog.eat();                             // "Bruno is eating" — found on the prototype
Object.getPrototypeOf(dog) === animal; // true
"eat" in dog;                          // true  — `in` checks the whole chain
Object.hasOwn(dog, "eat");             // false — not one of dog's own properties

The chain behind an everyday array

const nums = [1, 2, 3];

nums ──▶ Array.prototype ──▶ Object.prototype ──▶ null
         (map, filter,       (toString,
          push, …)            hasOwnProperty, …)

That's why every array can call .map(). The method is stored once on Array.prototype, not copied onto each array.

Constructor functions and classes

JavaScript
// Before ES6: a constructor function plus its prototype
function Person(name) {
  this.name = name;
}
Person.prototype.greet = function () {
  return `Hi, I'm ${this.name}`;
};

// ES6 class: the same mechanism under the hood
class Animal {
  constructor(name) {
    this.name = name;
  }
  speak() {
    return `${this.name} makes a sound`;
  }
}

class Dog extends Animal {
  speak() {
    return `${super.speak()} — Woof!`;
  }
}

new Dog("Bruno").speak(); // "Bruno makes a sound — Woof!"
typeof Animal;            // "function" — classes are functions

prototype vs __proto__

  • prototype is a property of constructor functions and classes. Objects created with new get it as their prototype.
  • __proto__ (or, better, Object.getPrototypeOf(obj)) is the link every object has to its own prototype.
  • So new Person("Asha").__proto__ === Person.prototype is true.

Follow-ups

  • How is this different from classical inheritance in Java or C++? In class-based languages, a class is a blueprint and objects are built from it. In JavaScript, objects inherit directly from other objects by delegation: when a property is missing, it's looked up on the linked object at runtime. Nothing gets copied.
  • What does new do? It (1) creates an empty object, (2) sets that object's prototype to Constructor.prototype, (3) runs the constructor with this pointing at the new object, and (4) returns the object, unless the constructor returns a different object.
Q13Not marked yet1–3 yrsAsked oftenObjects & arrays

What is the difference between a shallow copy and a deep copy?

Say this first~25 sec to say

A shallow copy duplicates only the top level, so nested objects and arrays are still shared with the original. A deep copy duplicates every level, so the copy is completely independent. Spread ({ ...obj }, [...arr]) and Object.assign make shallow copies. structuredClone() makes deep copies. JSON.parse(JSON.stringify(obj)) also makes a deep copy, but it breaks dates, drops functions and undefined, and more.

Could you say it?
Full answer3 min read
JavaScript
const user = {
  name: "Asha",
  address: { city: "Pune" },
};

// Shallow copy
const shallow = { ...user };
shallow.name = "Ravi";          // top level: independent
shallow.address.city = "Delhi"; // nested: shared with the original!
console.log(user.name);         // "Asha"
console.log(user.address.city); // "Delhi" ← the original changed

// Deep copy
const deep = structuredClone(user);
deep.address.city = "Mumbai";
console.log(user.address.city); // still "Delhi" — the original is untouched
MethodCopy typeWatch out for
{ ...obj } / [...arr]ShallowNested objects are shared
Object.assign({}, obj)ShallowSame as spread
arr.slice() / Array.from(arr)ShallowArrays only
JSON.parse(JSON.stringify(obj))DeepDrops functions and undefined. A Date becomes a string, NaN becomes null, Map and Set become {}, and circular references throw an error
structuredClone(obj)DeepBuilt into modern browsers and Node 17+. Handles Date, Map, Set and circular references. Throws on functions and DOM nodes, and class instances come back as plain objects
Lodash _.cloneDeep(obj)DeepAdds a dependency

Write a simple deep clone (a common task)

JavaScript
function deepClone(value) {
  if (value === null || typeof value !== "object") {
    return value; // primitives (and functions) are returned as they are
  }

  const copy = Array.isArray(value) ? [] : {};
  for (const key of Object.keys(value)) {
    copy[key] = deepClone(value[key]); // recurse into every level
  }
  return copy;
}

If asked about its limits: it doesn't handle Date, Map, Set or circular references.

Why this matters in React: state has to be updated immutably. If you copy only the top level and then change a nested field, you've mutated the old state too, and React may not re-render. Copy every level you change: { ...user, address: { ...user.address, city: "Delhi" } }.

Q14Not marked yetFresherAsked very oftenObjects & arrays

What is the difference between map, forEach, filter and reduce?

Say this first~30 sec to say

forEach runs a function on each item and returns nothing, so use it for side effects. map transforms each item and returns a new array of the same length. filter returns a new array containing only the items that pass a test. reduce combines the whole array into a single value, which can be a number, an object or anything else. None of them change the original array unless your callback does.

Could you say it?
Full answer3 min read
JavaScript
const prices = [100, 250, 400, 50];

prices.forEach((p) => console.log(p));               // logs each price, returns undefined
const labels = prices.map((p) => `₹${p}`);           // ["₹100", "₹250", "₹400", "₹50"]
const expensive = prices.filter((p) => p > 200);     // [250, 400]
const total = prices.reduce((sum, p) => sum + p, 0); // 800
forEachmapfilterreduce
ReturnsundefinedA new array, same lengthA new array, same length or shorterOne value of any type
ChainableNoYesYesDepends on what it returns
Use it toDo something with each itemTransform every itemKeep the matching itemsCombine items into one result

None of them can break out early. When you need to stop, use for...of, some(), every() or find().

Chaining them

JavaScript
const orders = [
  { item: "Book", price: 300, delivered: true },
  { item: "Pen", price: 20, delivered: false },
  { item: "Bag", price: 900, delivered: true },
];

const deliveredTotal = orders
  .filter((order) => order.delivered)
  .map((order) => order.price)
  .reduce((sum, price) => sum + price, 0); // 1200

reduce can build objects too, for example to group items

JavaScript
const skills = ["angular", "aws", "babel", "bootstrap", "css"];

const byLetter = skills.reduce((groups, skill) => {
  const letter = skill[0];
  if (!groups[letter]) groups[letter] = [];
  groups[letter].push(skill);
  return groups;
}, {});
// { a: ["angular", "aws"], b: ["babel", "bootstrap"], c: ["css"] }

Write your own map (a very common follow-up)

JavaScript
Array.prototype.myMap = function (callback) {
  const result = [];
  for (let i = 0; i < this.length; i++) {
    result.push(callback(this[i], i, this)); // value, index, original array
  }
  return result;
};

[1, 2, 3].myMap((n) => n * 10); // [10, 20, 30]

Practise myFilter and myReduce the same way. For reduce, remember that without an initial value it starts from the first element, and on an empty array it throws a TypeError.

Adding methods to built-in prototypes is fine in an interview, but avoid it in real projects. A method added like this shows up in every for...in loop over an array, and it can clash with features JavaScript adds in the future.

Gotcha: ["1", "2", "3"].map(parseInt) returns [1, NaN, NaN]. map passes (value, index), so this runs parseInt("2", 1) (1 isn't a valid radix) and parseInt("3", 2) ("3" isn't a binary digit). Write .map(Number) or .map((s) => parseInt(s, 10)) instead.

Q15Not marked yetFresherAsked oftenObjects & arrays

What is the difference between the spread and rest operators?

Say this first~25 sec to say

Both are written as ..., but they do opposite jobs. Spread unpacks an array or object into individual items. You use it in function calls, array literals and object literals. Rest packs the remaining items into one array or object. You use it in function parameters and destructuring. The simple rule: in a parameter list or on the left of =, it's rest. Everywhere else, it's spread.

Could you say it?
Full answer2 min read
JavaScript
// SPREAD: unpack
const nums = [1, 2, 3];
Math.max(...nums);                    // 3 — same as Math.max(1, 2, 3)
const more = [...nums, 4, 5];         // [1, 2, 3, 4, 5]

const user = { name: "Asha", age: 24 };
const updated = { ...user, age: 25 }; // { name: "Asha", age: 25 } — later keys win

// REST: pack
function sum(...values) {             // all arguments collected into an array
  return values.reduce((total, n) => total + n, 0);
}
sum(1, 2, 3, 4);                      // 10

const [first, ...others] = [10, 20, 30]; // first = 10, others = [20, 30]
const { name, ...details } = user;       // name = "Asha", details = { age: 24 }

Destructuring is usually asked about at the same time

JavaScript
// Arrays: matched by position
const [a, , c = 99] = [1, 2]; // a = 1, the second item is skipped, c = 99 (the default)

let x = 1;
let y = 2;
[x, y] = [y, x];              // swap without a temp variable: x = 2, y = 1

// Objects: matched by property name
const { city = "Unknown", role: jobTitle } = { role: "SDE" };
// city = "Unknown" (default value), jobTitle = "SDE" (renamed)

// Nested, directly in the parameters
function printUser({ name, address: { city } }) {
  console.log(`${name} lives in ${city}`);
}
printUser({ name: "Ravi", address: { city: "Bengaluru" } }); // "Ravi lives in Bengaluru"

Rules to remember

  • Rest must come last: const [...a, b] = arr is a SyntaxError.
  • Spread makes a shallow copy (see question 13).
Q16Not marked yetFresherAsked oftenModern JavaScript

Which ES6+ features do you use most?

Say this first~15 sec to say

ES6 (2015) was the biggest update JavaScript has had. The features used every day are let/const, arrow functions, template literals, destructuring, spread/rest, default parameters, classes, modules (import/export) and Promises. Later versions added async/await (ES2017), optional chaining ?. and nullish coalescing ?? (both ES2020).

Could you say it?
Full answer4 min read
FeatureExampleCovered in
let / constconst total = 0;Question 2
Arrow functionsconst add = (a, b) => a + b;Question 9
Template literals`Hello, ${name}`Below
Destructuringconst { name } = user;Question 15
Spread / rest[...arr], (...args)Question 15
Default parametersfunction greet(name = "Guest") {}Below
Classesclass Dog extends Animal {}Question 12
Modulesimport { add } from "./math.js";Below
Promisesfetch(url).then(…)Question 18
for...offor (const item of list) {}Below
Map / Setnew Set([1, 1, 2])Below
async / await (ES2017)const res = await fetch(url);Question 19
Optional chaining ?. (ES2020)user?.address?.cityBelow
Nullish coalescing ?? (ES2020)count ?? 10Below
JavaScript
// Template literals: interpolation and multi-line strings
const candidate = "Asha";
const message = `Hi ${candidate},
your interview is at ${10 + 1} AM.`;

// Default parameters
function greet(name = "Guest") {
  return `Welcome, ${name}`;
}
greet(); // "Welcome, Guest"

// Optional chaining: stops safely at null/undefined instead of crashing
const user = { profile: null };
user.profile?.email; // undefined (no TypeError)
user.getAge?.();     // undefined — only calls the function if it exists

// Nullish coalescing: falls back ONLY for null/undefined
const count = 0;
count || 10; // 10 — || treats 0 as missing ❌
count ?? 10; // 0  — ?? keeps valid falsy values like 0 and "" ✅

// Set holds unique values; Map holds key–value pairs with keys of any type
const uniqueTags = [...new Set(["js", "css", "js"])]; // ["js", "css"]
const visits = new Map();
visits.set("/jobs", 3);
visits.get("/jobs"); // 3

Modules

JavaScript
// math.js
export const add = (a, b) => a + b;      // named export
export default function multiply(a, b) { // default export (one per file)
  return a * b;
}
JavaScript
// app.js
import multiply, { add } from "./math.js"; // default import first, named imports in { }

for...in vs for...of

JavaScript
const langs = ["JS", "TS"];

for (const i in langs) console.log(i);       // "0", "1" — keys, as strings
for (const lang of langs) console.log(lang); // "JS", "TS" — values
  • for...of loops over the values of iterables: arrays, strings, Map and Set. It doesn't work on plain objects (TypeError: obj is not iterable), so use Object.entries(obj) for those.
  • for...in loops over the keys of an object, including inherited enumerable ones. Avoid it for arrays.

Follow-up

  • ES Modules vs CommonJS? ES Modules (import/export) are the language standard. They work natively in browsers and can be analysed before the code runs, which is what makes tree shaking possible. CommonJS (require/module.exports) is Node's older system, and it loads modules while the code is running.
Q17Not marked yet1–3 yrsAsked very oftenAsync JavaScript

How does the event loop work?

Say this first~30 sec to say

JavaScript is single-threaded: it has one call stack and runs one thing at a time. Slow work like timers, network requests and user events is handled by the browser through Web APIs. When that work finishes, its callback waits in a queue. The event loop waits until the call stack is empty, runs all the microtasks (Promise callbacks and code after await), then runs one macrotask (such as a setTimeout callback or a click handler), and repeats.

Could you say it?
Full answer4 min read

The pieces

PieceWhat it doesExamples
Call stackRuns functions one at a time, last in, first outYour synchronous code
Web APIsBrowser features that do the waiting outside the stacksetTimeout, fetch, DOM events
Microtask queueHigh-priority callbacks. All of them run before the next macrotask.then/.catch/.finally, code after await, queueMicrotask
Macrotask (task) queueCallbacks that run one per loopsetTimeout, setInterval, click and keyboard events
Event loopWhen the stack is empty, moves the next callback onto it—

One turn of the loop: run the sync code → stack is empty → run ALL microtasks → the browser may repaint → run ONE macrotask → repeat

The classic output question

JavaScript
console.log("1: start");

setTimeout(() => console.log("2: timeout"), 0);

Promise.resolve().then(() => console.log("3: promise"));

console.log("4: end");

Output:

1: start
4: end
3: promise
2: timeout

Lines 1 and 4 are synchronous, so they run first. Once the stack is empty, the microtask (the Promise callback) runs before the macrotask (the setTimeout callback), even though the timeout is 0 ms.

A harder version

JavaScript
console.log("A");

setTimeout(() => {
  console.log("B");
  Promise.resolve().then(() => console.log("C"));
}, 0);

Promise.resolve().then(() => {
  console.log("D");
  setTimeout(() => console.log("E"), 0);
});

console.log("F");

// Output: A F D B C E

A and F are synchronous. Next the microtask runs and logs D, and it queues timeout E behind the timeout that logs B. Then the next macrotask logs B and queues microtask C. Microtasks run straight after every macrotask, so C comes before E.

await splits code into microtasks too

JavaScript
async function run() {
  console.log("1");
  await null;
  console.log("2"); // everything after await runs later, as a microtask
}

run();
console.log("3");

// Output: 1 3 2

Key takeaways

  • setTimeout(fn, 0) means "run as soon as possible after the current code and all the microtasks", not "run immediately".
  • A long synchronous loop blocks everything: clicks, rendering and timers. Split heavy work into smaller chunks or move it to a Web Worker.
  • If microtasks keep queueing more microtasks forever, the page never gets a chance to repaint.
Q18Not marked yetFresherAsked very oftenAsync JavaScript

What is a Promise?

Say this first~30 sec to say

A Promise is an object that stands for a value you'll get in the future, such as the result of an API call. It's always in one of three states: pending, fulfilled (it worked, and here's the value) or rejected (it failed, and here's the error). Once it settles, it never changes. You handle the result with .then(), errors with .catch() and cleanup with .finally(). Each of these returns a new Promise, so you can chain steps instead of nesting callbacks.

Could you say it?
Full answer4 min read

Creating and using a Promise

JavaScript
function fetchUser(id) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (id > 0) resolve({ id, name: "Asha" });
      else reject(new Error("Invalid user id"));
    }, 500);
  });
}

fetchUser(1)
  .then((user) => {
    console.log(user.name); // "Asha"
    return user.id;         // passed to the next .then
  })
  .then((id) => console.log("User id:", id))
  .catch((err) => console.error(err.message)) // catches an error from ANY step above
  .finally(() => console.log("Done"));        // runs either way, e.g. to hide a loader

Why Promises beat callbacks

  • You get a flat chain instead of a nested pyramid.
  • One .catch() handles errors from every step.
  • Several async tasks are easy to combine (see the table below).

Running several Promises together

MethodFulfils whenRejects whenUse it for
Promise.allAll fulfil, giving an array of results in the original orderAny one rejects (it fails fast)Requests you need all of, e.g. user plus settings
Promise.allSettledAll settle, giving an array of { status, value } or { status, reason }NeverIndependent requests where partial success is fine
Promise.raceThe first to settle fulfilsThe first to settle rejectsAdding a timeout
Promise.anyThe first to fulfil does soAll reject, giving an AggregateErrorUsing the fastest of several mirrors or CDNs
JavaScript
const p1 = Promise.resolve(1);
const p2 = Promise.reject(new Error("fail"));
const p3 = new Promise((resolve) => setTimeout(() => resolve(3), 100));

Promise.all([p1, p3]).then(console.log);                            // [1, 3]
Promise.all([p1, p2, p3]).catch((err) => console.log(err.message)); // "fail"
Promise.allSettled([p1, p2]).then(console.log);
// [{ status: "fulfilled", value: 1 }, { status: "rejected", reason: Error: fail }]
Promise.any([p2, p3]).then(console.log);                            // 3

A timeout with Promise.race

JavaScript
const timeout = (ms) =>
  new Promise((_, reject) => setTimeout(() => reject(new Error("Request timed out")), ms));

Promise.race([fetch("/api/jobs"), timeout(5000)])
  .then((res) => res.json())
  .then((jobs) => console.log(jobs))
  .catch((err) => console.error(err.message));

Write Promise.all yourself (frequent at 1–3 years)

JavaScript
function promiseAll(promises) {
  return new Promise((resolve, reject) => {
    const results = [];
    let completed = 0;

    if (promises.length === 0) return resolve(results);

    promises.forEach((promise, index) => {
      Promise.resolve(promise) // also accepts plain values
        .then((value) => {
          results[index] = value; // keep the original order, not the finishing order
          completed++;
          if (completed === promises.length) resolve(results);
        })
        .catch(reject); // the first failure rejects the whole thing
    });
  });
}

Common mistakes

  • Forgetting to return inside .then(), so the next .then() receives undefined.
  • Nesting a .then() inside another .then(). That's callback hell again, so return the Promise and chain instead.
  • Leaving out .catch() at the end, so failures turn into "Uncaught (in promise)" errors.
Q19Not marked yetFresherAsked very oftenAsync JavaScript

How does async/await work?

Say this first~25 sec to say

async/await is a cleaner way to write Promise code, so that it reads top to bottom like synchronous code. An async function always returns a Promise. Inside it, await pauses that function (not the whole page) until the Promise settles. Then it gives you the value, or throws the error so you can catch it with try...catch.

Could you say it?
Full answer3 min read
JavaScript
async function loadJobs() {
  try {
    const res = await fetch("https://api.example.com/jobs");
    if (!res.ok) throw new Error(`HTTP ${res.status}`); // fetch doesn't reject on 404/500
    const jobs = await res.json();
    return jobs;
  } catch (err) {
    console.error("Could not load jobs:", err.message);
    return [];
  } finally {
    console.log("Request finished"); // runs after success and after failure
  }
}

loadJobs().then((jobs) => console.log(jobs.length)); // an async function returns a Promise

The same thing written with .then() chains:

JavaScript
function loadJobs() {
  return fetch("https://api.example.com/jobs")
    .then((res) => {
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      return res.json();
    })
    .catch((err) => {
      console.error("Could not load jobs:", err.message);
      return [];
    });
}

Sequential vs parallel (a favourite follow-up)

JavaScript
// ❌ Sequential: getPosts() waits for getUser() to finish. Total ≈ 2s + 1s = 3s
async function loadSequential() {
  const user = await getUser();   // takes 2s
  const posts = await getPosts(); // takes 1s
  return { user, posts };
}

// ✅ Parallel: both requests start at once. Total ≈ 2s
async function loadParallel() {
  const [user, posts] = await Promise.all([getUser(), getPosts()]);
  return { user, posts };
}

Only run requests one after another when the second one needs the result of the first.

Gotcha: await inside forEach doesn't wait

JavaScript
// Inside an async function:

// ❌ forEach ignores the Promises its callback returns
ids.forEach(async (id) => {
  await saveUser(id);
});
console.log("Saved!"); // runs before any save has finished

// ✅ One after another
for (const id of ids) {
  await saveUser(id);
}

// ✅ All at once
await Promise.all(ids.map((id) => saveUser(id)));

Remember

  • await only works inside an async function, or at the top level of an ES module.
  • fetch only rejects when the network fails. A 404 or 500 response still resolves, so check res.ok.
  • If async function f() { return 1; }, then f() returns a Promise that resolves to 1, not 1 itself.
Q20Not marked yet1–3 yrsAsked very oftenAsync JavaScript

What is the difference between debounce and throttle?

Say this first~20 sec to say

Both limit how often a function runs when an event fires over and over. Debounce waits until the events stop for a set time, then runs once. That's ideal for search-as-you-type. Throttle runs the function at most once per interval while the events keep firing. That's ideal for scroll and resize handlers.

Could you say it?
Full answer3 min read

An everyday picture: debounce is a lift door that closes only once people stop walking in. Throttle is a security guard who lets one person through every 5 seconds, however long the queue gets.

JavaScript
function debounce(fn, delay) {
  let timerId;
  return function (...args) {
    clearTimeout(timerId); // cancel the previously scheduled call
    timerId = setTimeout(() => {
      fn.apply(this, args); // runs only after `delay` ms with no new calls
    }, delay);
  };
}

// Call the search API only when the user pauses typing for 300ms
const searchInput = document.querySelector("#search");
searchInput.addEventListener(
  "input",
  debounce((event) => {
    console.log("Searching for:", event.target.value);
  }, 300)
);
JavaScript
function throttle(fn, limit) {
  let lastRun = 0;
  return function (...args) {
    const now = Date.now();
    if (now - lastRun >= limit) {
      lastRun = now;
      fn.apply(this, args); // runs at most once every `limit` ms
    }
  };
}

// Check the scroll position at most every 200ms
window.addEventListener(
  "scroll",
  throttle(() => {
    console.log("Scroll position:", window.scrollY);
  }, 200)
);
DebounceThrottle
When it runsOnce, after the events stopRegularly, at most once per interval
An event fires every 10 ms for 200 ms, with a 50 ms delay/limit1 call, about 50 ms after the last event4 calls, about 50 ms apart
Typical usesSearch boxes, auto-save, form validation, "resize finished"Scroll, mousemove, infinite scroll, stopping button spam
Built withA closure plus setTimeout/clearTimeoutA closure plus a timestamp (or a timer)

Both helpers rely on closures: timerId and lastRun survive between calls (see question 6). This version of throttle runs on the leading edge, so the last event in a burst can be skipped. Libraries like Lodash can also fire one final trailing call.

Q21Not marked yetFresherAsked oftenDOM & events

What is the DOM?

Say this first~30 sec to say

The DOM (Document Object Model) is the browser's live, tree-shaped model of the page. When the browser reads your HTML, it turns every element, attribute and piece of text into an object, called a node, in this tree. JavaScript reads and changes the page through these objects, and the browser updates the screen to match. The DOM isn't your HTML file. It's what the browser builds from that file, and JavaScript can change it at any time.

Could you say it?
Full answer3 min read
document
└── html
    ├── head
    │   └── title ── "Jobs"
    └── body
        ├── h1 ── "Openings"
        └── ul#job-list
            ├── li.job ── "Frontend Developer"
            └── li.job ── "Backend Developer"

Everyday DOM operations

JavaScript
// Select elements
const heading = document.getElementById("title");
const firstJob = document.querySelector(".job");   // the first match for any CSS selector
const allJobs = document.querySelectorAll(".job"); // every match, as a static NodeList

// Change content
heading.textContent = "Latest openings"; // safe — treated as plain text
// heading.innerHTML = userInput;        // ⚠️ parses HTML, so it's an XSS risk with user input

// Attributes, classes and styles
firstJob.setAttribute("aria-current", "true");
firstJob.classList.add("highlight");
firstJob.style.color = "teal";

// Create, insert and remove
const item = document.createElement("li");
item.textContent = "Data Analyst";
document.querySelector("#job-list").append(item);
item.remove();

Follow-ups

  • textContent vs innerText vs innerHTML? textContent returns all the text and is fast. innerText returns only the visible text, taking CSS into account, so it's slower because it triggers layout. innerHTML reads or writes HTML markup, so never give it untrusted input.
  • Attribute vs property? An attribute is what's written in the HTML. A property is the live value on the DOM object.
JavaScript
// <input id="name" value="Asha">, and then the user types "Ravi"
const input = document.getElementById("name");
input.value;                 // "Ravi" — the property holds the current value
input.getAttribute("value"); // "Asha" — the attribute keeps the original HTML value
  • What are data-* attributes? Custom attributes for storing extra information on an element. You read them through dataset, and dashed names become camelCase:
JavaScript
// <button data-job-id="42" data-company="acme">Apply</button>
button.dataset.jobId;   // "42"
button.dataset.company; // "acme"

Anyone can see and edit them in DevTools, so never store secrets in them or trust them for security checks.

  • NodeList vs HTMLCollection? querySelectorAll returns a static NodeList. It's a snapshot and has forEach. getElementsByClassName returns a live HTMLCollection that updates automatically when the DOM changes. It doesn't have forEach, so wrap it in Array.from() first.
  • What is the virtual DOM? Libraries like React keep a lightweight JavaScript copy of the UI. On every update they compare the new copy with the old one ("diffing") and apply only the changes that are needed to the real DOM, because real DOM updates can trigger expensive layout and repaint work.
Q22Not marked yetFresherAsked very oftenDOM & events

What are event bubbling, capturing and delegation?

Say this first~30 sec to say

When you click an element, the event travels through three phases: capturing (down from window to the clicked element), target (at the element itself), then bubbling (back up from the element to window). By default, listeners run during the bubbling phase. Event delegation takes advantage of bubbling: instead of adding a listener to every child, you add one listener to a shared parent and check event.target to see which child was clicked.

Could you say it?
Full answer2 min read
Capturing ↓   window → document → html → body → ul → li (target)
Bubbling  ↑   li → ul → body → html → document → window
JavaScript
// <div id="parent"><button id="child">Click me</button></div>
const parent = document.getElementById("parent");
const child = document.getElementById("child");

parent.addEventListener("click", () => console.log("Parent (bubbling)"));
parent.addEventListener("click", () => console.log("Parent (capturing)"), { capture: true });
child.addEventListener("click", () => console.log("Child"));

// Clicking the button logs:
// Parent (capturing)
// Child
// Parent (bubbling)

Event delegation

JavaScript
// <ul id="job-list">
//   <li data-id="1">Frontend Developer</li>
//   <li data-id="2">Backend Developer</li>
//   …hundreds more, some added later
// </ul>
const list = document.getElementById("job-list");

list.addEventListener("click", (event) => {
  const item = event.target.closest("li"); // works even if the click landed on a child of <li>
  if (!item) return;
  console.log("Clicked job:", item.dataset.id);
});

Why use it:

  • One listener instead of hundreds means less memory and less setup.
  • It works for elements added later, such as infinite scroll or new search results, without re-attaching listeners.

Watch out: a few events don't bubble, including focus, blur, mouseenter and mouseleave. For delegation, use focusin/focusout or mouseover/mouseout instead.

Controlling events

MethodWhat it does
event.stopPropagation()Stops the event from travelling any further, so ancestors never hear it
event.stopImmediatePropagation()The same, and it also skips any other listeners on the same element
event.preventDefault()Cancels the browser's default action (submitting a form, following a link, ticking a checkbox). The event still bubbles.
JavaScript
form.addEventListener("submit", (event) => {
  event.preventDefault(); // stop the page from reloading and handle the form in JavaScript
});

event.target vs event.currentTarget: target is the element that was actually clicked, the deepest one. currentTarget is the element whose listener is running right now. In delegation, that's the parent.

02HTML

0/5 got it

Q23Not marked yetFresherAsked very oftenSemantics

What is semantic HTML and why does it matter?

Say this first~20 sec to say

Semantic HTML means using elements that describe what the content is, such as <header>, <nav>, <main>, <article> and <button>, instead of using <div> and <span> for everything. It matters for three reasons. Accessibility: screen readers and keyboard users can understand and move around the page. SEO: search engines understand its structure. Maintainability: the code explains itself.

Could you say it?
Full answer4 min read
HTML
<!-- ❌ Div soup: meaningless to browsers, screen readers and search engines -->
<div class="header">
  <div class="nav">…</div>
</div>
<div class="content">
  <div class="job-card">…</div>
</div>
<div class="btn" onclick="apply()">Apply</div>

<!-- ✅ Semantic -->
<header>
  <nav>…</nav>
</header>
<main>
  <article class="job-card">…</article>
</main>
<button type="button" onclick="apply()">Apply</button>
ElementUse it for
<header>Introductory content for the page or a section: logo, title, navigation
<nav>The main navigation links
<main>The page's main content (only one visible per page)
<section>A themed group of content, usually with its own heading
<article>Self-contained content that makes sense on its own: a blog post, a job card, a comment
<aside>Related side content: a sidebar, related links
<footer>Closing content for the page or a section
<figure> + <figcaption>An image or diagram with a caption
<time>A date or time: <time datetime="2026-10-22">22 Oct</time>

Why <button> beats <div onclick>: a real button can be reached with Tab, works with Enter and Space, and is announced as a "button" by screen readers, all for free. A div needs tabindex, a role and keyboard handlers before it behaves the same way.

Follow-ups

  • div vs span? Neither carries any meaning. <div> is block-level (it starts on a new line and takes the full width) and groups larger chunks. <span> is inline and wraps a piece of text. Use them only when no semantic element fits.
  • section vs article vs div? If the content could stand alone, for example shared or syndicated on another site, it's an article. If it's a themed part of the page with a heading, it's a section. If you only need a wrapper for styling, it's a div.
  • <strong> vs <b>, and <em> vs <i>? They look similar by default, but they mean different things. <strong> marks text as important, and <em> adds stress that changes the meaning of a sentence. <b> and <i> only set text apart visually (a keyword, a foreign phrase, a book title) without adding any importance.
  • What did HTML5 add? Semantic elements (above), native <audio> and <video>, <canvas>, inline SVG, new input types (email, date, number, range), built-in form validation (required, pattern), localStorage/sessionStorage, and data-* attributes.
  • <canvas> vs <svg>? SVG is vector graphics written as markup. Every shape is a DOM element, it scales without blurring, it can be styled with CSS, and it's easier to make accessible, which suits icons, logos and charts. Canvas is a bitmap you draw on with JavaScript. Nothing inside it is a DOM element, and it copes well with thousands of moving objects, which suits games, image editors and heavy visualisations.
  • Headings: use a single <h1> for the page title, and don't skip heading levels just to get a smaller font. Screen-reader users jump between headings to find their way around.
Q24Not marked yetFresherAsked sometimesDocument structure

What does <!DOCTYPE html> do?

Say this first~25 sec to say

<!DOCTYPE html> has to be the very first line of an HTML document. It isn't an HTML tag. It's an instruction that tells the browser to render the page in standards mode, following modern HTML and CSS rules. Without it, browsers switch to quirks mode and imitate the bugs of late-1990s browsers so very old sites don't break. In quirks mode your layout can behave unpredictably.

Could you say it?
Full answer1 min read
HTML
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Frontend Jobs</title>
  </head>
  <body>
    <h1>Hello, world</h1>
  </body>
</html>
  • The HTML5 doctype is short and case-insensitive. Older ones pointed to a DTD file, for example <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">.
  • Some examples of quirks-mode behaviour: CSS lengths without units (width: 100) are accepted as pixels, and percentage heights and table styles are calculated the old way.
  • You can check the mode in the console. document.compatMode returns "CSS1Compat" in standards mode and "BackCompat" in quirks mode.

Follow-up

  • What are void (empty) elements? Elements that can't have content or a closing tag: <br>, <hr>, <img>, <input>, <meta>, <link> and <source>. The trailing slash in <br /> is optional in HTML.
Q25Not marked yet1–3 yrsAsked oftenLoading & performance

What is the difference between <script>, <script async> and <script defer>?

Say this first~25 sec to say

A plain <script> stops HTML parsing while the file downloads and runs. async downloads the script in parallel and runs it as soon as it arrives, pausing parsing at that moment, so scripts may run in any order. defer also downloads in parallel, but it waits until the HTML is fully parsed and then runs the scripts in the order they appear, just before DOMContentLoaded.

Could you say it?
Full answer2 min read
Blocks parsing while it downloads?When it runsKeeps order?Best for
<script>YesImmediately, where it appearsYesCode that must run before the rest of the page (rare)
<script async>NoAs soon as it's downloadedNoIndependent scripts like analytics and ads
<script defer>NoAfter the HTML is parsed, before DOMContentLoadedYesYour app code that needs the DOM or depends on other scripts
<script type="module">NoDeferred by defaultYesModern ES module code
HTML
<head>
  <!-- Analytics: depends on nothing, so order doesn't matter -->
  <script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXX"></script>

  <!-- App code: needs the DOM, and vendor.js must run before app.js -->
  <script defer src="/js/vendor.js"></script>
  <script defer src="/js/app.js"></script>
</head>

Why the old advice was "CSS in the <head>, JS at the end of the <body>"

  • CSS goes in the <head> so the page appears already styled, with no flash of unstyled content.
  • JS goes at the bottom so it doesn't stop the content from showing, and so the elements it needs already exist when it runs.
  • With defer, you can keep scripts in the <head> and get both benefits: the download starts earlier, but the script still runs after parsing.

Details interviewers like

  • async and defer only apply to external scripts (ones with src). They're ignored on inline classic scripts.
  • If a script has both, async wins.
Q26Not marked yetFresherAsked sometimesSEO & metadata

Which meta tags matter and why?

Say this first~25 sec to say

Meta tags sit in the <head> and describe the page to browsers, search engines and social apps. They aren't shown on the page itself. The ones that matter most are charset (the text encoding), viewport (essential for mobile and responsive design), description (the snippet in search results), Open Graph tags (the preview card when a link is shared on WhatsApp or LinkedIn) and robots (indexing rules).

Could you say it?
Full answer2 min read
HTML
<head>
  <!-- Text encoding: keep it first so every character renders correctly -->
  <meta charset="UTF-8" />

  <!-- Responsive design: match the device width instead of showing a zoomed-out desktop page -->
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />

  <!-- SEO -->
  <title>Frontend Developer Jobs for Freshers</title>
  <meta name="description" content="Verified frontend internships and fresher jobs, updated daily." />
  <meta name="robots" content="index, follow" />
  <link rel="canonical" href="https://example.com/jobs/frontend" />

  <!-- Link previews on WhatsApp, LinkedIn, X and Slack -->
  <meta property="og:title" content="Frontend Developer Jobs for Freshers" />
  <meta property="og:description" content="Verified fresher jobs, updated daily." />
  <meta property="og:image" content="https://example.com/og-image.png" />
  <meta name="twitter:card" content="summary_large_image" />
</head>
  • Without the viewport tag, mobile browsers lay the page out about 980px wide and shrink it to fit the screen. The text becomes tiny, and your media queries don't kick in the way you expect.
  • <title> isn't a meta tag, but it's the most important <head> element for SEO, and it's what the browser tab shows.
  • Google ignores <meta name="keywords">, so don't spend time on it.
  • http-equiv meta tags act like HTTP headers, for example <meta http-equiv="Content-Security-Policy" content="default-src 'self'">.
Q27Not marked yet1–3 yrsAsked sometimesAccessibility

How do you make a web page accessible?

Say this first~25 sec to say

Accessibility (often shortened to a11y) means everyone can use your site, including people who use screen readers, people who use only a keyboard, and people with low vision or colour blindness. The basics are semantic HTML, meaningful alt text, a <label> for every input, full keyboard support with a visible focus outline, enough colour contrast, and ARIA only when native HTML can't do the job.

Could you say it?
Full answer3 min read
HTML
<!-- <html lang="en"> tells screen readers which language to pronounce the page in -->

<!-- Describe what the image shows; use an empty alt for purely decorative images -->
<img src="team.jpg" alt="Five engineers planning a sprint at a whiteboard" />
<img src="divider.svg" alt="" />

<!-- Every input needs a label; clicking the label also focuses the input -->
<label for="email">Email</label>
<input id="email" type="email" autocomplete="email" required />

<!-- Real buttons and links work with the keyboard out of the box -->
<button type="button">Save job</button>

<!-- Icon-only buttons need an accessible name -->
<button type="button" aria-label="Close dialog">✕</button>

<!-- Announce content that changes on its own to screen readers -->
<div aria-live="polite">3 new jobs loaded</div>

Checklist

  • Keyboard: everything clickable must be reachable with Tab and work with Enter or Space. Never remove the focus outline without adding a visible replacement (style :focus-visible instead).
  • Contrast: at least 4.5:1 for normal text and 3:1 for large text (the WCAG AA standard).
  • Don't rely on colour alone: pair the red border on an invalid field with an error message.
  • Keep headings in order: h1, then h2, then h3. Screen-reader users navigate by headings.
  • The first rule of ARIA: don't use ARIA if a native element already does the job. A <div role="button"> still needs tabindex="0" and keyboard handlers, while <button> has them built in.
  • Test it: run Lighthouse and axe DevTools for automated checks, then try the page with only a keyboard and with a screen reader (NVDA on Windows, VoiceOver on Mac and iPhone).

Follow-up: how do you hide something visually but keep it available to screen readers? display: none and visibility: hidden hide content from screen readers as well. Use a "visually hidden" class instead:

CSS
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

03CSS

0/9 got it

Q28Not marked yetFresherAsked very oftenBox model

What is the CSS box model?

Say this first~25 sec to say

Every element is drawn as a rectangular box made of four layers, from the inside out: content → padding → border → margin. By default (box-sizing: content-box), width and height apply to the content only, so padding and border are added on top. With box-sizing: border-box, width includes the padding and border, which makes sizes much more predictable. That's why most projects set it globally.

Could you say it?
Full answer3 min read
┌─────────────── margin ───────────────┐
│  ┌──────────── border ────────────┐  │
│  │  ┌───────── padding ────────┐  │  │
│  │  │         content          │  │  │
│  │  └──────────────────────────┘  │  │
│  └────────────────────────────────┘  │
└──────────────────────────────────────┘
CSS
.card {
  width: 200px;
  padding: 20px;
  border: 5px solid #ccc;
  margin: 10px;
}
/* content-box (the default): visible width = 200 + 20×2 + 5×2 = 250px */

.card-fixed {
  box-sizing: border-box;
  width: 200px;
  padding: 20px;
  border: 5px solid #ccc;
}
/* border-box: visible width = 200px, and the content area shrinks to 150px */

Margin is never part of the element's width. It's empty space outside the border, so the first card takes up 250 + 10×2 = 270px of room.

The common global reset

CSS
*,
*::before,
*::after {
  box-sizing: border-box;
}

Follow-ups

  • Padding vs margin? Padding sits inside the border and shows the element's background. Margin sits outside the border, is always transparent and can be negative. margin: 0 auto centres a block element that has a width.
  • What is margin collapsing? When two block elements are stacked vertically, the bottom margin of the first and the top margin of the second merge into one margin equal to the larger of the two. So 20px and 30px give a 30px gap, not 50px. It doesn't happen with horizontal margins, or between flex and grid items.
  • Reset vs normalize CSS? A reset removes the browser's default styles (margins, heading sizes, list bullets) so you start from zero. Normalize keeps the useful defaults but makes them consistent across browsers and fixes known bugs. Most modern projects use a small "modern reset". Tailwind's Preflight, for example, is built on modern-normalize.
Q29Not marked yetFresherAsked oftenSelectors & cascade

How does CSS specificity work?

Say this first~30 sec to say

When several rules set the same property on the same element, the browser picks a winner in this order: !important first, then specificity, then source order (the later rule wins). Specificity is a three-part score, (IDs, classes, elements), compared from left to right. Classes, attributes and pseudo-classes count in the middle column. Elements and pseudo-elements count in the last one. An inline style="" beats any selector, and !important beats inline styles.

Could you say it?
Full answer2 min read
SelectorScore (IDs, Classes, Elements)
p(0, 0, 1)
p::first-line(0, 0, 2)
.card(0, 1, 0)
a:hover(0, 1, 1)
input[type="email"](0, 1, 1)
.nav .link(0, 2, 0)
#header(1, 0, 0)
#header .nav a(1, 1, 1)
* and combinators like >, +, ~(0, 0, 0)
style="…" (inline)Beats every selector

Compare one column at a time, starting from the left. That means one ID beats any number of classes: (1, 0, 0) wins over (0, 15, 0).

HTML
<p id="intro" class="lead">Welcome</p>
CSS
#intro { color: red; }   /* (1,0,0) — wins */
.lead  { color: blue; }  /* (0,1,0) */
p      { color: green; } /* (0,0,1) */

.btn { color: white; }
.btn { color: black; }   /* same score, so the later rule wins: black */

Gotcha: specificity doesn't apply to inherited values. A rule that targets an element directly always beats a value the element inherits from its parent:

CSS
#sidebar { color: red; } /* very specific, but a <p> inside only *inherits* it… */
p { color: blue; }       /* …while this rule targets <p> directly, so blue wins */

Modern pseudo-classes: :is(), :not() and :has() take the score of their most specific argument. :where() always scores zero, which makes it handy for defaults that should be easy to override.

Good habits: style with classes rather than IDs, keep selectors short, and save !important for last resorts such as utility classes or overriding third-party CSS.

Follow-up

  • id vs class? An id must be unique on the page. A class can be reused on many elements, and one element can have many classes. IDs have much higher specificity, which makes them hard to override, so style with classes. Keep IDs for JavaScript hooks, in-page links (#pricing) and connecting labels to inputs (<label for="email">).
Q30Not marked yetFresherAsked very oftenLayout

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

Say this first~30 sec to say

Block elements start on a new line and stretch to the full width of their parent, and width, height, margins and padding all work on them. Inline elements sit inside a line of text and are only as wide as their content. width and height are ignored, and vertical margins don't push other lines away. Inline-block elements sit inline like text but respect width, height and all margins and padding.

Could you say it?
Full answer2 min read
blockinlineinline-block
Starts on a new lineYesNoNo
Default widthThe full width of its parentFits the contentFits the content
width / heightWorkIgnoredWork
Vertical margin and paddingPush neighbours awayMargin is ignored; padding is painted but doesn't move other linesPush neighbours away
Elements that use it by defaultdiv, p, h1–h6, ul, li, section, formspan, a, strong, em, label, codebutton, input, select
HTML
<span class="tag">React</span>
<span class="tag">CSS</span>
CSS
.tag {
  display: inline-block; /* stays on one line, like text… */
  width: 80px;           /* …but width, height and vertical padding now work */
  padding: 4px 8px;
  margin: 4px;
  text-align: center;
}

display: flex and display: grid turn an element into a layout container for its children. See question 32.

Follow-up: display: none vs visibility: hidden vs opacity: 0

Takes up space?Can be clicked?Read by screen readers?
display: noneNo, it's removed from the layoutNoNo
visibility: hiddenYes, it leaves a gapNoNo
opacity: 0YesYes, it's still clickable!Yes
Q31Not marked yetFresherAsked very oftenLayout

What are the different position values?

Say this first~35 sec to say

static is the default: normal flow, where top and left do nothing. relative keeps the element in the flow but lets you nudge it from its normal spot, and it becomes the reference point for absolutely positioned children. absolute takes the element out of the flow and places it relative to its nearest positioned ancestor. fixed places it relative to the viewport, so it stays put while you scroll. sticky behaves like relative until you scroll past a threshold (such as top: 0), then sticks like fixed, but only within its parent.

Could you say it?
Full answer3 min read
ValueStays in the normal flow?Positioned relative toMoves when you scroll?Typical use
staticYesNothing (offsets are ignored)YesThe default
relativeYes, and its original space is keptIts own normal positionYesSmall nudges; an anchor for absolute children
absoluteNoThe nearest ancestor whose position isn't static (otherwise the page)Yes, along with that ancestorBadges, tooltips, dropdowns
fixedNoThe viewportNoFloating buttons, chat widgets, modals, top bars
stickyYesIts nearest scrolling ancestor (usually the viewport), once the threshold is reachedUntil it sticksSticky headers, table headings, sidebars
CSS
/* A badge in the corner of a card */
.card {
  position: relative; /* the reference box for the badge */
}
.card .badge {
  position: absolute;
  top: 8px;
  right: 8px; /* 8px from the CARD's corner, not the page's */
}

/* A navbar that sticks once it reaches the top */
.navbar {
  position: sticky;
  top: 0;
  z-index: 10;
}

Common gotchas

  • sticky isn't sticking? It needs a threshold such as top or bottom. It also only sticks inside its parent, and if any ancestor has overflow: hidden, auto or scroll, it sticks inside that box instead of the viewport.
  • fixed isn't fixed to the screen? If an ancestor has a transform, filter or perspective, the fixed element is positioned relative to that ancestor instead.
  • z-index isn't working? It only applies to positioned elements (and to flex and grid children). Also, z-index values only compete within the same stacking context. A new stacking context is created by things like position combined with z-index, opacity below 1, transform and position: fixed. A child with z-index: 9999 still can't rise above an element that sits above its parent's stacking context.
Q32Not marked yetFresherAsked very oftenLayout

When would you use Flexbox vs Grid?

Say this first~30 sec to say

Flexbox is one-dimensional. It lays items out in a single row or column and is ideal for distributing space and aligning items: navbars, toolbars, button groups, centring. Grid is two-dimensional. It controls rows and columns at the same time and is ideal for page layouts, dashboards and card galleries. Flexbox works from the content out, while Grid works from the layout in. Most real pages use both: Grid for the overall layout and Flexbox inside components.

Could you say it?
Full answer3 min read

Flexbox: a navbar

CSS
.navbar {
  display: flex;
  justify-content: space-between; /* main axis (horizontal here): spread the items out */
  align-items: center;            /* cross axis: centre them vertically */
  gap: 16px;
}

.navbar .search {
  flex: 1; /* grow to fill all the leftover space */
}

Grid: responsive cards and a page layout

CSS
/* Cards that wrap automatically, with no media queries needed */
.job-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 24px;
}

/* A classic page layout with named areas */
.page {
  display: grid;
  grid-template-columns: 240px 1fr;
  grid-template-rows: auto 1fr auto;
  grid-template-areas:
    "header  header"
    "sidebar main"
    "footer  footer";
  min-height: 100vh;
}
.page > header { grid-area: header; }
.page > aside  { grid-area: sidebar; }
.page > main   { grid-area: main; }
.page > footer { grid-area: footer; }
FlexboxGrid
DimensionsOne: a row or a columnTwo: rows and columns
ApproachContent first: the items' sizes shape the layoutLayout first: define the tracks, then place items
Best forNavbars, toolbars, centring, wrapping tags, items in a linePage layouts, dashboards, galleries, card grids
Key propertiesjustify-content, align-items, flex-wrap, flexgrid-template-columns, grid-template-areas, fr, minmax(), repeat()
Overlapping itemsAwkwardEasy: put items in the same cell

Flexbox cheat sheet

  • On the container: flex-direction (row or column), justify-content (main axis), align-items (cross axis), flex-wrap, gap.
  • On the items: flex-grow, flex-shrink, flex-basis, align-self, order.
  • flex: 1 is short for flex-grow: 1; flex-shrink: 1; flex-basis: 0%. The item grows to share the free space equally with the other flex: 1 items.
Q33Not marked yetFresherAsked very oftenLayout

How do you center a div?

Say this first~20 sec to say

The modern answer is to make the parent a flex or grid container, with either display: flex; justify-content: center; align-items: center; or the shorter display: grid; place-items: center;. To centre a block with a set width horizontally, use margin: 0 auto. For overlays and modals, use position: absolute with top: 50%; left: 50%; transform: translate(-50%, -50%).

Could you say it?
Full answer2 min read
CSS
/* 1. Flexbox: the most common answer */
.parent {
  display: flex;
  justify-content: center; /* horizontally */
  align-items: center;     /* vertically */
  min-height: 100vh;
}

/* 2. Grid: the shortest */
.parent {
  display: grid;
  place-items: center;
  min-height: 100vh;
}

/* 3. Absolute + transform: overlays and modals, works even when the size is unknown */
.parent {
  position: relative;
}
.child {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

/* 4. Horizontal only: a block element with a width */
.child {
  width: 600px;
  margin: 0 auto;
}

/* 5. Inline content such as text, links or inline images */
.parent {
  text-align: center;
}

Why translate(-50%, -50%)? top: 50%; left: 50% moves the child's top-left corner to the centre of the parent. Translating by −50% of the child's own width and height pulls it back, so that its centre lines up with the parent's centre.

Bonus: inside a flex parent, margin: auto on the child centres it in both directions.

Q34Not marked yetFresherAsked oftenUnits & sizing

What is the difference between px, em, rem, % and vw/vh?

Say this first~30 sec to say

px is a fixed size. % is relative to the parent element. em is relative to the element's own font size (for font-size itself, the parent's), so it compounds when elements are nested. rem is relative to the root (<html>) font size, which is 16px by default, so it means the same thing everywhere. That makes it the best choice for font sizes and spacing. vw and vh are 1% of the viewport's width and height.

Could you say it?
Full answer2 min read
UnitRelative toGood for
pxNothing, it's fixedBorders, shadows, small fixed details
%The parent elementFluid widths
emThe element's font size (the parent's, when used for font-size)Padding that should scale with a component's text, e.g. buttons
remThe root <html> font sizeFont sizes, spacing and layout. It respects the user's browser font setting
vw / vh1% of the viewport width / heightFull-screen sections, fluid headings
chThe width of the "0" characterComfortable line lengths: max-width: 65ch

The em compounding trap

HTML
<div class="outer">
  Outer text
  <div class="inner">Inner text</div>
</div>
CSS
html   { font-size: 16px; }
.outer { font-size: 1.25em; } /* 16 × 1.25 = 20px */
.inner { font-size: 1.25em; } /* 20 × 1.25 = 25px — it compounds */

/* With 1.25rem, both would be exactly 20px */

Follow-ups

  • Why prefer rem for font sizes? If a user raises the default font size in their browser settings, text sized in rem grows with it, while text set in px stays the same. (Browser zoom works either way.)
  • Why can 100vh be too tall on phones? Mobile browser toolbars appear and disappear as you scroll, so 100vh can be taller than the visible area. Use 100dvh (dynamic viewport height) instead.
  • What is clamp()? It sets a value that scales but stays within limits. font-size: clamp(1rem, 2.5vw, 2rem) grows with the screen width but never goes below 1rem or above 2rem.
Q35Not marked yetFresherAsked oftenResponsive design

How do you make a website responsive?

Say this first~25 sec to say

A responsive site is one codebase whose layout adapts to any screen size. The ingredients are the viewport meta tag, fluid layouts (Flexbox, Grid, percentages), relative units like rem, flexible images (max-width: 100%) and media queries that change styles at breakpoints. A mobile-first approach writes the base styles for small screens and adds min-width media queries for larger ones.

Could you say it?
Full answer5 min read
HTML
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
CSS
/* Base styles for mobile: one column */
.jobs {
  display: grid;
  grid-template-columns: 1fr;
  gap: 16px;
}

/* Tablets and up */
@media (min-width: 768px) {
  .jobs {
    grid-template-columns: repeat(2, 1fr);
  }
}

/* Desktops and up */
@media (min-width: 1024px) {
  .jobs {
    grid-template-columns: repeat(3, 1fr);
  }
}

/* Images shrink with their container instead of overflowing it */
img {
  max-width: 100%;
  height: auto;
}

Mobile-first vs desktop-first

  • Mobile-first: write base styles for phones, then use @media (min-width: …) to add complexity for bigger screens. It forces you to prioritise content, and in India most users are on phones.
  • Desktop-first: write base styles for desktop, then use @media (max-width: …) to undo things on smaller screens. It usually ends up with more overrides.

Media queries can check more than width

CSS
/* Dark mode with CSS variables: change the values once, and everything that uses them follows */
:root {
  --bg: #ffffff;
  --text: #111111;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #111111;
    --text: #eeeeee;
  }
}

body {
  background: var(--bg);
  color: var(--text);
}

@media (prefers-reduced-motion: reduce) {
  * {
    animation: none;
    transition: none;
  }
}

@media print {
  nav,
  .ads {
    display: none;
  }
}

Responsive images: srcset lets the browser download the right size for the screen, so phones don't download desktop-sized images:

HTML
<img
  src="banner-800.jpg"
  srcset="banner-400.jpg 400w, banner-800.jpg 800w, banner-1600.jpg 1600w"
  sizes="(max-width: 600px) 100vw, 50vw"
  alt="Engineers collaborating in an office"
/>

Follow-ups

  • CSS variables vs Sass variables? Sass variables are replaced with fixed values when the CSS is compiled. CSS custom properties (declared as --brand: #2563eb; and used as var(--brand)) exist at runtime. They inherit through the DOM, can change inside media queries and can be updated from JavaScript, which is what makes theme switching easy.
  • What are container queries? @container (min-width: 400px) { … } styles a component based on the size of its container rather than the whole viewport, so the same card can change its layout depending on whether it sits in a narrow sidebar or a wide main column. The container needs container-type: inline-size.
Q36Not marked yetFresherAsked sometimesSelectors & cascade

What is the difference between pseudo-classes and pseudo-elements?

Say this first~20 sec to say

A pseudo-class (one colon, :) selects an element when it's in a particular state or position, for example :hover, :focus, :checked or :nth-child(2). A pseudo-element (two colons, ::) styles a specific part of an element or adds generated content, for example ::before, ::after, ::first-letter or ::placeholder.

Could you say it?
Full answer2 min read
CSS
/* Pseudo-classes: state or position */
a:hover { text-decoration: underline; }
button:focus-visible { outline: 2px solid #2563eb; }
li:nth-child(odd) { background: #f5f5f5; } /* zebra stripes */
input:invalid { border-color: crimson; }
.card:not(.featured) { opacity: 0.9; }
.card:has(img) { padding: 0; }             /* the "parent selector": cards that contain an image */

/* Pseudo-elements: part of an element, or generated content */
.required::after {
  content: " *"; /* ::before and ::after need `content`, even if it's just "" */
  color: crimson;
}
p::first-letter { font-size: 2em; }
input::placeholder { color: #888; }
::selection { background: #fde68a; }
Pseudo-classPseudo-element
Syntax: (one colon):: (two colons; older ones like :before also accept one)
SelectsThe whole element, when it's in a state or positionA part of the element, or a virtual element
Examples:hover, :focus, :active, :checked, :disabled, :first-child, :nth-child(), :not(), :has()::before, ::after, ::first-line, ::first-letter, ::placeholder, ::marker, ::selection

Follow-up: :nth-child() vs :nth-of-type()

HTML
<div>
  <h2>Title</h2>
  <p>First paragraph</p>
  <p>Second paragraph</p>
</div>
CSS
p:nth-child(2)   { color: red; }  /* "First paragraph" — it's the 2nd child overall */
p:nth-of-type(2) { color: blue; } /* "Second paragraph" — it's the 2nd <p> */

04Browser & Web Fundamentals

0/4 got it

Q37Not marked yetFresherAsked very oftenStorage

What is the difference between localStorage, sessionStorage and cookies?

Say this first~30 sec to say

All three store small pieces of data in the browser. localStorage keeps data with no expiry date, so it survives closing the browser, and it's shared across tabs of the same site. sessionStorage lasts only as long as that one tab stays open. Cookies are tiny (about 4 KB), can have an expiry date, and are sent to the server automatically with every request. That last point is why cookies are used for login sessions.

Could you say it?
Full answer3 min read
localStoragesessionStorageCookies
Size limitAbout 5 MB per originAbout 5 MB per originAbout 4 KB per cookie
ExpiresNever, until code or the user clears itWhen the tab is closedAt the Expires/Max-Age you set, otherwise when the browser session ends
Shared between tabsYes (same origin)No, one tab onlyYes
Sent to the serverNoNoYes, with every request to that domain
Readable by JavaScriptYesYesYes, unless the cookie is HttpOnly
Good forTheme, saved filters, draftsProgress through a multi-step formLogin sessions, preferences the server needs
JavaScript
// Web Storage only stores strings
localStorage.setItem("theme", "dark");
localStorage.getItem("theme"); // "dark"
localStorage.removeItem("theme");

// Store objects as JSON
const filters = { role: "frontend", remote: true };
localStorage.setItem("filters", JSON.stringify(filters));
const savedFilters = JSON.parse(localStorage.getItem("filters"));

sessionStorage.setItem("checkoutStep", "2");

// Cookies from JavaScript (clunky: it's one long string)
document.cookie = "lang=en; max-age=86400; path=/; SameSite=Lax";

A secure session cookie is usually set by the server:

Set-Cookie: sessionId=abc123; HttpOnly; Secure; SameSite=Lax; Max-Age=3600
Cookie flagWhat it does
HttpOnlyJavaScript can't read the cookie, so an XSS attack can't steal it
SecureThe cookie is only sent over HTTPS
SameSite=Strict / Lax / NoneControls whether the cookie is sent on cross-site requests, which protects against CSRF

Security note: don't keep auth tokens or personal data in localStorage. Any script that gets injected into your page through an XSS bug can read it. Session tokens are safer in cookies marked HttpOnly, Secure and SameSite.

Follow-up: localStorage is synchronous, so reading or writing a lot of data in it can slow down the main thread. For larger or structured data, use IndexedDB.

Q38Not marked yet1–3 yrsAsked oftenRendering

What happens when you type a URL and press Enter?

Say this first~35 sec to say

The browser looks up the domain's IP address (DNS), opens a connection to the server (TCP, plus a TLS handshake for HTTPS), sends an HTTP request and gets HTML back. It then parses the HTML into the DOM and the CSS into the CSSOM, and combines them into a render tree. Next it works out the size and position of everything (layout), fills in the pixels (paint) and stacks the layers on screen (composite). JavaScript can pause parsing and change the DOM along the way.

Could you say it?
Full answer3 min read

Step by step

  1. Check the input. Is it a URL or a search term? Is a cached copy available?
  2. DNS lookup. Find the server's IP address by checking the browser cache, then the operating system cache, then your ISP's (or a public) DNS resolver, which asks the root, TLD (.com) and authoritative name servers.
  3. Connect. TCP sets up the connection with a three-way handshake (SYN → SYN-ACK → ACK). For HTTPS, a TLS handshake follows to verify the certificate and agree on encryption. (HTTP/3 uses QUIC over UDP, which combines these steps.)
  4. Send the request. For example, GET /jobs with headers like Cookie, Accept and User-Agent.
  5. Get the response. A status code (200 OK, 301 Moved Permanently, 404 Not Found…), headers and the HTML.
  6. Build the DOM. The browser parses the HTML into the DOM tree and starts downloading the CSS, JS and images it finds along the way.
  7. Build the CSSOM. CSS is parsed into the CSSOM. CSS is render-blocking: nothing gets painted until it's ready.
  8. Run JavaScript. A plain <script> pauses parsing until it has downloaded and run (see question 25).
  9. Build the render tree. It combines the DOM and CSSOM and contains only what's visible. Elements with display: none are left out.
  10. Layout (reflow). Work out the exact size and position of every box.
  11. Paint. Fill in the pixels: text, colours, borders, shadows, images.
  12. Composite. Combine the painted layers in the right order (often on the GPU) and put them on screen.

Steps 6–12 are called the critical rendering path. Making a page appear faster mostly means shortening it: less render-blocking CSS and JS, smaller files, and the CSS for the first screen inlined.

Follow-up: reflow vs repaint

Reflow (layout)Repaint
Triggered byChanging geometry: width, height, margin, font-size, adding or removing elements, reading offsetHeight straight after a changeChanging only how things look: color, background, visibility, box-shadow
CostExpensive, because it can affect large parts of the pageCheaper

Changes to transform and opacity can often skip both steps and only re-composite, which is why smooth animations use them.

JavaScript
// ❌ Layout thrashing: every iteration reads layout straight after the previous write
items.forEach((item) => {
  item.style.width = container.offsetWidth + "px";
});

// ✅ Read once, then write
const width = container.offsetWidth;
items.forEach((item) => {
  item.style.width = width + "px";
});

Also asked: DOMContentLoaded vs load? DOMContentLoaded fires once the HTML is parsed and deferred scripts have run. load fires later, once everything else (images, stylesheets, iframes) has finished loading too.

Q39Not marked yet1–3 yrsAsked oftenSecurity

What is CORS?

Say this first~25 sec to say

Browsers follow the same-origin policy: JavaScript running on one origin can't read responses from a different origin. An origin is the protocol + domain + port. CORS (Cross-Origin Resource Sharing) is how a server opts in to sharing its responses with other origins, by sending headers such as Access-Control-Allow-Origin. The browser enforces CORS, but only the server can fix a CORS error.

Could you say it?
Full answer3 min read

Same origin or not? Compared with https://example.com:

URLSame origin?Why
https://example.com/jobsYesOnly the path is different
http://example.comNoDifferent protocol
https://api.example.comNoDifferent subdomain
https://example.com:8080NoDifferent port

How a CORS request works

  1. Your frontend on https://app.example.com calls fetch("https://api.example.com/jobs").
  2. The browser adds the header Origin: https://app.example.com to the request.
  3. The server replies with Access-Control-Allow-Origin: https://app.example.com (or * for public data).
  4. If the header matches, your code gets the response. If it doesn't, the browser blocks the response and shows a CORS error in the console.

Preflight requests. Some requests count as "non-simple": methods like PUT, PATCH or DELETE, a JSON Content-Type, or custom headers like Authorization. For these, the browser first sends an OPTIONS request asking for permission. The server has to answer with Access-Control-Allow-Methods and Access-Control-Allow-Headers before the real request is sent.

JavaScript
// Backend (Node/Express): allow one specific frontend
const cors = require("cors");

app.use(
  cors({
    origin: "https://app.example.com",
    credentials: true, // allow cookies
  })
);
JavaScript
// Frontend: send cookies with a cross-origin request
fetch("https://api.example.com/profile", {
  credentials: "include",
});

When cookies are involved, the server can't use *. It has to name the exact origin and send Access-Control-Allow-Credentials: true.

Common misconceptions

  • "I can fix CORS in my frontend code." You can't. The server has to allow your origin. During local development you can route requests through a dev-server proxy (Vite, webpack or Next.js rewrites).
  • "mode: "no-cors" fixes it." It doesn't. You get an "opaque" response that your code can't read.
  • "CORS protects the server." It protects users. It stops a malicious site from using your logged-in browser to read your data from another site. A simple cross-origin request still reaches the server; the browser only hides the response from JavaScript. Tools like Postman and curl ignore CORS completely.
Q40Not marked yet1–3 yrsAsked oftenPerformance

How would you improve a website's performance?

Say this first~25 sec to say

Start by measuring with Lighthouse and Core Web Vitals to find the real bottleneck. Then send less (compressed, correctly sized images; minified, code-split JS; lazy loading), send it faster (a CDN, caching, preloading key resources), show something sooner (defer non-critical JS, inline critical CSS, render on the server) and do less work in the browser (debounce events, avoid layout thrashing, virtualise long lists).

Could you say it?
Full answer3 min read

1. Send less

  • Compress images and use modern formats (WebP, AVIF). Serve the right size with srcset.
  • Lazy-load images and iframes below the fold with loading="lazy".
  • Minify JS and CSS, and turn on Brotli or gzip compression on the server.
  • Split code by route or feature and load it on demand (import(), React.lazy).
  • Remove unused code (tree shaking) and swap heavy libraries for lighter ones.

2. Send it faster

  • Serve static files from a CDN, close to the user.
  • Cache aggressively, e.g. Cache-Control: max-age=31536000, immutable for files with a content hash in their name.
  • preconnect to important third-party domains, and preload critical fonts or the hero image.

3. Show something sooner

  • defer non-critical scripts, and inline the CSS needed for the first screen.
  • Use font-display: swap so text appears while web fonts are still loading.
  • Use server-side rendering or static generation (e.g. with Next.js) for content pages, for a faster first paint and better SEO.

4. Do less work in the browser

  • Debounce or throttle frequent events (question 20).
  • Animate transform and opacity instead of top, left or width.
  • Virtualise long lists so only the rows on screen are rendered.
  • Move heavy calculations to a Web Worker.
  • Remove event listeners and clear intervals you no longer need, to avoid memory leaks.
HTML
<!-- Lazy-load offscreen images; width and height reserve space and prevent layout shift -->
<img src="office.webp" alt="Our office" width="800" height="450" loading="lazy" />

<!-- Open connections and start critical downloads early -->
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link rel="preload" href="/images/hero.webp" as="image" />
JavaScript
// Load a heavy feature only when the user actually needs it
button.addEventListener("click", async () => {
  const { openEditor } = await import("./editor.js");
  openEditor();
});

Core Web Vitals: Google's user-experience metrics

MetricWhat it measuresGood score
LCP: Largest Contentful PaintLoading: when the main content appears2.5 s or less
INP: Interaction to Next PaintResponsiveness: how quickly the page reacts to taps, clicks and key presses200 ms or less
CLS: Cumulative Layout ShiftVisual stability: how much content jumps around0.1 or less

INP replaced First Input Delay (FID) as a Core Web Vital in March 2024.

Tools: Lighthouse, the Performance and Network panels in Chrome DevTools, PageSpeed Insights, WebPageTest, and a bundle analyser for checking JavaScript size.

How to structure your answer: measure, find the biggest bottleneck, fix it, then measure again. Saying that you measure first sets you apart from candidates who just list techniques.

Last step

Write these from memory

Before an interview for a 1–3 year role, make sure you can write each of these on a blank page without help:

  • Q20
  • Q18
  • Q14
  • Q8
  • Q11
  • Q13
  • Q6
  • Q22
  • Q33
  • Q17
  • Q6

0 of 11 done

Written and checked by CareersAt.Tech. Every code example on this page was run before publishing. Updated 22 Oct 2026.