JavaScript Essential Concepts: A Complete Knowledge Guide
Explore essential JavaScript concepts with clear explanations, practical examples, common mistakes, and important limitations.
1. What is JavaScript, and why is it important?
JavaScript is a high-level programming language primarily used to create interactive web applications. It runs in browsers and in server-side environments such as Node.js, and supports functional, object-oriented, and event-driven programming.
Its flexibility, extensive ecosystem, and ability to power both client and server applications make it one of the most widely used languages in modern software development.
- Runs in browsers and server-side runtimes
- Powers interactive and full-stack applications
- Supports several programming styles
2. What are the different data types in JavaScript?
JavaScript has seven primitive types: string, number, boolean, undefined, null, symbol, and bigint. Primitive values are immutable. Objects represent more complex structures; arrays and functions are specialised kinds of objects.
JavaScript is dynamically typed, so the same variable can hold values of different types during its lifetime.
- Seven primitive types
- Arrays and functions are objects
- Variables are dynamically typed
let value = 10; value = 'Hello'; typeof value; // 'string'
3. What is the difference between var, let, and const?
`var` is function-scoped and can be redeclared and reassigned. `let` and `const` are block-scoped; `let` permits reassignment, while `const` does not permit the binding to be reassigned.
A `const` object can still be mutated because the binding stores a reference. Modern JavaScript generally favours `const`, uses `let` when reassignment is necessary, and avoids `var`.
let score = 10;
score = 20;
const user = { name: 'Alex' };
user.name = 'Sam'; // Allowed4. What is hoisting?
Hoisting describes how JavaScript creates declarations before executing a scope. Function declarations are initialized during setup, while `var` bindings are initialized with `undefined`.
Bindings declared with `let` and `const` also exist before their declaration is evaluated, but remain inaccessible in the temporal dead zone. A function expression follows the initialization rules of its variable. Declarations are not physically moved in the source code.
sayHello(); // Works
function sayHello() {
console.log('Hello');
}
console.log(total); // undefined
var total = 10;
console.log(price); // ReferenceError
let price = 20;5. What is a closure?
A closure is a function together with access to its outer lexical scope, even after the outer function has finished executing. Closures are useful for maintaining private state, creating function factories, and configuring callbacks.
They retain referenced values for as long as the closure remains reachable, so long-lived closures can also retain more memory than intended.
function createCounter() {
let count = 0;
return () => ++count;
}
const counter = createCounter();
counter(); // 1
counter(); // 26. What is the difference between == and ===?
Loose equality (`==`) performs type coercion before comparison. Strict equality (`===`) compares values without coercing their types, making its behaviour easier to predict.
Strict equality is the normal default in professional code. JavaScript also provides `Object.is()`, which differs from `===` for `NaN` and signed zero.
5 == '5'; // true 5 === '5'; // false Object.is(NaN, NaN); // true
7. What is an arrow function?
An arrow function is concise syntax for a function expression. Unlike regular functions, arrow functions do not create their own `this`, `arguments`, or `prototype`; they inherit `this` from the surrounding lexical scope.
They work well for callbacks, but cannot be used as constructors and may be inappropriate for object methods that need a dynamic `this` value.
const add = (first, second) => first + second;
8. What does the this keyword mean?
In a regular function, the value of `this` generally depends on how the function is called. A method call usually sets `this` to the object before the dot, while a standalone function receives `undefined` in strict mode.
Arrow functions inherit `this` lexically. Regular functions can be given an explicit value with `call`, `apply`, or `bind`.
const user = {
name: 'Alex',
introduce() {
return this.name;
},
};
user.introduce(); // 'Alex'9. What is event bubbling?
Event bubbling is the process in which an event starts at its target and propagates upward through its ancestors. A click on a button can therefore trigger handlers on the button, its containing elements, and the document.
Bubbling enables event delegation. `event.stopPropagation()` can stop it, but should only be used when the surrounding handlers genuinely must not receive the event.
10. What is event delegation?
Event delegation attaches one event listener to a parent rather than separate listeners to every child. Because events bubble, the parent can inspect `event.target` or use `closest()` to determine which child initiated the event.
This reduces listener overhead and naturally handles matching elements added to the DOM later.
document.querySelector('.list').addEventListener('click', (event) => {
const button = event.target.closest('.delete-button');
if (button) button.closest('li').remove();
});11. What is a Promise?
A Promise represents the eventual completion or failure of an asynchronous operation. It begins as pending and settles once as either fulfilled with a value or rejected with a reason.
Promises support composition through `then`, `catch`, `finally`, and combinators such as `Promise.all`, making asynchronous workflows easier to manage than deeply nested callbacks.
async function loadUser() {
try {
const response = await fetch('/api/user');
return await response.json();
} catch (error) {
console.error(error);
}
}12. What are async and await?
`async` and `await` provide readable syntax for Promise-based code. An `async` function always returns a Promise, and `await` pauses only that async function until the supplied value settles; it does not block the JavaScript thread.
Use `try` and `catch` when an awaited rejection can be handled locally, or allow it to propagate to the caller.
async function loadUsers() {
const response = await fetch('/api/users');
if (!response.ok) throw new Error('Request failed');
return response.json();
}13. What is the difference between null and undefined?
`undefined` usually indicates that a value is missing or has not been assigned. `null` is normally assigned deliberately to represent the intentional absence of a value.
Both are primitive values. A well-known historical quirk is that `typeof null` returns `"object"`, so direct equality or nullish checks are usually clearer.
let result; // undefined let selectedUser = null; // intentionally empty
14. What is the DOM?
The Document Object Model is a programming interface that represents an HTML document as a tree of objects. JavaScript uses DOM APIs to read or change content, attributes, styles, and structure.
The DOM also exposes events that let applications respond to clicks, keyboard input, form submissions, and other user interactions.
const heading = document.querySelector('h1');
heading.textContent = 'Welcome';15. What is scope in JavaScript?
Scope determines where a binding can be accessed. JavaScript has global, module, function, and block scopes; `let` and `const` are block-scoped, while `var` is function-scoped.
JavaScript uses lexical scope, meaning accessibility is determined by where variables and functions are written in the source code.
if (true) {
const message = 'Hello';
}
console.log(message); // ReferenceError16. What is a callback function?
A callback is a function passed to another function so it can be invoked later or as part of an operation. Callbacks are common in event handlers, timers, array methods, and older asynchronous APIs.
Not every callback is asynchronous: the callback supplied to `map()`, for example, normally runs synchronously.
function greet(name, callback) {
callback('Hello, ' + name);
}
greet('Alex', console.log);17. What is callback hell?
Callback hell describes deeply nested callbacks whose control flow and error handling are difficult to understand, test, and maintain. It commonly appears in multi-step asynchronous workflows.
Named functions and smaller modules help, while Promises and `async`/`await` usually provide a flatter structure for modern asynchronous code.
- Extract named functions
- Split workflows into focused modules
- Use Promises or async/await where appropriate
18. What is JSON, and how is it used?
JSON, or JavaScript Object Notation, is a text format for exchanging structured data. It resembles JavaScript object syntax but is language-independent and follows stricter rules.
`JSON.parse()` converts JSON text into a JavaScript value, while `JSON.stringify()` serialises a compatible value as JSON text. JSON is widely used in APIs, configuration, and storage.
const user = JSON.parse('{"name":"Alex"}');
const text = JSON.stringify(user);19. What is the difference between synchronous and asynchronous JavaScript?
Synchronous code runs one operation at a time on the call stack, so a long task can block the main thread. Asynchronous runtime APIs can start work such as a timer or network request and deliver its result later without synchronously waiting.
The event loop coordinates when callbacks and Promise reactions run. JavaScript does not automatically make CPU-intensive code non-blocking simply because it is placed inside an async function.
20. What is a higher-order function?
A higher-order function accepts another function, returns a function, or does both. Common examples include `map`, `filter`, and `reduce`.
Treating behaviour as a value enables reusable abstractions, expressive data transformations, and functional composition.
const prices = [10, 20, 30]; const discounted = prices.map((price) => price * 0.9);
21. What is currying?
Currying transforms a function that accepts multiple arguments into a sequence of functions that each accept one argument. It can create specialised functions and support function composition.
Currying is related to, but different from, partial application: partial application fixes some arguments, while currying changes the function into a chain of single-argument calls.
const multiply = (first) => (second) => first * second; const double = multiply(2); double(5); // 10
22. What is debouncing?
Debouncing delays a function until a specified period has passed without another call. Each call cancels the previous timer and creates a new one, so the callback runs only after activity stops.
It is useful for search input, autocomplete, form validation, resize handling, draft saving, and other expensive work triggered by rapid events. Unlike throttling, debouncing normally waits for activity to stop.
function debounce(callback, delay) {
let timerId;
return function (...args) {
clearTimeout(timerId);
timerId = setTimeout(() => {
callback.apply(this, args);
}, delay);
};
}23. What is throttling?
Throttling limits a function to no more than one execution in a specified interval. It is useful for continuous, high-frequency events such as scrolling, pointer movement, or progress tracking.
Unlike debouncing, throttling permits execution at controlled intervals while activity continues; debouncing normally waits until the activity stops.
24. What is the difference between map() and forEach()?
Both methods iterate over an array, but `map()` returns a new array containing transformed values while `forEach()` returns `undefined`.
Use `map()` when the purpose is to produce a new array. Use `forEach()` for side effects such as logging or updating something outside the iteration.
const numbers = [1, 2, 3]; const doubled = numbers.map((number) => number * 2); numbers.forEach((number) => console.log(number));
25. What is the difference between a library and a framework?
Libraries and frameworks both provide reusable tools, but differ mainly in control and scope. With a library, application code generally chooses when to call it; a framework usually supplies more structure and calls application code at defined extension points.
React is commonly described as a UI library, while Angular is a comprehensive frontend framework. The boundary is not absolute, so the right choice depends on project requirements, team experience, ecosystem, and desired structure.
1. JavaScript Fundamentals
What is a JavaScript engine?
A JavaScript engine reads, compiles, and executes JavaScript. V8 powers Chrome and Node.js, SpiderMonkey powers Firefox, and JavaScriptCore powers Safari. Modern Microsoft Edge also uses V8; Chakra powered the older EdgeHTML-based browser.
Modern engines combine interpretation with just-in-time compilation, optimising frequently executed code while the program runs.
- V8: Chrome, Edge, and Node.js
- SpiderMonkey: Firefox
- JavaScriptCore: Safari
What is the difference between client-side and server-side JavaScript?
Client-side JavaScript runs in the browser and handles interface updates, validation, animation, and user interaction. Server-side JavaScript runs in an environment such as Node.js and can process requests, access databases, authenticate users, and produce responses.
The language is the same, but each environment provides different APIs. Browsers provide the DOM; Node.js provides server and file-system APIs.
What happens if a variable is assigned without a declaration?
In non-strict script code, assigning to an undeclared identifier may create a property on the global object. This is not equivalent to a proper variable declaration and can cause accidental shared state.
In strict mode and JavaScript modules, the assignment throws a ReferenceError. Always declare bindings explicitly with `const` or `let`.
'use strict'; count = 10; // ReferenceError
2. Variables and Data Types
What is a variable?
A variable is a named binding that refers to a JavaScript value. That value may be a primitive, object, array, function, or any other value.
The binding and the value are separate ideas: `const` prevents reassignment of the binding, but does not automatically make an object immutable.
const name = 'Alex'; let score = 10;
What are primitive data types?
Primitive data types represent simple, immutable values. JavaScript has seven primitive types: string, number, boolean, undefined, null, symbol, and bigint.
A primitive value itself cannot be changed after creation. Variables can be assigned new primitive values, and primitives are copied and compared by value.
let word = 'cat'; word[0] = 'b'; console.log(word); // 'cat' let first = 10; let second = first; second = 20; console.log(first); // 10
What is the difference between primitive values and objects?
Primitive values are immutable and compared by value. Objects are mutable by default and are normally compared by identity, meaning two separate objects with identical properties are not strictly equal.
String methods illustrate immutability: `toUpperCase()` returns a new string rather than changing the original value.
10 === 10; // true
{} === {}; // false
const user = { name: 'Alex' };
user.name = 'Sam'; // Object mutatedWhat are arrays, functions, and objects?
An array is an ordered collection of values, a function is reusable behaviour that may return a value, and an object groups related information using key-value properties and methods.
Arrays and functions are specialised objects in JavaScript, but their distinct APIs and roles make these practical definitions useful in technical discussions.
const animals = ['dog', 'cat'];
function add(a, b) {
return a + b;
}
const person = {
name: 'Alex',
introduce() {
return 'Hello, I am ' + this.name;
},
};What does the typeof operator do?
`typeof` returns a string describing the broad type of a value. It reports arrays and `null` as `"object"`, while functions receive the special result `"function"`.
Use `Array.isArray()` for arrays and a direct null check for `null`; `typeof` alone is not a complete type inspection system.
typeof 42; // 'number'
typeof []; // 'object'
typeof null; // 'object'
typeof function () {}; // 'function'
Array.isArray([]); // trueWhat is type coercion?
Type coercion is conversion from one type to another. JavaScript performs implicit coercion in some operations, while explicit conversion uses functions such as `Number`, `String`, and `Boolean`.
Explicit conversion usually communicates intent more clearly. Be especially careful that `+` may concatenate strings while arithmetic operators such as `-` attempt numeric conversion.
'5' + 2; // '52'
'5' - 2; // 3
Number('5'); // 5
Boolean(0); // false3. Operators and Conditions
What are unary, binary, and ternary operators?
A unary operator works with one operand, such as `typeof value` or `!isActive`. A binary operator works with two operands, such as `a + b`. The conditional operator uses three expressions: a condition, a result when truthy, and a result when falsy.
Use a ternary for a concise conditional value, not for deeply nested control flow.
const status = age >= 18 ? 'Adult' : 'Minor';
What is operator precedence?
Operator precedence determines which operations are evaluated first. Multiplication, for example, has higher precedence than addition. Parentheses override the normal order and often make intent clearer during review.
Do not rely on readers remembering every precedence rule when a pair of parentheses can remove ambiguity.
2 + 3 * 4; // 14 (2 + 3) * 4; // 20
What is short-circuit evaluation?
Logical operators stop evaluating once their result is known. `&&` stops at the first falsy operand, while `||` stops at the first truthy operand. Both return an operand rather than necessarily returning a boolean.
Nullish coalescing (`??`) falls back only for `null` or `undefined`, preserving valid falsy values such as `0`, `false`, and an empty string.
isLoggedIn && showDashboard(); const name = providedName || 'Guest'; const count = suppliedCount ?? 0;
What is the difference between spread and rest syntax?
Both use `...`, but spread expands an iterable or object into another context, while rest collects remaining values into one array or object. The surrounding syntax determines which behaviour applies.
A useful memory rule is: spread expands; rest collects.
const combined = [...first, 3, 4];
function total(...numbers) {
return numbers.reduce((sum, number) => sum + number, 0);
}4. Arrays
How do you add and remove array elements?
`push()` and `pop()` operate at the end of an array; `unshift()` and `shift()` operate at the beginning. All four mutate the original array. `push()` and `unshift()` return the new length, while `pop()` and `shift()` return the removed value.
Operations at the beginning generally require reindexing, so repeated `shift()` calls can be costly for large arrays.
- push: add to end
- pop: remove from end
- unshift: add to beginning
- shift: remove from beginning
What is the difference between find() and filter()?
`find()` stops at the first match and returns that element or `undefined`. `filter()` checks the collection and returns a new array containing every match.
Use `find()` when one result is enough and `filter()` when the result is naturally a collection.
const numbers = [2, 4, 6, 8]; numbers.find((number) => number > 4); // 6 numbers.filter((number) => number > 4); // [6, 8]
What is the difference between slice() and splice()?
`slice(start, end)` returns a shallow copy of a range without changing the original; its end index is excluded. `splice(start, deleteCount, ...items)` removes, replaces, or inserts elements and mutates the original array.
`splice()` returns the removed elements, not the updated array.
const values = ['a', 'b', 'c']; values.slice(1, 3); // ['b', 'c'] values.splice(1, 1, 'x'); // values is ['a', 'x', 'c']
How do sorting and reversing affect arrays?
`sort()` and `reverse()` mutate the original array. Default sorting compares string representations, so numeric sorting needs a comparison function. Modern `toSorted()` and `toReversed()` return new arrays instead.
Prefer the non-mutating methods when preserving the source array matters, such as application state updates.
const numbers = [10, 2, 30]; numbers.toSorted((first, second) => first - second); // [2, 10, 30]
What are array destructuring and array-like objects?
Array destructuring assigns iterable values to bindings and can skip items, provide defaults, or swap variables. An array-like object instead has indexed properties and a `length`, but may not provide array methods.
Use `Array.from()` to convert array-like values. Spread also works when the value implements the iterable protocol.
const [primary, secondary] = ['red', 'blue']; [first, second] = [second, first]; const values = Array.from(arrayLike);
5. Loops
What is a loop?
A loop repeatedly executes a block of code while a condition remains true or until a collection has been processed. JavaScript provides for, while, do...while, for...of, and for...in loops, alongside array iteration methods.
Every condition-controlled loop needs a path that eventually makes its condition false to avoid an accidental infinite loop.
for (let index = 0; index < 3; index += 1) {
console.log(index);
}Which loop should you choose?
Use a classic `for` loop when you need index control or know the iteration pattern. Use `while` when repetition depends on a condition, and `do...while` when the body must run at least once.
Use `for...of` for iterable values and array methods for concise transformations. Every condition-controlled loop must have a path that eventually stops it.
What is the difference between break and continue?
`break` exits the nearest loop or switch entirely. `continue` skips the remainder of the current loop iteration and proceeds with the next one.
Both provide control that callback-based iteration methods such as `forEach()` do not support.
for (const number of numbers) {
if (number < 0) continue;
if (number === 100) break;
console.log(number);
}What is the difference between for...of and for...in?
`for...of` iterates values from an iterable such as an array, string, Set, or Map. `for...in` iterates enumerable property keys, including inherited enumerable properties.
Use `for...of` for array values. For plain objects, `Object.keys()`, `Object.values()`, or `Object.entries()` usually make ownership and intent clearer than `for...in`.
How does forEach() compare with for...of?
`forEach()` is concise for synchronous side effects on every array item, but it cannot be stopped with `break` or `continue`. It also does not await an async callback as a sequence.
`for...of` supports loop control and straightforward `await`, making it the clearer choice for sequential asynchronous work.
for (const item of items) {
if (shouldStop(item)) break;
await processItem(item);
}6. Functions
What function forms are available?
Common forms include function declarations, function expressions, arrow functions, methods, async functions, generator functions, and constructor functions. These are related function values with different syntax or behaviour, not wholly separate data types.
Choose based on semantics: declarations for hoisted named functions, arrows for lexical `this`, methods for object behaviour, and async or generator forms for their control-flow capabilities.
What is the difference between named and anonymous functions?
A named function has an explicit identifier, which can improve stack traces and support direct recursion. An anonymous function has no explicit name in its syntax and is often assigned to a variable or used as a short callback.
JavaScript may infer a useful name for an anonymous function from the variable or property receiving it, but that does not make it a named function expression in the source.
function add(a, b) {
return a + b;
}
const subtract = function (a, b) {
return a - b;
};What is the purpose of anonymous functions?
An anonymous function has no explicit name in its syntax. It is useful for short behaviour needed only once, especially callbacks written beside the operation that uses them.
Use a named function when logic is reused, lengthy, recursive, needs a clearer stack trace, or must later be passed to removeEventListener using the same function reference.
const numbers = [1, 2, 3, 4];
const doubled = numbers.map((number) => number * 2);
button.addEventListener('click', () => {
console.log('Button clicked');
});What is a function expression?
A function expression creates a function as part of an expression, normally assigning the result to a variable. The function is a value and can be passed, returned, or stored like other values.
Unlike a function declaration, it cannot be called through a let or const binding before that binding has been initialized.
const add = function (a, b) {
return a + b;
};
add(2, 3); // 5What is the difference between parameters, arguments, and defaults?
Parameters are the names in a function definition; arguments are the values supplied at a call site. Default parameters apply when an argument is omitted or explicitly `undefined`, but not when it is `null`.
Defaults belong in the signature because they make the function contract visible.
function greet(name = 'Guest') {
return 'Hello, ' + name;
}
greet(); // 'Hello, Guest'
greet(undefined); // 'Hello, Guest'
greet(null); // 'Hello, null'What are first-class functions?
JavaScript treats functions as first-class values. They can be assigned to variables, stored in objects and arrays, passed as arguments, and returned from other functions.
This capability is the foundation of callbacks, closures, higher-order functions, middleware, and functional composition.
Why use callback functions?
Callbacks allow behaviour to be supplied without rewriting the surrounding process. The receiving function controls when to run the callback, while callers can provide different operations.
This supports reusable code, separation of responsibilities, event-driven programming, and asynchronous result handling. Deeply nesting asynchronous callbacks can make control flow difficult, so modern code often uses Promises instead.
function calculate(a, b, operation) {
return operation(a, b);
}
calculate(10, 5, (a, b) => a + b);
calculate(10, 5, (a, b) => a * b);What is a pure function?
A pure function produces the same output for the same inputs and does not alter external state. It does not mutate its arguments or perform observable work such as changing the DOM, writing storage, logging, or making a request.
Side effects are necessary in applications, but keeping them outside pure calculation functions makes code easier to test and reason about.
function add(a, b) {
return a + b;
}
add(2, 3); // Always 5What is function composition?
Function composition combines small functions so the output of one function becomes the input of another. Each function can focus on one transformation, while the composed function expresses the complete processing sequence.
Composition improves reuse and testing when the steps have clear input and output contracts.
const double = (number) => number * 2; const addOne = (number) => number + 1; const calculate = (number) => addOne(double(number)); calculate(5); // 11
What is functional programming?
Functional programming builds programs from small, reusable functions and favours pure logic, immutable data, and composition. Instead of changing existing values directly, transformations create new values.
JavaScript is multi-paradigm rather than purely functional, but functional techniques are common in React, Redux, and array-processing code.
const numbers = [1, 2, 3]; const doubled = numbers.map((number) => number * 2); // numbers remains [1, 2, 3]
What do call(), apply(), and bind() do?
These methods control `this` for regular functions. `call()` invokes immediately with separate arguments, `apply()` invokes immediately with arguments in an array-like value, and `bind()` returns a new function to invoke later.
Arrow functions ignore attempts to rebind `this` because their `this` is lexical.
- call: invoke now with separate arguments
- apply: invoke now with an argument array
- bind: create a bound function for later
7. Strings
What are template literals?
Template literals use backticks, support multiline text, and interpolate expressions with `${...}`. They are still strings unless used with a tag function.
Single and double quotes create ordinary strings; choosing between them is normally a style convention.
const message = `Hello, ${name}`;
const lines = `First line
Second line`;What does string immutability mean?
A string value cannot have individual characters changed in place. Methods such as `toUpperCase`, `slice`, `replace`, `split`, and `trim` return new values rather than modifying the original string.
To change text, create and assign a new string.
let word = 'cat';
word[0] = 'b';
console.log(word); // 'cat'
word = `b${word.slice(1)}`; // 'bat'8. The DOM
What is the difference between HTML and the DOM?
HTML is source markup describing a document. The DOM is the browser's in-memory object representation created from that markup.
Scripts and browser corrections can change the DOM after parsing, so the current DOM may differ from the original HTML source.
How do DOM selector methods differ?
`getElementById()` returns one element or `null`. `getElementsByClassName()` and `getElementsByTagName()` return live HTMLCollections that update as the DOM changes. `querySelector()` returns the first CSS-selector match, while `querySelectorAll()` returns a static NodeList.
Modern code often favours the query selector APIs for their expressive CSS selector support.
What is the difference between innerHTML and textContent?
`innerHTML` parses and writes HTML markup, whereas `textContent` inserts plain text. Use `textContent` for ordinary text and untrusted values.
Never pass unsanitised user content to `innerHTML`; doing so can create cross-site scripting vulnerabilities.
element.textContent = userSuppliedValue; element.innerHTML = '<strong>Trusted markup</strong>';
How do you create, clone, and remove DOM nodes?
`createElement()` creates an element, while `createTextNode()` creates text. `cloneNode(true)` copies a node and its descendants, but listeners registered with `addEventListener()` are not copied.
Use `element.remove()` for modern removal, or `parent.removeChild(child)` when the parent-driven operation is useful.
9. Error Handling
What do try, catch, finally, and throw do?
`try` contains work that may fail, `catch` handles a thrown exception, and `finally` runs afterwards whether the operation succeeded or failed. `throw` creates or propagates an exception.
Throw an `Error` instance or subclass rather than a string so callers receive a message, stack trace, and useful type.
try {
await loadData();
} catch (error) {
console.error(error);
} finally {
hideLoadingIndicator();
}What is error propagation?
An unhandled thrown error moves up the call stack until a caller catches it. Promise rejections similarly travel through a Promise chain until handled by `.catch()` or by `try...catch` around an awaited operation.
Catch an error where you can recover, translate it, add useful context, or present an appropriate boundary response; otherwise let it propagate.
What are common error types and good handling practices?
Built-in classes include Error, SyntaxError, ReferenceError, TypeError, RangeError, URIError, and AggregateError. A logical error is different: execution succeeds but produces the wrong result.
Validate external input, handle rejected Promises, preserve original causes, avoid empty catches, keep sensitive data out of logs, and show users clear non-technical messages.
10. Objects, Sets, and Maps
What are classes and objects?
A class is a template for creating related objects with shared behaviour. An object created from a class is called an instance. JavaScript classes provide clearer syntax over the language’s prototype-based inheritance system.
Instance fields belong to each object, methods are normally shared through the prototype, and static fields or methods belong to the class itself.
class Car {
constructor(model, year) {
this.model = model;
this.year = year;
}
describe() {
return this.model + ' was made in ' + this.year;
}
}
const car = new Car('BMW X5', 2025);How do object access and iteration work?
Dot notation is concise for known identifier-like keys, while bracket notation supports dynamic keys and names such as `"first-name"`. Use `Object.keys`, `Object.values`, or `Object.entries` to iterate own enumerable properties.
`Object.hasOwn(object, key)` checks only owned properties; the `in` operator also checks the prototype chain.
user.name;
user[propertyName];
for (const [key, value] of Object.entries(user)) {
console.log(key, value);
}What is the difference between a shallow and deep copy?
Spread and `Object.assign()` create shallow copies: the outer object is new, but nested object references are shared. `structuredClone()` creates independent copies of many supported nested values and handles circular references.
JSON serialisation is not a universal cloning method because it loses or changes values such as functions, undefined, BigInt, symbols, Dates, Maps, Sets, and special numbers.
const shallow = { ...original };
const deep = structuredClone(original);What is a Set?
A Set stores unique values and provides `add`, `has`, `delete`, and `size`. It is iterable and preserves insertion order.
A common use is removing duplicate primitive values from an array, although object uniqueness still depends on reference identity.
const unique = [...new Set([1, 1, 2, 3])]; // [1, 2, 3]
What is the difference between a Map and an object?
A Map accepts keys of any type, is directly iterable, preserves insertion order, and provides `get`, `set`, `has`, `delete`, and `size`. It communicates frequently changing key-value collection semantics clearly.
Objects use string or symbol keys, integrate naturally with object and JSON syntax, and are the conventional choice for structured records. Modern objects have defined property-ordering rules, so their order is not simply unpredictable.
Asynchronous JavaScript
Explain the JavaScript event loop.
JavaScript executes one piece of code at a time on the call stack. Runtime APIs handle operations such as timers and network requests outside that stack, then queue their callbacks when results are ready. The event loop schedules queued work once the stack is empty.
Promise reactions use the microtask queue, which is drained before the next normal task such as a timer callback. This is coordination rather than simultaneous JavaScript execution on the main thread.
console.log('Start');
setTimeout(() => console.log('Timer'), 0);
Promise.resolve().then(() => console.log('Promise'));
console.log('End');
// Start, End, Promise, TimerHow do you implement and use a Promise?
Create a Promise when adapting asynchronous work that does not already provide one. Its executor receives resolve and reject functions, and consumers use then and catch or async and await.
Do not manually wrap an API such as fetch that already returns a Promise. Return or await the existing Promise directly.
function wait(delay) {
return new Promise((resolve) => {
setTimeout(resolve, delay);
});
}
async function run() {
try {
await wait(1000);
console.log('One second completed');
} catch (error) {
console.error(error);
}
}How do you handle errors with async and await?
Use try and catch around awaited operations when the function can handle the failure meaningfully. An awaited rejection or explicit throw transfers control to catch; otherwise the rejected Promise can propagate to the caller.
Fetch rejects for network failures but not for HTTP error statuses, so check response.ok yourself. Use finally for cleanup that must happen whether the operation succeeds or fails.
async function loadUsers() {
try {
const response = await fetch('/api/users');
if (!response.ok) {
throw new Error('Request failed: ' + response.status);
}
return await response.json();
} catch (error) {
console.error('Unable to load users:', error);
throw error;
} finally {
hideLoadingIndicator();
}
}11. Browser Events
What is event handling?
Event handling is the process of responding to browser events such as clicks, keyboard input, form submissions, and pointer movement. addEventListener receives an event type and a callback to execute when that event occurs.
A named callback is useful when the logic is substantial or the listener may need to be removed later, because removal requires the same function reference.
const button = document.getElementById('my-button');
function handleClick() {
console.log('Button clicked');
}
button.addEventListener('click', handleClick);What is an event object?
The browser passes an event object to a listener with information about what occurred. Important members include `type`, `target`, `currentTarget`, `preventDefault()`, and `stopPropagation()`.
The exact event subclass may add data such as keyboard keys, pointer coordinates, or form submission details.
What are capturing, target, and bubbling phases?
During capturing, an event travels from outer ancestors toward its target. It then reaches the target phase and, for bubbling events, travels back through ancestors. Most listeners use the bubbling phase by default.
Register a capturing listener with `{ capture: true }`. Use `stopPropagation()` sparingly because it can disrupt delegation and unrelated ancestor listeners.
What is the difference between preventDefault() and stopPropagation()?
`preventDefault()` cancels a cancelable browser action, such as link navigation or form submission. `stopPropagation()` prevents the event from continuing through the event path. One does not imply the other.
`stopImmediatePropagation()` additionally blocks later listeners registered on the same element.
What is the difference between target and currentTarget?
`event.target` is where the event originated. `event.currentTarget` is the element whose listener is currently running, so they often differ during delegation.
Inside a regular `addEventListener` callback, `this` normally equals `currentTarget`; arrow functions inherit `this`, so using `event.currentTarget` is usually clearer.
How do you remove an event listener?
Call `removeEventListener()` with the same event type, function reference, and capture setting used during registration. Two identical-looking anonymous functions are different objects and cannot remove one another.
An AbortController offers convenient grouped cleanup by passing its signal when registering listeners and later calling `abort()`.
const controller = new AbortController();
button.addEventListener('click', handleClick, {
signal: controller.signal,
});
controller.abort();12. Senior Events
What is event capturing?
During capturing, an event travels from outer ancestors toward its target. After the target phase, most events bubble back upward. Event listeners use bubbling by default, while the capture option registers a listener for the downward phase.
Capturing can observe or intercept interactions before target and bubbling listeners, but should be used deliberately because it changes expected event ordering.
form.addEventListener(
'click',
() => console.log('Form capture'),
{ capture: true }
);How do you stop event propagation?
event.stopPropagation() prevents an event from continuing through the remaining capture or bubble path. event.stopImmediatePropagation() also prevents later listeners on the same element from running.
Use either carefully because stopping propagation can break event delegation and unrelated ancestor handlers. It does not cancel the browser’s default action; preventDefault handles that separately.
button.addEventListener('click', (event) => {
event.stopPropagation();
console.log('Only the button handler');
});13. Debouncing and Throttling
When should you use debounce?
Use debounce for frequent events where only the final result after a pause matters. Each new call resets the delay, preventing repeated expensive work while activity continues.
Search suggestions, form validation, resize calculations, auto-saving, and filtering large datasets are common examples. Do not debounce interactions that require continuous feedback.
const search = debounce((query) => {
fetch('/api/search?q=' + encodeURIComponent(query));
}, 300);
input.addEventListener('input', (event) => {
search(event.target.value);
});What is the difference between debounce and throttle?
Debouncing waits until calls stop and normally runs only the final call. Throttling permits execution while activity continues but limits it to at most once per interval.
Debounce a search request where the final value matters. Throttle a scroll-position update where periodic progress matters.
14. Event Loop
In what order will synchronous logs and a zero-delay timer run?
Synchronous statements run immediately on the call stack. A setTimeout callback is handled by the runtime and queued as a task after its minimum delay, so even a zero-delay timer waits for the current stack to empty.
Zero milliseconds means the callback cannot become eligible before that delay; it does not mean run immediately.
console.log('First');
setTimeout(() => {
console.log('Second');
}, 0);
console.log('Third');
// First, Third, SecondWhere do Promise callbacks fit in the event loop?
Synchronous code executes first. Promise reactions enter the microtask queue, which is drained after the current stack completes and before the event loop selects the next normal task such as a timer callback.
This priority explains why an already-resolved Promise callback runs before a zero-delay setTimeout scheduled in the same turn.
console.log('A');
setTimeout(() => console.log('B'), 0);
Promise.resolve().then(() => console.log('C'));
console.log('D');
// A, D, C, B15. Prototypes and Inheritance
What is prototypal inheritance?
JavaScript objects can delegate property lookup to another object through their prototype. If a property is not found directly, the engine searches each object along the prototype chain until it finds the property or reaches null.
Methods inherited from a prototype are shared rather than copied onto every object.
const animal = {
speak() {
return 'Sound';
},
};
const dog = Object.create(animal);
dog.speak(); // 'Sound'How does prototypal inheritance differ from class inheritance?
Traditional class inheritance describes classes inheriting from classes. JavaScript fundamentally links objects through prototype chains. The class and extends syntax provide a familiar class-style interface while still creating prototype relationships underneath.
Classes do not replace JavaScript’s prototype model; they use it.
class Animal {
speak() {
return 'Sound';
}
}
class Dog extends Animal {}
new Dog().speak();Should you modify built-in prototypes?
Avoid modifying built-in prototypes because the change affects the entire runtime, may conflict with libraries or future standards, and hides behaviour from readers. Prefer a normal utility function you own.
A carefully specified polyfill is a limited exception: feature-detect first and implement the standard semantics rather than inventing a custom built-in method.
function toTitleCase(value) {
return value.replace(/w/g, (letter) => letter.toUpperCase());
}16. Async Functions and Promises
What does an async function return?
An async function always returns a Promise. Returning an ordinary value fulfills that Promise with the value, while throwing an error rejects it. Returning an existing Promise adopts that Promise’s eventual state.
Callers must therefore await the function or handle its returned Promise.
async function getNumber() {
return 42;
}
getNumber().then(console.log); // 42
async function fail() {
throw new Error('Failed');
}What is the relationship between async/await and Promises?
Async and await are syntax built on Promises. An async function returns a Promise, and await suspends only that function until the supplied value settles before resuming it.
Await does not block the main thread. It makes sequential Promise workflows easier to read and allows local rejection handling with try and catch.
async function loadUser() {
const response = await fetch('/api/user');
return response.json();
}17. Pure Functions
When are pure functions useful?
Pure functions are useful for calculations, state transformations, selectors, reducers, and business rules. Their output depends only on explicit inputs, making them predictable, reusable, and straightforward to test.
Applications still need side effects. The goal is to isolate I/O and mutation from pure business logic rather than pretending side effects do not exist.
function applyDiscount(price, percentage) {
return price * (1 - percentage / 100);
}
applyDiscount(100, 20); // 8018. Browser Compatibility
What is polyfilling?
A polyfill supplies a JavaScript implementation of a modern runtime API when a target browser does not provide it. Feature detection determines whether the implementation is needed.
Polyfills add missing APIs such as Promise or Array methods. Transpilation instead converts unsupported syntax such as optional chaining or spread into older syntax.
if (!Array.prototype.includes) {
// Install a standards-compatible implementation.
}What are the drawbacks of polyfills?
Polyfills improve compatibility but add download size, parsing and execution work, maintenance, and sometimes imperfect emulation. Sending unnecessary compatibility code to modern browsers wastes resources.
Define supported browsers, use feature detection, load polyfills selectively, and retire obsolete browser support when business requirements allow it.
19. Closures
Where are closures used?
Closures preserve private state and remembered configuration. They appear in counters, function factories, event handlers, callbacks, memoisation, module patterns, and React hooks.
A returned multiplier function, for example, remembers the multiplier supplied when it was created.
function createMultiplier(multiplier) {
return (number) => number * multiplier;
}
const double = createMultiplier(2);
double(5); // 10What are the drawbacks of closures?
A closure can retain referenced values for as long as the function remains reachable. Long-lived closures that capture large objects, DOM elements, listeners, timers, or subscriptions can therefore increase memory use or contribute to leaks.
Remove listeners, clear timers and subscriptions, avoid unnecessary captures, release long-lived references, and use memory-profiling tools. Engines may optimise unused variables; closures do not automatically retain everything forever.
function createHandler(largeData) {
return () => console.log(largeData.length);
}20. Browser Storage
What is the difference between cookies, localStorage, and sessionStorage?
Cookies are small values with configurable expiry, domain, path, Secure, HttpOnly, and SameSite controls. Matching cookies are sent with HTTP requests, making properly configured HttpOnly cookies suitable for server-managed sessions.
localStorage is origin-scoped and persists until cleared. sessionStorage is scoped to an origin and browser tab and normally disappears when that tab closes. Both Web Storage APIs are accessible to JavaScript, so sensitive authentication credentials should not be stored there.
localStorage.setItem('theme', 'dark');
sessionStorage.setItem('checkoutStep', '2');21. Application Performance
How would you optimise a new front-end application?
Start with measurable goals and production-like evidence. Optimise the production build, remove unused code, split bundles by route, lazy-load non-critical features, compress assets, optimise images and fonts, cache static files, and serve them near users.
Validate changes with Lighthouse and browser profiles, then use real-user monitoring because actual devices, networks, and journeys reveal bottlenecks that lab tests may miss.
Why bundle and compress JavaScript?
Bundling resolves application modules into deployable assets and enables chunking, tree shaking, and stable content hashes. The goal is useful chunk boundaries rather than one enormous bundle.
Minification changes the code representation. Brotli or Gzip compression happens during transfer. These are different layers and production applications normally use both.
What are minification and uglification?
Minification removes unnecessary whitespace, comments, and syntax while preserving behaviour. Minifiers may also fold constants, remove unreachable code, and mangle safe local identifiers. Uglification is often used informally for these transformations.
The output is difficult for humans to inspect, so production debugging depends on source maps and error monitoring.
function calculateTotal(price, tax) {
return price + tax;
}
// May become: function a(b,c){return b+c}What are source maps?
A source map records how generated code corresponds to original files, names, and locations. Developer tools and monitoring services use it to translate a stack frame in a minified bundle back to maintained source.
Teams often upload production maps privately to an error-monitoring service rather than serving them publicly when source exposure is a concern.
bundle.min.js:1:2478
->
src/checkout/payment.ts:42What is code splitting?
Code splitting creates chunks loaded when a route, component, or feature is needed. Dynamic import is the platform primitive, while frameworks provide routing and lazy-component integrations.
Split at meaningful boundaries. Too many tiny chunks can add request overhead and loading waterfalls, so preload likely next steps and measure the result.
const loadAdminPanel = () => import('./AdminPanel');What is tree shaking?
Tree shaking is build-time dead-code elimination based on the module graph. Static ES module syntax makes imports and exports analyzable without executing the program.
Top-level side effects, dynamic patterns, and some CommonJS modules can prevent removal. Package sideEffects metadata must accurately describe required setup.
22. Image Performance
How would you optimise large images?
Generate variants close to rendered sizes, compress them, select modern formats, and let srcset and sizes help the browser choose an appropriate resource. An image CDN can automate transformation and edge delivery.
Lazy-load below-the-fold images and reserve their dimensions. Do not lazy-load the primary above-the-fold LCP image; give genuinely critical imagery appropriate priority.
Why specify image width and height?
Image width and height attributes provide an intrinsic aspect ratio. The browser can reserve the correct shape before the resource arrives, preventing surrounding content from jumping.
CSS can still resize the image responsively; the attributes describe source proportions rather than forcing a fixed rendered size.
<img src="product.webp" width="800" height="600" alt="Product" />
23. Code Quality and Testing
How would you manage code quality at scale?
Code quality at scale needs shared standards and automated feedback. Use TypeScript, linting, formatting, focused tests, dependency scanning, accessibility checks, performance budgets, and build validation locally and in CI.
Reviews should focus on behaviour, design, maintainability, and risk rather than formatting tools can enforce. Production monitoring completes the feedback loop.
Which testing layers would you use?
Unit tests give fast feedback for isolated calculations. Integration tests verify meaningful collaboration between components or services. End-to-end tests exercise realistic critical journeys in a deployed-like browser environment.
Choose the cheapest layer that provides confidence, avoid duplicating every assertion at every layer, and keep the end-to-end suite focused enough to remain trustworthy.
24. Front-End Security
What is an XSS attack?
Cross-site scripting allows attacker-controlled content to execute in the security context of a trusted site. Stored XSS persists in application data, reflected XSS returns malicious input in a response, and DOM-based XSS is introduced by unsafe client-side processing.
An attacker may read JavaScript-accessible data, perform actions as the user, alter the interface, or capture sensitive input.
How do you prevent XSS?
Prevent XSS primarily through safe, context-aware output handling. Use framework text escaping, textContent for plain text, and a trusted sanitizer only when rendering HTML is genuinely required. Input validation is not a substitute for output safety.
Avoid unsafe sinks, keep dependencies current, deploy a restrictive Content Security Policy, and protect session cookies with HttpOnly, Secure, and appropriate SameSite settings.
element.textContent = userComment; // Sanitise before using an HTML-rendering API.
25. Content Delivery Networks
What is a CDN and how does it work?
A content delivery network places edge servers across geographic regions. Routing directs a request to a suitable edge, which serves a fresh cached response or requests it from the origin on a miss.
Cache keys, TTLs, validation headers, purging, and privacy rules determine what can be reused safely. HTML and API caching require careful variation and authorization design.
What are the advantages of a CDN?
Serving content from a nearby edge reduces network distance and latency. Shared caches reduce origin requests and can absorb bursts that would otherwise overload application infrastructure.
CDNs also commonly provide compression, TLS termination, request filtering, DDoS mitigation, observability, and programmable edge behaviour.
What are the disadvantages of a CDN?
A CDN introduces another distributed system with its own configuration, logs, failure modes, and costs. Cache invalidation can be difficult, stale content may persist, and provider outages become dependencies.
Incorrect cache keys or headers can expose private content. Use content-hashed URLs for immutable static assets and carefully test personalized responses.
<script src="/assets/app.a84f21.js"></script>
Which CDN providers could you use?
Common providers include AWS CloudFront, Cloudflare, Azure Front Door and CDN, Google Cloud CDN, Fastly, Akamai, and Vercel’s edge network. Existing infrastructure often makes one option operationally simpler.
Compare geographic coverage, origin integration, cache controls, observability, security, edge compute, support, egress pricing, and migration options.
26. Front-End Architecture
What are micro-frontends?
Micro-frontends apply independently owned application boundaries to a frontend. A shell composes domain-focused areas and coordinates navigation, authentication, routing, observability, and design-system use.
Implementations include Module Federation, build-time packages, server-side composition, Web Components, and occasionally iframes for strongly isolated cases.
When would you use micro-frontends?
Use micro-frontends when several teams are blocked by shared ownership and coordinated releases, domains are clear, and independent deployment has measurable value. The organisation must operate the additional tooling and runtime boundaries.
Do not adopt them merely because a codebase is large. A modular monolith, packages, clearer ownership, or improved CI is usually simpler for one small team.
What are the benefits of micro-frontends?
The main benefit is organisational: teams can own a business area from development through deployment. Domain-focused codebases and release pipelines can reduce coordination and support gradual replacement of legacy areas.
Technical boundaries may improve fault isolation when runtime composition, shared dependencies, and shell behaviour degrade gracefully.
What are the disadvantages of micro-frontends?
Micro-frontends move some complexity from team coordination into architecture and infrastructure. Challenges include duplicate dependencies, bundle growth, shared state, routing, authentication, communication, version compatibility, and consistent design.
Testing, monitoring, debugging, local development, and deployments become distributed concerns. The organisational benefit must justify that continuing cost.
What criteria would justify moving from a frontend monolith?
Ask whether teams regularly block one another, deployments are tightly coupled, domains are clear, releases genuinely need independence, shared state is manageable, and the organisation can support additional platform work.
Try clearer modules, ownership boundaries, packages, and faster CI first. Move only when independent deployment solves a measured organisational bottleneck more effectively than a modular monolith.
27. Runtime and Memory
What is the temporal dead zone?
Bindings declared with let, const, and class are created when their scope is entered, but remain uninitialized until execution reaches the declaration. Reading them before initialization throws a ReferenceError rather than returning undefined.
This behavior catches accidental use before declaration and differs from var, whose binding is initialized to undefined during hoisting.
console.log(value); // ReferenceError const value = 42;
What does strict mode change?
Strict mode opts code into safer JavaScript semantics. It prevents assignment to undeclared variables, makes a plain function call receive undefined as this, and turns several silent failures into exceptions.
Add "use strict" to scripts or functions when needed. ES modules and class bodies already run in strict mode, so adding the directive there is unnecessary.
'use strict'; undeclaredValue = 1; // ReferenceError
Is JavaScript pass-by-value or pass-by-reference?
Every JavaScript argument is passed by value. A primitive value is copied directly; an object argument copies the reference value, so both variables initially point to the same object.
Mutating that object is visible to the caller. Reassigning the local parameter only changes the local copy of the reference, which is why JavaScript is not pass-by-reference.
function update(user) {
user.name = 'Ada';
user = { name: 'Grace' };
}
const user = { name: 'Lin' };
update(user);
console.log(user.name); // AdaHow does JavaScript garbage collection work?
Modern JavaScript engines primarily use tracing garbage collectors. They start from roots such as global variables, active execution contexts, and live closures, then mark the object graph that remains reachable.
Unreachable values may be reclaimed later. Developers cannot rely on exact collection timing, so resources such as subscriptions, files, and sockets still need explicit lifecycle management.
What commonly causes memory leaks in browser applications?
A garbage-collected application leaks memory when obsolete data remains reachable. Common browser causes include event listeners and subscriptions that are never removed, intervals that continue running, detached DOM nodes, unbounded caches, and long-lived closures.
Use lifecycle cleanup, bounded caches, AbortController where appropriate, and repeated heap snapshots to identify retained objects and their reference paths.
When should you use WeakMap or WeakSet?
WeakMap stores values against object or non-registered symbol keys, while WeakSet tracks object-like values without owning them strongly. If no other strong reference reaches a key, the associated weak entry can disappear.
Weak collections are intentionally not enumerable and expose no size because garbage collection is nondeterministic. Use Map or Set when primitive keys, iteration, or stable membership counts are required.
28. Modern JavaScript and Browser APIs
What is a Symbol and when is it useful?
Calling Symbol creates a unique primitive value, even when two symbols have the same description. Symbols can be object property keys and are skipped by common string-key enumeration such as Object.keys.
Well-known symbols customize language behavior, including iteration and type conversion. Symbol-keyed properties remain discoverable through reflection, so symbols provide uniqueness rather than privacy.
const id = Symbol('id');
const record = { [id]: 123 };
record[id]; // 123How do the Promise combinators differ?
Promise combinators coordinate multiple asynchronous inputs. Promise.all preserves input order and rejects when one input rejects. Promise.allSettled waits for every outcome. Promise.race mirrors the first settled input, while Promise.any returns the first fulfillment.
Choose based on failure semantics rather than convenience. Starting several promises is concurrent, but these methods do not cancel unfinished work automatically.
Why does fetch not reject for HTTP errors?
The Fetch API distinguishes transport failure from an HTTP response. A server response with an error status still produced a valid Response object, so the promise resolves. DNS failure, connection failure, cancellation, and similar request failures reject it.
Check response.ok before parsing successful data, then preserve useful status and response details in the application error.
const response = await fetch('/api/user');
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
const user = await response.json();How do you cancel asynchronous browser work?
AbortController provides a standard cancellation signal for Fetch and other compatible browser APIs. Calling abort notifies every operation using the signal and lets code release work when navigation, replacement requests, or time limits make the result irrelevant.
Cancellation is cooperative: an API must support AbortSignal, and custom asynchronous work must listen for it and stop its own resources.
const controller = new AbortController();
const request = fetch('/api/search', { signal: controller.signal });
controller.abort();
await request; // Rejects with an abort-related errorHow do ES modules differ from CommonJS?
ES modules are the JavaScript standard module system. Their static structure supports browser loading, tooling analysis, and tree shaking, while exported bindings remain live. CommonJS evaluates modules synchronously and exposes values through require and module.exports.
Node.js supports both systems, but their resolution rules and interoperability can differ. Package type, file extension, and package exports determine how a module is interpreted.
What is the difference between optional chaining and nullish coalescing?
Optional chaining short-circuits a continuous property, element, or call chain when its current value is nullish. Nullish coalescing chooses a fallback only for null or undefined.
This differs from logical OR, which also replaces false, zero, NaN, and empty strings. Use these operators when those falsy values are meaningful application data.
const city = user.address?.city ?? 'Unknown'; const retries = config.retries ?? 3; // Keeps 0
When should you use a Web Worker?
Web Workers provide an isolated execution context outside the main browser thread. They are useful for expensive computation that would otherwise delay input, rendering, and animation. Data is exchanged with postMessage using structured cloning or transferable objects.
Workers add startup, communication, serialization, and lifecycle costs. They do not improve ordinary DOM work or inherently make network requests faster.
What is CORS and how does it work?
The same-origin policy restricts browser scripts from reading many cross-origin responses. Cross-Origin Resource Sharing lets a server relax that restriction with headers such as Access-Control-Allow-Origin.
For non-simple requests, the browser first sends a preflight to check methods and headers. CORS is enforced by browsers, is not authentication, and does not stop non-browser clients from making requests.
How to Explain Concepts Clearly
A strong technical explanation has three parts: a clear one-sentence definition, a short example, and a practical use case, limitation, or common mistake. Avoid memorising scripts word for word because useful follow-up discussions test whether you understand the underlying behaviour.
For example: “A closure is when a function retains access to variables from its outer lexical scope, even after the outer function has returned. A counter is a common example because the returned function remembers its private count. Closures are useful for preserving state and creating function factories.”
Practise explaining each concept aloud in your own words. Confidence comes from reasoning about an example, not merely reciting a definition.
- Define the concept clearly
- Give a concise example
- Explain a use case, trade-off, or limitation