Types, values and equality
The eight types, how they convert, and the comparisons that trip people up: the questions that open most JavaScript screens.
01What are JavaScript data types?MidJavaScriptdemo
Eight: seven primitives (string, number, bigint, boolean, undefined, symbol, null) and object. Arrays, functions, dates, maps and class instances are all objects. Primitives are immutable and compared by value; objects are mutable and compared by reference.
- Seven primitives plus
object typeof null === "object"is a legacy bug- Primitives are immutable and compared by value
- Arrays and functions are objects
- Autoboxing explains methods on primitives
A primitive has no methods of its own. "hi".length works because the engine wraps the string in a temporary String object for the property access (autoboxing), then throws the wrapper away.
The trap: typeof [] and typeof null are both "object". Use Array.isArray(value) for arrays and value === null for null.
typeof with what Object.prototype.toString reports.02What does typeof typeof 0 evaluate to?MidJavaScript
Show the answerHide the answer
"string". typeof 0 is the string "number", and the typeof of any string is "string". Because typeof always returns a string, any chain of two or more is "string".
typeofreturns a string- Evaluates right to left
- Safe on undeclared variables, except in the TDZ
Unary operators apply right to left, so the inner typeof runs first.
typeof is also the one safe way to probe a variable that may not be declared at all: typeof notDeclared is "undefined" instead of a ReferenceError. The exception is a let or const still in its temporal dead zone, which throws.
03What is the difference between null and undefined?MidJavaScriptdemo
undefined means no value has been assigned: an unset variable, a missing property or argument, a function with no return. null is a value your code assigns on purpose to say "empty". They are loosely equal (==) but not strictly equal (===).
undefined: not assigned;null: intentionally emptynull == undefinedbutnull !== undefined- Defaults only kick in for
undefined - JSON keeps
null, dropsundefined
- Default parameters and destructuring defaults apply to
undefinedonly, not tonull. JSON.stringifydrops properties whose value isundefinedand keepsnull. An API that wants to clear a field needsnull.??and?.treat both as "nullish".value == nullis the one idiomatic use of==: it is true for exactly these two.typeof undefinedis"undefined";typeof nullis"object".
null and undefined behave in each column.04What is the only value not equal to itself in JavaScript?SeniorJavaScript
NaN. IEEE 754 defines NaN as unequal to everything, itself included, so NaN === NaN is false. Test for it with Number.isNaN(x) or Object.is(x, NaN), never with ===.
NaN !== NaNNumber.isNaNvs globalisNaNObject.isandincludesfind ittypeof NaNis"number"
- The global
isNaNconverts its argument to a number first, soisNaN("abc")istrue.Number.isNaNdoes not convert. includes,SetandMapuse SameValueZero, which findsNaN;indexOfuses===, which cannot.- NaN comes from failed numeric conversions (
Number("abc"),parseInt("x")) and undefined maths (0 / 0,Math.sqrt(-1)).
The trap: The self-inequality is a usable test: x !== x is true only for NaN. It works, but Number.isNaN says what it means.
05What are truthy and falsy values in JavaScript?MidJavaScriptdemo
A value is falsy if it converts to false in a boolean context. The list is short: false, 0, -0, 0n, "", null, undefined and NaN. Everything else is truthy, including "0", "false", [] and {}.
- The eight falsy values
- Empty arrays and objects are truthy
"0"and"false"are truthy strings- Prefer
??when 0 or "" are valid
Boolean contexts are if, while, the ternary, !, && and ||. Boolean(value) and !!value make the conversion explicit.
The trap: if (count) treats a real 0 like a missing value, and name || "Anonymous" replaces an intentional empty string. When 0 or "" are valid, test for null/undefined explicitly or use ??.
06What is the difference between the equality operators == and ===?JuniorJavaScriptdemo
=== (strict equality) compares type and value with no conversion. == (loose equality) first converts the operands to a common type by a fixed set of rules, which produces surprises like "0" == false. Use ===; the one common exception is x == null, which matches both null and undefined.
===never converts types==follows the abstract equality algorithmx == nullidiom- Objects compare by reference either way
Object.isfor NaN and -0
Object.is is a third comparison: like ===, except Object.is(NaN, NaN) is true and Object.is(0, -0) is false. React uses it to decide whether state changed.
Interview line: I default to ===, and use == null on purpose when I mean "null or undefined".
== and === and look at how much of the grid lights up.07What is the output of the following code?MidJavaScript
Show the answerHide the answer
true, then false. In a == c, an object meets a string, so the array is converted to a primitive: "1,2,3", equal to c. In a == b, both sides are objects, so there is no conversion and they are compared by reference: two different arrays.
- Object vs primitive: the object is converted with
toString - Object vs object: reference comparison
- How to compare arrays by content
To compare contents, compare element by element (as above), or deeply for nested data. JSON.stringify(a) === JSON.stringify(b) works for plain data but is order-sensitive for objects and loses undefined, functions and NaN.
08What does 0.1 + 0.2 === 0.3 evaluate to?MidJavaScriptdemo
false. Numbers are IEEE 754 doubles, stored in binary, and 0.1, 0.2 and 0.3 have no exact binary representation. The rounding errors don't cancel: the sum is 0.30000000000000004. Compare with a tolerance, or keep money in integer cents.
- IEEE 754 binary64
- 0.1 has no exact binary form
- Compare with a relative epsilon
- Integer cents for money
- MAX_SAFE_INTEGER and BigInt
- The same happens in every language that uses binary floating point: Python, Java, C#.
- For display, round:
(0.1 + 0.2).toFixed(2)orIntl.NumberFormat. For money, store integer cents (or useBigInt), and format at the edge. - Integers are exact up to
Number.MAX_SAFE_INTEGER(2^53 − 1). Past that, useBigInt.
09What is short-circuit evaluation in JavaScript?MidJavaScriptdemo
&&, || and ?? stop evaluating as soon as the result is known, and return one of their operands, not necessarily a boolean. a && b returns a if it is falsy, otherwise b. a || b returns a if it is truthy, otherwise b. a ?? b returns b only when a is null or undefined.
- Returns an operand, not a boolean
- Right side may never run
??vs||for defaults- Optional chaining is related short-circuiting
- Guards:
user && user.name, today usuallyuser?.name. - Defaults:
options.retries ?? 3keeps a deliberate0;options.retries || 3would replace it. - Conditional calls:
onClose && onClose(), oronClose?.(). - Logical assignment:
cache[key] ??= compute()only computes when the entry is missing.
The trap: a ?? b || c is a syntax error: ?? cannot be mixed with && or || without parentheses.
10What is the difference between the postfix i++ and prefix ++i increment operators?JuniorJavaScriptdemo
Both add 1 to the variable. They differ in the value of the expression: i++ evaluates to the value before the increment, ++i to the value after. The difference only matters when the result is used.
- Same side effect, different expression value
- Postfix returns the old value
- Irrelevant when the value is discarded
In a for loop's update clause the value is thrown away, so i++ and ++i behave the same. In an expression like arr[i++] = x, the old index is used and then i moves on.
The trap: count++ inside a React state setter (setCount(count++)) passes the old value and mutates a local variable. Write setCount((c) => c + 1).
11What is the difference between an expression and a statement in JavaScript?MidJavaScript
An expression produces a value: 1 + 2, user.name, fn(), a ? b : c. A statement performs an action and produces no value you can use: if, for, return, a let declaration. Rule of thumb: if you can assign it to a variable or pass it as an argument, it is an expression.
- Expressions produce values; statements perform actions
- Expression statements
- Function declarations vs expressions
- JSX braces take expressions
- An expression statement is an expression used where a statement is expected:
fn();orx = 1;. - A function declaration is a statement and is hoisted; a function expression (
const f = function () {}) is a value and follows its variable's rules. - JSX braces accept expressions only. That is why conditional rendering uses
&&and? :rather thanif. - An arrow function's concise body is an expression; a body with braces is a block of statements and needs
return.
12Are semicolons required in JavaScript?MidJavaScript
Not strictly. Automatic semicolon insertion (ASI) adds them at line breaks where the next token cannot continue the statement. But ASI does not insert one before a line that starts with (, [ or a template literal, and it does insert one right after return. Pick a style and let a formatter enforce it.
- ASI exists and is specified
- Hazards: lines starting with
(,[or a template literal returnfollowed by a newline- Consistency via a formatter
The engine reads a\n[1, 2].forEach(...) as a[1, 2].forEach(...): the comma operator makes that a[2], which is undefined, and calling forEach on it throws. A leading ; before such lines (the no-semicolon style's rule) or semicolons everywhere both prevent it.
- Lines starting with
(,[, a template literal,+,-or/are the hazards. return,throw,break,continue,yieldand postfix++/--are "restricted productions": a line break after them ends the statement.- Prettier with
semi: falseinserts the protective leading semicolons for you.
13What does the following function return?MidJavaScript
Show the answerHide the answer
undefined. A line break directly after return ends the statement, so ASI turns it into return;. The object literal below becomes an unreachable block containing a label (message:) and a string expression.
- ASI after
return - The braces become a block with a label
- Brace on the same line
Keep the opening brace (or parenthesis) on the same line as return. This is one reason JavaScript style guides put braces at the end of the line, not on the next one. Multi-line JSX is wrapped in return ( for the same reason.
14What does "use strict" do and what are some of the key benefits to using it?SeniorJavaScript
It opts a script or a function into strict mode, which turns silent mistakes into errors and removes some confusing features. ES modules and class bodies are strict automatically, so in modern code you rarely type it.
- Silent errors become thrown errors
- No accidental globals
thisis undefined in plain calls- Modules and classes are strict by default
- Assigning to an undeclared variable throws a
ReferenceErrorinstead of creating a global. - Writing to a read-only or non-writable property, or adding to a frozen object, throws a
TypeErrorinstead of failing silently. thisisundefinedin a plain function call, instead of the global object.with, octal literals like010, duplicate parameter names anddeleteon a plain variable are syntax errors.- Words reserved for the future (
implements,interface,package, ...) cannot be identifiers.
In a non-strict ("sloppy") script, the first assignment would silently create a global, the second would be ignored, and whoAmI() would return the global object.
The trap: "use strict" must be the first statement of the script or function. Concatenating a strict file with a sloppy one can switch the whole bundle's mode, which is one reason bundlers emit modules.
Scope, hoisting and closures
Where a variable is visible, when it exists, and how a function keeps hold of the variables around it.
01What are the differences between var, let, const and no keyword statements?MidJavaScriptdemo
var is function-scoped, hoisted with the value undefined, and can be redeclared. let and const are block-scoped and sit in a temporal dead zone until their declaration runs; const also cannot be reassigned (its object can still change). Assigning with no keyword creates an accidental global in sloppy code and throws in strict code.
- Function scope vs block scope
- Temporal dead zone
constprevents reassignment, not mutation- Per-iteration
letbindings in loops - Implicit globals without a keyword
The loop shows the practical difference. There is one var i for the whole function, and the callbacks run after the loop, when it is 3. A let in a for header gets a fresh binding per iteration, so each callback closes over its own j.
Interview line: I use const by default, let when the variable is reassigned, and never var.
i bindings exist versus j bindings.02How does hoisting work in JavaScript?MidJavaScript
Before running a scope, the engine registers every declaration in it, as if they had moved to the top. Function declarations are fully available. var exists with the value undefined. let, const and class exist but are uninitialised (the temporal dead zone) until their line runs. Assignments are never hoisted.
- Declarations are processed before execution
- Function declarations are fully hoisted
var→ undefined;let/const→ TDZ- Assignments are not hoisted
- Function declarations can be called before the line they appear on. That is what lets helpers live at the bottom of a file.
- Function expressions and arrow functions follow their variable:
vargivesundefined(calling it is aTypeError),let/constthrow in the TDZ. - The TDZ is a feature: reading a variable before it is set is almost always a bug, and
letturns it into an error instead of a silentundefined.
The trap: Nothing is physically moved. Hoisting is a model for the creation phase: the scope's bindings are created first, then the code runs line by line.
03What will the console log in this example?MidJavaScriptdemo
Show the answerHide the answer
undefined. The inner var foo is hoisted to the top of foobar, so inside the function foo refers to the local variable, not the outer one. At the console.log, that local exists but has not been assigned yet.
- The local declaration shadows the outer variable
- Declaration hoisted, assignment not
letwould throw instead
With let foo = 2 inside the function, the same code throws a ReferenceError instead: the local still shadows the outer foo, but it is in its temporal dead zone at the console.log.
foo appear in the function's scope before the first line runs.04What is the difference between lexical scoping and dynamic scoping?MidJavaScript
With lexical (static) scoping, a function sees the variables of the place where it is written. With dynamic scoping, it sees the variables of the place where it is called. JavaScript is lexically scoped, which is what makes closures work. The one dynamic-looking part is this, which depends on the call (except in arrow functions).
- Lexical = where defined; dynamic = where called
- JavaScript is lexical
- Enables closures
thisis the dynamic exception, arrows make it lexical
show was defined at the top level, so level resolves there. Under dynamic scoping (Bash, early Lisps, Perl's local) the call from run would have printed "inside run".
- Lexical scoping can be read from the source: you know what a name refers to without running the program.
thisis resolved per call, which is why a method passed as a callback loses its object.- Arrow functions take
thislexically, from the surrounding code, which is why they fix that problem.
05What is a closure? Can you give a useful example of one?SeniorJavaScriptdemo
A closure is a function together with the variables of the scope it was created in. The function keeps access to them after that scope has finished running. Every function in JavaScript is a closure; it matters when a function outlives its parent, as with factories, callbacks, private state and React hooks.
- Function + its lexical environment
- Outlives the parent scope
- Private state and factories
- Each call creates a new scope
- Stale closures in React
Each call to createCounter makes a new count, and only the two returned functions can reach it: private state without a class. The same mechanism powers once, memoize, debounce timers, partially applied functions and event handlers that remember their item.
The trap: Closures capture variables, not values. A stale closure is one that captured an old variable: in React, every render has its own seconds. The fix above is setSeconds((s) => s + 1).
count after createCounter returns: the scope survives because a function still points at it.06What is the reason for wrapping the entire contents of a JavaScript source file in a function that is immediately invoked?MidJavaScript
An IIFE (immediately invoked function expression) gives the file a private scope. Classic scripts all share one global scope, so top-level vars and functions would collide with other scripts; inside the function they stay local, and you expose only what you choose. ES modules have their own scope, so IIFEs are now mostly legacy.
- Avoids polluting the global scope
- Private state (module pattern)
- Parentheses make it an expression
- Replaced by ES modules
- The module pattern: return an object of public functions, keep the rest private (jQuery plugins, UMD bundles).
- Safe aliases:
(function ($, undefined) { ... })(jQuery)gave the file a guaranteed$. - Async setup in a classic script:
(async () => { await init(); })();. Modules can use top-levelawaitinstead. - The parentheses are required:
function () {}()at the start of a statement parses as a declaration, not an expression.
Interview line: With type="module" scripts and bundlers, every file already has its own scope, so I reach for an IIFE only to run async code in a classic script.
07What is the difference between a parameter and an argument?MidJavaScript
Parameters are the names in a function's definition; arguments are the values passed when it is called. In function add(a, b), a and b are parameters; in add(1, 2), 1 and 2 are arguments.
- Parameters: definition; arguments: call
- Missing → undefined, extra → ignored
- Default and rest parameters
function.length
- JavaScript does not check the count: missing arguments are
undefined(or take the default), extra ones are ignored unless collected with a rest parameter. fn.lengthcounts parameters before the first one with a default or a rest parameter.- Arguments are passed by value; for objects, that value is a reference.
Objects, this and prototypes
How objects are made, how they inherit, what this points at, and what copying and comparing an object really does.
01Describe the different ways to create an object. When should certain ways be preferred over others?MidJavaScript
An object literal for one-off data; a class (or a constructor function with new) when you need many instances sharing methods; a factory function when you want privacy through closures and no new; Object.create(proto) to pick the prototype explicitly, including Object.create(null) for a bare dictionary (though a Map is usually better).
- Literal, class/constructor, factory,
Object.create - Shared methods on the prototype save memory
- Factories give privacy without
this Object.create(null)has no inherited keys
Others worth naming: Object.fromEntries(pairs), structuredClone(obj) and spread copies ({ ...base, extra: 1 }).
02What is the this keyword and how does it work?SeniorJavaScriptdemo
this is decided by how a function is called, not where it is defined. Called as obj.method(), it is obj. Called with new, it is the new object. With call, apply or bind, it is what you pass. Called plainly, it is undefined in strict code (the global object in sloppy code). Arrow functions have no this of their own and use the surrounding one.
- Determined at call time
- The four binding rules and their precedence
- Arrow functions are lexical
- Lost
thisin callbacks - Strict vs sloppy default
The rows are in order of precedence: new beats bind, which beats a method call, which beats the default. user.arrow returns "undefined" because the arrow's surrounding scope is the module, whose this is undefined; later works because the arrow inside it borrows the method's this.
The trap: Passing a method as a callback (button.addEventListener("click", obj.save), setTimeout(obj.save)) detaches it from obj. Bind it, or wrap it: () => obj.save().
this became.03Create a standalone function bind that is functionally equivalent to the method Function.prototype.bind.MidJavaScript
Show the answerHide the answer
Return a new function that, whenever it is called, calls the original with the fixed this and the preset arguments followed by the new ones: (...args) => fn.apply(context, [...preset, ...args]). Because the outer function ignores its own this, a later .call cannot override the binding.
- Return a closure that calls
apply/call - Supports preset arguments
- Bound
thiscan't be overridden bycall - Edge case:
newon a bound function
- Partial application comes for free: the preset arguments are fixed and the rest are supplied later.
- The native
bindalso handlesnew: calling a bound function withnewignores the boundthisand constructs normally. Supporting that needsnew.targetand a prototype link. - Binding an arrow function changes nothing about its
this; only the preset arguments apply.
04How does prototypal inheritance differ from classical inheritance?MidJavaScriptdemo
In classical inheritance (Java, C#), classes are blueprints and a subclass extends a parent class. In JavaScript, objects inherit directly from other objects: each object has a hidden link, its prototype, and a property lookup that misses on the object walks up that chain. class and extends are syntax over this model.
- Objects inherit from objects
- The prototype chain and lookup
classis syntactic sugar- Delegation and shadowing
- Chain ends at
null
- Delegation, not copying: methods exist once on the prototype; every instance looks them up.
- Dynamic: adding a method to a prototype makes it available to existing instances;
Object.setPrototypeOfcan even change the chain (slowly). - Shadowing: assigning a property on the instance hides the prototype's one instead of changing it.
- The chain ends at
Object.prototype, whose prototype isnull.
Interview line: A JavaScript class is a constructor function plus a prototype object; extends links one prototype to another.
05Explain the difference between a static method and an instance method.SeniorJavaScriptdemo
A static method belongs to the class itself (Array.isArray, Object.keys, Promise.all) and is called on it; it has no instance to work on. An instance method lives on the prototype (Array.prototype.push) and works on this, the instance it is called on. Static methods suit factories and utilities; instance methods suit behaviour of one object.
- Static: on the constructor
- Instance: on the prototype, uses
this - Factories and utilities are static
- Static members are inherited by subclasses
- Inside a static method,
thisis the class (or a subclass that inherited it), not an instance. - Static members are inherited by subclasses:
class B extends AgivesB.createifA.createexists. - Static fields and
static {}blocks hold class-wide data and setup, such as caches or counters.
06Does JavaScript pass by value or by reference?MidJavaScriptdemo
Always by value. For objects, the value that is copied is a reference to the object (sometimes called call by sharing). So a function can mutate an object it receives and the caller sees it, but reassigning the parameter only changes the function's local copy.
- Always pass by value
- The value of an object is a reference
- Mutation is visible, reassignment isn't
- Call by sharing
nis a copied number: changing it inside has no effect outside.listis a copied reference to the same array:pushmutates that one array.user = ...points the local parameter at a new object; the caller's variable still points at the old one.
True pass-by-reference (C++ &, C# ref) would let user = ... replace the caller's variable. JavaScript has no way to do that.
list and the parameter point at one array; reassigning user moves only the local arrow.07How do you clone an object in JavaScript?MidJavaScriptdemo
Shallow copy with spread ({ ...obj }, [...arr]) or Object.assign({}, obj): top-level properties are copied, nested objects are shared. Deep copy with structuredClone(obj), which handles nested objects, Date, Map, Set and circular references. Avoid the JSON.parse(JSON.stringify(obj)) trick except for plain JSON data.
- Shallow vs deep
- Spread /
Object.assignare shallow structuredClonefor deep copies- JSON round-trip limitations
- Nested updates in React
The trap: Spread looks like a copy, and is one, one level deep. Updating nested state in React ({ ...state, user: { ...state.user, name } }) has to spread every level it changes.
structuredClone.08How do you compare two objects in JavaScript?MidJavaScript
===, == and Object.is all compare object references, so two separate objects with identical contents are not equal. To compare contents, compare keys and values, shallowly or recursively; in tests and Node use a deep-equal helper (node:util's isDeepStrictEqual, Vitest's toEqual) rather than JSON.stringify.
- Objects compare by reference
- Shallow vs deep comparison
- Recursion over keys
- Why JSON.stringify is unreliable
- Use a tested helper
- Shallow equality (keys and
Object.ison each value) is whatReact.memoanduseMemodependencies effectively use. - A full deep equal also has to handle
Date,Map,Set, typed arrays, prototypes and cycles. That is why it belongs in a library. JSON.stringifycompares in key order and dropsundefinedand functions, so it misreports both ways.
09Contrast mutable and immutable values, and mutating vs non-mutating methods.SeniorJavaScript
Primitives are immutable: string methods return new strings, and you can only replace a variable's value. Objects and arrays are mutable: they can be changed in place, and everyone holding a reference sees the change. Mutating methods change the array you call them on (push, sort, splice); non-mutating ones return a new array (map, filter, slice, toSorted).
- Primitives are immutable
- Mutating vs copying methods
- ES2023 change-by-copy methods
- Freeze is shallow
- Reference checks in React
Immutability matters most where change is detected by reference. React compares state with Object.is: mutate an array and set it back, and React sees the same reference and skips the render.
The trap: Object.freeze is shallow and const only stops reassignment: neither makes nested data immutable.
Functions and functional style
Pure functions, composition, recursion and caching: the ideas behind array methods, reducers and React's render model.
01What is functional programming?SeniorJavaScriptdemo
A style of building programs from pure functions over immutable data, combined by composition. Functions are values that can be passed and returned (higher-order functions); state changes are expressed as new values rather than mutation; side effects are pushed to the edges. JavaScript supports it alongside imperative and object-oriented styles.
- Pure functions
- Immutability
- Higher-order functions
- Composition
- Side effects at the edges
- Easier testing and reasoning
- Pure functions: output depends only on input; no side effects.
- Immutability: produce new values instead of changing existing ones.
- First-class and higher-order functions:
map,filter,reduce,compose, callbacks. - Composition: small functions piped together into bigger ones.
- Declarative: describe the result, not the loop.
Interview line: React leans on these ideas: components should be pure functions of props and state, and reducers return new state instead of mutating it.
02What is a pure function?SeniorJavaScript
A function that, given the same arguments, always returns the same result, and has no side effects: it does not mutate its arguments or outside state, and does no I/O (network, DOM, logging, storage, random numbers, the current time). Pure functions are easy to test, cache and run in any order.
- Same input, same output
- No side effects
- Doesn't mutate arguments
- Testable, cacheable
- React render purity
- Referential transparency: a call can be replaced by its result. That is what makes memoization safe.
- Side effects are not bad; they are the point of a program. Purity is about keeping them in a few known places.
- React requires rendering to be pure. StrictMode renders twice in development to surface impure components.
03Explain the differences between imperative and declarative programming.SeniorJavaScript
Imperative code says how: the exact steps, in order, usually mutating state (a for loop pushing into an array, document.createElement then appendChild). Declarative code says what result you want and leaves the steps to something else: SQL, HTML, CSS, array.filter(...), or React's JSX, where you describe the UI for a state and React works out the DOM changes.
- How vs what
- Mutation and steps vs expressions of the result
- SQL, HTML, CSS, JSX are declarative
- Declarative is built on imperative
The imperative version of that component would find the element, set its text, and add or remove the class on every change, remembering the previous state to know which. Declarative code is usually shorter and harder to get out of sync; imperative code gives finer control, which is why declarative libraries are built on imperative code underneath.
04What is the difference between the array methods map() and forEach()?MidJavaScript
map returns a new array of the callback's return values, the same length as the original. forEach returns undefined; it exists for side effects. Use map to transform data (it is chainable and works in JSX), forEach (or for...of) to do something for each item.
mapreturns a new arrayforEachreturns undefined- Neither breaks early
- Neither awaits async callbacks
- Neither can stop early. Use
for...ofwithbreak, orsome/every/find, which stop at the first match. - Neither awaits an async callback.
forEach(async ...)fires every call and moves on; usefor...ofwithawaitfor sequence, orPromise.all(items.map(async ...))for parallel. - Both skip holes in sparse arrays.
The trap: Using map only for its side effects builds an array nobody uses, and tells the reader you meant to transform data.
05Create a function pipe that performs left-to-right function composition by returning a function that accepts one argument.SeniorJavaScriptdemo
Show the answerHide the answer
Collect the functions with a rest parameter and return a function that runs a value through them with reduce: const pipe = (...fns) => (x) => fns.reduce((value, fn) => fn(value), x). compose is the same with reduceRight (right to left).
- Rest parameter +
reduce - Left-to-right vs
compose - Unary functions
- Identity when empty
compose(square, double, addOne)(3) is square(double(addOne(3))): (3 + 1) * 2 = 8, squared is 64. With no functions, pipe returns its input unchanged, because reduce starts from x.
- Each function takes one argument and returns one value, so the output of one fits the input of the next.
- An async version awaits each step:
(x) => fns.reduce(async (value, fn) => fn(await value), x).
06What is memoization?SeniorJavaScriptdemo
Caching a function's results by its arguments: the first call computes and stores the result, later calls with the same arguments return the stored value. It trades memory for time, and is only correct for pure functions. The classic win is recursive problems with overlapping subproblems, like Fibonacci.
- Cache results by arguments
- Only for pure functions
- Overlapping subproblems
- Cache key strategy
- Memory cost, LRU, WeakMap
- The key: one primitive argument maps directly to a
Mapkey. Several arguments need a combined key (JSON.stringify(args)for simple data) or nested maps. - Memory: an unbounded cache grows forever. Use an LRU limit, or a
WeakMapwhen the argument is an object, so entries can be garbage-collected. - React:
useMemoandmemomemoize per component instance, and only against the previous inputs. The React Compiler inserts this automatically.
The trap: Memoizing an impure function (one that reads the time, Math.random or mutable state) returns stale answers.
07What is recursion and when is it useful?SeniorJavaScriptdemo
A function that solves a problem by calling itself on a smaller version of it, until it reaches a base case it can answer directly. It fits data that is recursive itself (trees, the DOM, nested JSON, file systems, menus) and divide-and-conquer algorithms (merge sort, binary search), and it is often clearer than the loop with an explicit stack it replaces.
- Base case and recursive case
- Natural for trees and nested data
- Stack overflow risk
- Exponential repeated work, fixed by memoization
- No tail-call optimisation in practice
- Base case first: without one, the function recurses until
RangeError: Maximum call stack size exceeded. - Depth limit: the call stack holds roughly ten thousand frames, depending on the engine. Very deep data needs a loop with an explicit stack.
- Repeated work: naive recursive Fibonacci is O(2^n) because it recomputes the same values. Memoize, or build up iteratively.
- Proper tail calls are in the spec but only Safari implements them, so don't rely on them.
Asynchronous JavaScript
One thread, an event loop, and three generations of async code: callbacks, promises and async/await.
01What is the difference between synchronous and asynchronous code in JavaScript?MidJavaScriptdemo
Synchronous code runs line by line on the one main thread, and each line finishes before the next starts. Asynchronous work (timers, network, file I/O, user events) is started, and its callback runs later, once the current code has finished and the call stack is empty. Promise callbacks (microtasks) run before the next timer or event (macrotask).
- Single thread, run to completion
- The event loop
- Microtasks before macrotasks
- Blocking vs non-blocking
setTimeout(fn, 0)is not immediate
- The call stack runs synchronous code to completion. Nothing interrupts it.
- Web APIs / libuv do the waiting (timers, sockets, disk) outside the JavaScript thread.
- The task queue (macrotasks) holds callbacks from timers, events and I/O.
- The microtask queue holds promise reactions and
queueMicrotaskcallbacks. It is drained completely after every task, before rendering and before the next task.
The trap: A long synchronous loop blocks everything: clicks, rendering, timers. Async code does not make work faster; it lets the thread do other things while waiting. CPU-heavy work belongs in a Web Worker.
02What is a callback? Can you show an example using one?MidJavaScript
A callback is a function passed to another function, to be called by it later. Some run synchronously (array.map(fn), sort(compare)), others asynchronously when something happens (addEventListener, setTimeout, Node's fs.readFile). Callbacks were the original way to handle async results; promises are built on them.
- Function passed as an argument
- Synchronous and asynchronous callbacks
- Higher-order functions
- Inversion of control
- Error handling and nesting problems
- Inversion of control: you hand your code to someone else, who decides when, how often and with what to call it.
- Errors cannot be caught with a surrounding
try/catchonce the callback runs later; conventions like Node's error-first callbacks fill the gap. - Nesting dependent steps leads to the "pyramid of doom". Promises and
async/awaitflatten it.
03What are Promises?MidJavaScriptdemo
A Promise is an object standing for the eventual result of an asynchronous operation. It starts pending and settles once, either fulfilled with a value or rejected with a reason. You react with .then, .catch and .finally; each returns a new promise, so steps chain, and a thrown error skips ahead to the next .catch.
- Placeholder for a future value
- Pending, fulfilled, rejected
- Chaining returns new promises
- Error propagation to
.catch - Combinators
- Microtask timing
- Handlers always run asynchronously, as microtasks, even on an already-resolved promise.
- Returning a promise from
.thenwaits for it, which is what makes chains flat. - A rejection with no handler surfaces as an
unhandledrejectionevent (and crashes Node by default).
.then queues the next step as a microtask: count how many steps run before the timer.04In which states can a Promise be?JuniorJavaScriptdemo
Three: pending (not settled yet), fulfilled (it has a value) and rejected (it has a reason, usually an Error). Fulfilled and rejected together are called settled, and a promise settles only once: later resolve or reject calls are ignored.
- Pending, fulfilled, rejected
- Settled = fulfilled or rejected
- Settles exactly once
- Resolved vs fulfilled
- "Settled" is a description, not a fourth state.
- "Resolved" is not the same as "fulfilled": a promise resolved with another promise follows it and may still be pending, or end up rejected.
- There is no synchronous way to read a promise's state. You find out by attaching a handler.
05What is an async function?MidJavaScriptdemo
Show the answerHide the answer
A function that always returns a promise and can use await inside. Returning a value fulfils that promise, throwing rejects it. await pauses the function (not the thread) until the awaited promise settles, then resumes it as a microtask, so asynchronous code reads like synchronous code, with ordinary try/catch.
- Always returns a promise
awaitpauses the function, not the threadtry/catchfor rejections- Sequential vs parallel awaits
- Syntax over promises
The trap: await in a loop runs the iterations one after another. When the steps don't depend on each other, start them all and await Promise.all(...).
main leave the call stack at await and come back later as a microtask.06What is event-driven programming?SeniorJavaScriptdemo
A style where the program's flow is driven by events (clicks, messages, timers, I/O completing) rather than a fixed sequence. Code registers handlers for event types, and an emitter calls every handler subscribed to an event when it happens. The browser's DOM events and Node's EventEmitter are both built this way; it keeps the sender and the receivers decoupled.
- Flow driven by events
- Emitters and handlers (pub/sub)
- Decoupling
- Unsubscribe to avoid leaks
- DOM events and Node EventEmitter
- Publish/subscribe: the emitter doesn't know who is listening, and emitting with no listeners is not an error.
- Unsubscribe: returning an
offfunction (asaddEventListenerpairs withremoveEventListener) is what lets components clean up and avoid leaks. - In the browser:
new CustomEvent("cart:add", { detail })withdispatchEventgives the same pattern on DOM nodes or anEventTarget.
07What is debouncing?MidJavaScriptdemo
Delaying a function until calls to it have stopped for a set time. Every call resets the timer, so a burst of calls produces one run, after the burst. It is the standard fix for search-as-you-type, autosave and resize handlers. Throttling is the cousin: run at most once per interval, even during the burst.
- Wait until calls stop
- Reset the timer on each call
- Search inputs and resize
- Debounce vs throttle
- Leading vs trailing edge
Interview line: Debouncing a search box also needs the latest request to win: the debounce reduces requests, it doesn't stop an old response from arriving last.
JavaScript in the browser
Events, cross-origin requests, content types and why UI libraries exist: JavaScript where it meets the page and the network.
01What is event delegation and why is it useful? Can you show an example of how to use it?MidJavaScriptHTMLdemo
Instead of a listener on every child, put one listener on a common ancestor and use event.target to find which child the event came from. It works because most events bubble up through their ancestors. One listener uses less memory, and it also handles children added later, without re-binding.
- Relies on event bubbling
- One listener on an ancestor
event.targetvscurrentTarget- Works for dynamically added elements
- Non-bubbling events
event.targetis the element that was actually clicked (possibly a<span>inside the button), so match withclosest(selector), not===.event.currentTargetis the element the listener is attached to: here, the list.focus,blur,mouseenterandmouseleavedon't bubble. Delegate withfocusin/focusoutandmouseover/mouseoutinstead.- A child calling
event.stopPropagation()stops the event from reaching the delegated listener.
React uses delegation itself: since React 17 it attaches one listener per event type at the root container and dispatches to your onClick props from there.
02What is CORS?MidJavaScriptSecuritydemo
Cross-Origin Resource Sharing: the browser mechanism that lets a server relax the same-origin policy. By default, a page's script cannot read responses from another origin (scheme + host + port). With CORS, the server lists which origins, methods and headers it accepts in Access-Control-Allow-* response headers, and the browser enforces them. Requests that are not "simple" are first checked with an OPTIONS preflight.
- Same-origin policy and origins
Access-Control-Allow-Origin- Preflight
OPTIONSrequests - Credentials rules
- Enforced by browsers, not servers
- Credentials (cookies,
fetch(url, { credentials: "include" })) needAccess-Control-Allow-Credentials: trueand an exact origin:*is not allowed with them. - CORS is enforced by the browser.
curl, servers and Postman ignore it. It protects users' data in other origins, not your API: you still need authentication and CSRF protection. - A CORS error is a server configuration problem;
mode: "no-cors"only gets you an opaque response you cannot read.
The trap: Reflecting any Origin back with credentials allowed is equivalent to turning the same-origin policy off for logged-in users.
03What is a MIME type and what is it used for?MidJavaScriptHTML
A MIME type (media type) labels what kind of data a stream of bytes is, as type/subtype with optional parameters: text/html; charset=utf-8, application/json, image/png. Servers send it in the Content-Type header and clients in Accept; browsers use it to decide whether to render, run, or download a response.
- type/subtype format
Content-TypeandAcceptheaders- Browsers decide how to handle content by it
nosniff
- The name comes from Multipurpose Internet Mail Extensions, the email standard that introduced it.
- Browsers refuse to run a module script, or a stylesheet, served with the wrong type.
X-Content-Type-Options: nosniffstops the browser from guessing ("sniffing") a type from the bytes, which blocks some content-injection attacks.- In the browser:
blob.type,file.type,<input type="file" accept="image/*">.
04What is the purpose of JavaScript UI libraries/frameworks like React, Vue, Angular, Hyperapp, etc?SeniorJavaScriptReact
To keep the UI in sync with application state without hand-written DOM manipulation. You describe what the UI should look like for a given state, and the library works out and applies the DOM changes when the state changes. On top of that they give a component model for reuse and composition, and an ecosystem for routing, data loading and server rendering.
- Sync UI with state declaratively
- Avoid manual DOM manipulation
- Reusable components
- Virtual DOM or compiled updates
- Ecosystem: routing, SSR, data
- State is the single source of truth: with jQuery-era code, state often lived in the DOM (classes,
data-attributes) and drifted out of sync. - Components package markup, behaviour and styling together and compose like functions.
- Rendering strategy differs: React and Vue diff a virtual DOM, Svelte and Solid compile to targeted DOM updates, Angular uses change detection over templates.
Interview line: The core value is that the view becomes a function of state, so I change data and the library does the bookkeeping.
05What is a virtual DOM and why is it used in libraries/frameworks?SeniorJavaScriptReactdemo
A virtual DOM is a lightweight tree of plain JavaScript objects describing what the UI should be. On each update, the library renders a new tree, compares (diffs) it with the previous one, and applies only the differences to the real DOM. It lets you write code as if the whole UI re-renders from scratch, while the browser only sees the minimal changes.
- In-memory tree of plain objects
- Diffing and reconciliation
- Minimal real DOM updates
- Keys for lists
- Not inherently faster than direct DOM updates
- Virtual DOM is not the shadow DOM
- Reconciliation: React compares element types first. A different type (
div→section) replaces the whole subtree; the same type keeps the DOM node and updates changed attributes. - Lists are matched by
key, so items can move without being recreated. - Batching: many state changes produce one diff and one round of DOM writes.
- Not free: diffing costs CPU. A virtual DOM is not faster than precise hand-written DOM updates; it is faster than naive re-rendering and much easier to write correctly. Svelte and Solid skip it by compiling updates ahead of time.
- React's Fiber architecture splits rendering into interruptible units of work, so updates can be prioritised (transitions).
The trap: The "virtual DOM" is not a copy of the browser DOM, and it is not a shadow DOM (that is an unrelated Web Components feature for encapsulation).
Coding challenges
Small functions to write on the spot, plus the vocabulary for talking about how fast they are.
01Create a function batches that returns the maximum number of whole batches that can be cooked from a recipe.MidJavaScriptdemo
Show the answerHide the answer
For every ingredient in the recipe, the number of batches it allows is Math.floor(available / required), and 0 when it is missing. The answer is the smallest of those numbers: the scarcest ingredient decides.
- Ratio per ingredient, floored
- Minimum across ingredients
- Missing ingredient → 0
- Edge cases: empty recipe, NaN
- Iterate the recipe, not the pantry: extra ingredients in
availabledon't matter, missing ones do. ?? 0turns a missing ingredient into 0 batches. Without it,undefined / 5isNaNandMath.minreturnsNaN.- Edge case to mention: an empty recipe gives
Math.min()=Infinity. Decide what that should mean and say so. - It is O(r) for r recipe ingredients. Spreading into
Math.minis fine here; for very large arrays,reduceavoids argument-count limits.
02Create a function that masks a string of characters with # except for the last four (4) characters.MidJavaScriptdemo
Show the answerHide the answer
Take the last four characters with slice(-4) and pad them back to the original length with padStart(length, "#"). Strings of four characters or fewer come back unchanged.
slice(-4)+padStart- Short strings unchanged
- Unicode code units vs code points
- One-liner but explain it
The last call returns an empty string. One subtlety worth raising: length and slice count UTF-16 code units, so emoji and some accented characters count as two. Splitting into code points with [...text] fixes that.
03Generate an array, containing the Fibonacci sequence, up until the nth term.MidJavaScriptdemo
Start from 0 and 1, and make each next term the sum of the previous two, until the array has n terms. A loop does it in O(n) time; the naive recursive definition is O(2^n) and is the classic reason to memoize.
- Iterative O(n) solution
- Base terms and edge cases
- Naive recursion is exponential
- BigInt for large terms
- Clarify the start: some definitions begin at 1, 1. Ask, or state your assumption.
- Handle
nof 0 and 1 without special cases by building up from an empty array, as above. - Terms pass
Number.MAX_SAFE_INTEGERat the 79th; useBigInt(0n,1n) for exact large terms.
04Given an array of words, write a method to output matching sets of anagrams.MidJavaScriptdemo
Show the answerHide the answer
Anagrams share the same letters, so they share the same letters sorted. Use the sorted word as a key in a Map, push each word into its key's group, and keep the groups with more than one word.
- Canonical key: sorted letters
- Group with a Map or object
- Filter groups of size > 1
- Complexity and the counting alternative
- Complexity: O(n · k log k) for n words of length k, because of the sort per word.
- A letter-count key (
a1e1r1s1t1) avoids sorting and makes it O(n · k). Object.groupBy(words, keyFn)(ES2024) does the grouping in one call.stateis left out: it has no partner.
05What is Big O Notation?SeniorJavaScriptdemo
A way to describe how an algorithm's running time (or memory) grows as its input grows, keeping only the dominant term and dropping constants. O(n) means doubling the input roughly doubles the work; O(n²) means it quadruples it. It describes scalability, usually for the worst case, not the speed on a given input.
- Growth rate relative to input size
- Worst case, dominant term, no constants
- Common classes with examples
- Time vs space
- Practical limits of the notation
- Drop constants and smaller terms: 3n² + 10n + 7 is O(n²).
- Space complexity counts extra memory the same way: the
Setversion trades O(n) memory for time. - Big O is an upper bound; Big Θ is a tight bound and Big Ω a lower bound. In interviews, "Big O" usually means the tight worst case.
The trap: For small inputs, constants dominate: a simple O(n²) loop over 20 items can beat a clever O(n log n) solution. Big O says what happens as n gets large.
React fundamentals
Elements, components, JSX and where state lives: the vocabulary every other React answer builds on.
01What is the difference between an element and a component in React?JuniorReactJavaScript
An element is a plain, immutable object describing what to render: { type, props }, created by JSX or createElement. A component is a function (or class) that takes props and returns elements. <Greeting name="Ada" /> is an element whose type is the Greeting component.
- Element: plain object
{ type, props } - Component: function returning elements
- Elements are immutable
- Capitalisation in JSX
- Elements are cheap descriptions: creating one does not render anything or call the component. React calls
Greetinglater, when it renders that element. - Host elements have a string type (
"h1") and become DOM nodes; component elements have a function type and expand into more elements. - Components are capitalised in JSX, which is how the compiler tells
<Greeting />(a variable) from<greeting />(a tag name).
Interview line: A component is the recipe; an element is one order placed from it, with specific props.
02Why does React use className instead of class like in HTML?MidReactHTML
JSX compiles to JavaScript, and React's props follow the DOM's property names rather than HTML's attribute names. The DOM property is element.className because class is a reserved word in JavaScript. The same logic gives htmlFor instead of for, and camelCase names like tabIndex and onClick.
- JSX is JavaScript
classis a reserved word- Props follow DOM property names
htmlForand camelCase attributes
classis reserved, soconst { class } = propswould not compile.classNameis safe to destructure.- Using
classin JSX gets a warning from React ("Invalid DOM propertyclass. Did you meanclassName?"). - Custom elements (Web Components) are the exception: React 19 passes props to them as attributes or properties, and
classis accepted there. - Other frameworks chose differently: Vue, Svelte, Solid and Preact accept
class.
03How do you write comments inside a JSX tree in React?JuniorReact
Between children, a comment has to be a JavaScript comment inside braces: {/* like this */}. Inside a tag, between props, ordinary // and /* */ comments work. Plain HTML comments (<!-- -->) are not valid JSX.
{/* */}between children//and/* */between props- No HTML comments in JSX
The trap: Without braces, /* text */ between children renders as literal text on the page.
04What are fragments?SeniorReactJavaScript
A fragment groups several children without adding a DOM node. A component returns one value, so returning siblings needs a wrapper; <>...</> (or <Fragment>) is a wrapper that disappears from the output. It matters where an extra <div> would break the HTML or the layout: table rows, list items, flex and grid children.
- Group children without a DOM node
- Short syntax vs
Fragmentwithkey - Tables, lists and layout
- Valid HTML
- The short syntax
<></>cannot take props. To give a fragment akey(when mapping pairs of elements), write<Fragment key={...}>. - A
<div>inside<tr>or<ul>produces invalid HTML; inside a grid or flex container it becomes the item instead of its children. - An array of elements is another way to return siblings, but every item then needs a key.
05What is the children prop?SeniorReactJavaScript
children is the prop holding whatever is written between a component's opening and closing tags: text, elements, an array of them, or even a function. It is what makes composition work: layouts, cards, modals and providers wrap content they know nothing about. Type it as ReactNode.
- Content between the tags
- Composition
ReactNodetype- Multiple slots via props
- Render props
- Composition over configuration: rather than
<Card body="..." link="..." />with a prop per piece, the caller passes any content. - Several slots are just more props of type
ReactNode:<Layout sidebar={<Nav />}>main content</Layout>. - Render props:
childrencan be a function the component calls with data, as in<DataLoader>{(data) => <List items={data} />}</DataLoader>. Hooks have replaced most uses. - The
ChildrenandcloneElementAPIs exist but are fragile; prefer passing data through props or context.
The trap: Children passed from a parent are created by that parent. A wrapper that re-renders does not re-render them, which is a simple way to keep expensive subtrees from re-rendering.
06What are inline conditional expressions?MidReactJavaScriptdemo
Ways to choose what to render inside JSX, which only accepts expressions: the ternary cond ? <A /> : <B /> for either/or, cond && <A /> for show-or-nothing, and null to render nothing. For anything longer, compute a variable before the return or return early.
- Ternary and
&& - Rendering
null - The
0 &&pitfall - Early returns and variables for complex cases
false,null,undefinedandtruerender nothing.0,NaNand""are values React does render (an empty string renders nothing visible but still counts as a text node).- Nested ternaries get hard to read quickly. Extract a component, or map a status to a component with an object lookup.
- A component that returns
nullis still mounted: its state and effects stay alive. Rendering{show && <Panel />}in the parent unmounts it instead.
&& version renders.07What is a key? What are the benefits of using it in lists?MidReactJavaScriptdemo
A key is a stable identity for an element among its siblings. When a list re-renders, React matches old and new children by key, so it can keep each item's DOM and state, move items instead of recreating them, and only insert or remove what changed. Use an id from the data; the array index breaks when items are inserted, removed or reordered.
- Identity among siblings
- Matching during reconciliation
- Preserves state and DOM
- Stable ids, not indexes or random values
- Key changes remount
- Keys only need to be unique among siblings, not globally.
- The index is acceptable for static lists that never reorder and hold no state.
- Changing an element's key on purpose remounts it, which is the simplest way to reset a component's state (
<Form key={userId} />). keyis not passed to the component as a prop.
08What is a stateful component in React?JuniorReactJavaScript
A component that owns state: data that changes over time and is kept by React between renders. In a function component that is useState or useReducer (in a class, this.state and setState). Updating the state makes React re-render the component with the new value.
- Owns data that changes
useState/useReducer(orthis.state)- Updates trigger re-renders
- Per-instance, snapshot semantics
- State is private to each instance: two
<LikeButton />s count separately. - State is a snapshot per render: the handler sees the value of the render it was created in, which is why the updater form exists.
- Keep state minimal: anything you can compute from props or other state should be computed during render, not stored.
09What is a stateless component?JuniorReactJavaScript
A component with no state of its own: it renders purely from its props, so the same props always give the same output. They are the easiest components to reuse and test. Before hooks, "stateless functional component" meant any function component; today a function component can have state, so "stateless" or "presentational" describes what it does, not how it is written.
- Renders from props only
- Same props, same output
- Reusable and testable
- Historical name for function components
A common split is a stateful container that fetches and holds data, rendering stateless pieces that only display it. With hooks, the container's logic often moves into a custom hook instead.
Interview line: I try to push state up to where it is needed and keep the leaves stateless; they become trivial to test and to memoize.
10What does lifting state up in React mean?JuniorReactdemo
When two components need the same changing data, move the state out of them into their closest common parent, and pass the value and a function to change it down as props. There is then one source of truth, and the components cannot disagree.
- Shared state in the closest common parent
- Pass value and setter as props
- Single source of truth
- Controlled children
- Derive instead of duplicating
- The inputs became controlled: their value comes from props, and they report changes instead of storing them.
- Only one temperature is stored; the other is derived during render, so they can never drift apart.
- If the common parent ends up far away and many layers pass the props through, that is the point to consider context or a store.
Events, refs and the DOM
How React handles events, how to reach a real DOM node when you need one, and how to render outside your parent.
01What is the difference between HTML and React event handling?MidReactJavaScriptHTMLdemo
In HTML, a handler is a lowercase attribute holding a string of code (onclick="save()"), and return false can cancel the default action. In React, handlers are camelCase props holding a function (onClick={save}), you must call event.preventDefault() explicitly, and the handler receives a cross-browser SyntheticEvent wrapping the native one.
- camelCase props
- Functions, not strings
preventDefault, notreturn false- SyntheticEvent
- Delegation at the root
onChangeon every input
Since React 17, React attaches its listeners to the root container rather than document, and event pooling (reusing event objects) is gone, so reading the event asynchronously is safe.
02How do you pass an argument to an event handler or callback?MidReactJavaScript
Wrap the call in an arrow function, onClick={() => remove(item.id)}, so it runs on click instead of during render. Alternatives: remove.bind(null, item.id), a curried handler onClick={removeHandler(item.id)} where removeHandler returns a function, or a data- attribute read from event.currentTarget.dataset.
- Arrow function wrapper
- Don't call the handler during render
bindand curryingdata-attributes- New functions per render rarely matter
The trap: onClick={remove(item.id)} calls remove while rendering and passes its return value (probably undefined) as the handler. If remove updates state, the result is an infinite render loop.
- Inline arrows create a new function each render. That is fine almost always; it only matters when passing to a memoized child, where
useCallbackor the dataset version keeps the reference stable. - To keep the event too:
onClick={(event) => remove(item.id, event)}.
03What are refs in React? When should they be used?SeniorReactJavaScriptdemo
A ref is a mutable box, { current }, that React keeps for the lifetime of a component and that does not cause a re-render when it changes. Use refs for the DOM (focus, scroll, measuring, media playback, integrating a non-React widget) and for values that must survive renders without being displayed (timer ids, the previous value, an AbortController). Anything shown on screen belongs in state.
- Mutable
{ current }that persists - Changing it doesn't re-render
- DOM access: focus, measure, scroll
- Instance values: timers, previous value
refas a prop in React 19- Not during render
- React 19:
refis an ordinary prop for function components, soforwardRefis no longer needed:function Input({ ref, ...props }) { return <input ref={ref} {...props} /> }. - Callback refs receive the node when it is attached and can return a cleanup function (React 19) that runs when it is detached.
useImperativeHandleexposes a limited API (focus,scrollToEnd) instead of the whole DOM node.
The trap: Don't read or write ref.current during rendering (other than lazy initialisation). The value isn't tracked, so the UI won't update, and concurrent rendering can run a render that is thrown away. Read refs in effects and event handlers.
04Which is the preferred option between callback refs and findDOMNode()?SeniorReactJavaScriptdemo
Callback refs (or ref objects). findDOMNode was deprecated in StrictMode for years and removed in React 19. It reached through a component to whatever DOM node it happened to render first, which broke encapsulation, returned only one node, didn't work with fragments and was incompatible with concurrent rendering.
findDOMNodeis removed in React 19- It broke encapsulation and concurrent rendering
- Callback refs run on attach and detach
- Ref objects for later access
- Pass refs explicitly to children
- A ref object (
useRef) is enough when you only need the node later, in an effect or a handler. - A callback ref is right when you need to act the moment the node appears or changes: measure it, observe it, focus it.
- To reach a DOM node inside a child component, the child passes
refto it: as a prop in React 19, throughforwardRefbefore that.
05What are portals in React?SeniorReactJavaScriptdemo
createPortal(children, domNode) renders children into a different DOM node, typically document.body, while they stay in the same place in the React tree. So a modal or tooltip escapes its parent's overflow: hidden and stacking context, but still receives context and its events still bubble to its React ancestors.
- Render into another DOM node
- Stays in the React tree
- Escapes overflow and z-index
- Events bubble through React ancestors
- Focus management and native
<dialog>
- Use for modals, popovers, tooltips, dropdowns and toasts that must not be clipped by an ancestor.
- Event bubbling follows the React tree: a click inside the portal reaches an
onClickon the component that rendered it, even though the DOM nodes are elsewhere. - Accessibility is your job: focus moving into the dialog, Escape to close, focus returning to the trigger, the background made inert.
- Native alternatives now cover many cases:
<dialog>withshowModal()renders in the top layer, as does thepopoverattribute.
Interview line: A portal changes where the DOM goes, not where the component lives: context, state and events behave as if it never moved.
Lifecycle, classes and patterns
How a component is born, updates and dies, in class and hook terms, plus the patterns built on top: boundaries, context and higher-order components.
01What are the different phases of the component lifecycle in React?MidReactdemo
Mounting: the component renders for the first time and its DOM is inserted; then effects run. Updating: new props, state or context cause a re-render; React commits the changes, then runs the cleanup and setup of effects whose dependencies changed. Unmounting: the component is removed and every effect cleans up. Class components add an error handling phase for boundaries.
- Mounting, updating, unmounting (+ error handling)
- Render vs commit phases
- Effects run after commit; cleanup before the next run
- Hooks: synchronisation, not events
- StrictMode double mount
- Render phase: React calls your component to compute elements. It must be pure; React may call it more than once or discard the result.
- Commit phase: React applies the DOM changes, attaches refs, then runs
useLayoutEffect(before paint) and lateruseEffect(after paint). - With hooks, think in synchronisation rather than lifecycle events: an effect keeps something in sync with its dependencies, starting and stopping as they change.
- In development, StrictMode mounts, unmounts and remounts every component once, to prove the cleanup undoes the setup.
02What are the lifecycle methods in React?MidReactdemo
Methods React calls on a class component at each phase: constructor, render, componentDidMount, componentDidUpdate, componentWillUnmount, plus the less common shouldComponentUpdate, getDerivedStateFromProps, getSnapshotBeforeUpdate, and for error boundaries getDerivedStateFromError and componentDidCatch. In function components, hooks cover the same ground.
- Mount, update, unmount methods
shouldComponentUpdatefor performance- Error boundary methods
- Hook equivalents
- Legacy
UNSAFE_methods
The class version spreads one concern (the connection) across three methods; the hook version keeps it in one useEffect with [roomId]. That is the main reason hooks replaced classes for new code.
The trap: componentWillMount, componentWillReceiveProps and componentWillUpdate were renamed UNSAFE_ in React 16.3 because they misbehave with async rendering. Don't use them in new code.
03What is the purpose of callback function as an argument of setState?MidReactJavaScript
In a class component, this.setState(update, callback) runs callback after the state has been applied and the component has re-rendered. It exists because setState does not update this.state immediately: updates are batched. Function components have no such callback; you react to the new state in an effect, or simply use the value you just computed.
- Runs after the update is committed
setStateis batched / asynchronous- Updater function for derived updates
- Hooks: use the computed value or an effect
- When the next state depends on the previous one, pass an updater:
this.setState((s) => ({ count: s.count + 1 })), orsetCount((c) => c + 1)with hooks. - If something really must happen after the DOM updates (scroll to the new item), put it in
useEffect(oruseLayoutEffect) keyed on that state.
04How do you ensure methods have the correct this context in React component classes?SeniorReactJavaScriptdemo
Class methods aren't bound to the instance, so onClick={this.handleClick} calls the method with this undefined. Fix it by binding in the constructor (this.handleClick = this.handleClick.bind(this)), by declaring the method as an arrow-function class field (handleClick = () => {...}, the usual choice), or by wrapping at the call site (onClick={() => this.handleClick()}).
- Methods are not auto-bound
- Bind in the constructor
- Class field arrow functions
- Inline arrows and their cost
- Function components avoid it
- Constructor
bind: one bound copy per instance; verbose. - Class field arrow: one function per instance,
thiscaptured lexically; the common style. - Inline arrow: a new function each render; fine unless passed to a memoized child.
Interview line: This is a JavaScript this problem, not a React one, and function components sidestep it entirely: there is no this.
this.handleClick as a prop does.05What are error boundaries in React?SeniorReactdemo
A class component that catches errors thrown while rendering any component below it, and shows a fallback UI instead of unmounting the whole app. It implements static getDerivedStateFromError (to switch to the fallback) and usually componentDidCatch (to log). It does not catch errors in event handlers, async code, server rendering or in itself.
- Catches render-time errors in children
getDerivedStateFromError+componentDidCatch- Not event handlers or async code
- Must be a class
- Granular placement
- Without any boundary, an uncaught render error unmounts the whole root.
- Place boundaries around independent areas (a widget, a route), so one failure doesn't blank the page.
- There is still no hook equivalent; the
react-error-boundarypackage wraps the class and adds reset helpers. React 19 addsonCaughtErrorandonUncaughtErroroptions tocreateRootfor reporting.
06What is context?SeniorReactJavaScriptdemo
Context passes a value to every component below a provider without threading it through props at each level (prop drilling). Create it with createContext(defaultValue), provide it with <ThemeContext value={theme}> (React 19; earlier <ThemeContext.Provider>), and read it with useContext(ThemeContext) or use(ThemeContext). Every consumer re-renders when the provided value changes.
- Avoids prop drilling
createContext, provider,useContext- Default value without a provider
- All consumers re-render on change
- Memoize the value
- Not a state manager by itself
- Good for data many components need and that changes rarely: theme, locale, the signed-in user, a router, a store instance.
- Not a state manager: context only delivers a value. Pair it with
useStateoruseReducerabove it. - Re-renders: React compares the provided value with
Object.is. An inline object (value={{ user, setUser }}) is new every render and re-renders all consumers; memoize it, or split state and setters into two contexts. - Without a provider above it,
useContextreturns the default value fromcreateContext.
The trap: Reaching for context to avoid passing a prop two levels down usually makes code harder to follow. Composition (passing children) often removes the drilling instead.
07What are higher-order components?SeniorReact
A higher-order component (HOC) is a function that takes a component and returns a new component that renders it with extra behaviour or props: withRouter(Profile), Redux's connect(...)(List), withAuth(Page). It is the component version of a higher-order function. Custom hooks have replaced most HOCs, but they still appear in older code and some libraries.
- Function from component to component
- Reuses cross-cutting logic
- Examples:
connect,withRouter - Define outside render
- Mostly replaced by hooks
- Create HOCs outside components: calling
withLogging(Profile)inside a render makes a new component type every time, which remounts the subtree and loses its state. - Pass all props through and set a
displayNameso DevTools shows something readable. - Downsides: "wrapper hell" in the tree, props colliding between layers, and static methods and refs that don't pass through automatically.
- The hook equivalent is usually simpler:
const user = useAuth()instead ofwithAuth(Page).
08How to apply prop validation in React?SeniorReact
Today, with TypeScript: describe the props as a type, and the compiler checks every use of the component before the code runs. The old runtime approach, Component.propTypes with the prop-types package, logged warnings in development; React 19 removed those checks entirely, so propTypes are silently ignored. For data from outside the program (APIs, forms, URLs), validate at runtime with a schema library.
- TypeScript prop types
propTypesremoved in React 19- Default parameters instead of
defaultProps - Runtime validation for external data (zod, valibot)
- React 19 also ignores
defaultPropson function components; use default parameter values, as above. - TypeScript checks are erased at build time and cost nothing at runtime, but they cannot see inside
JSON.parseorresponse.json(), which areany.
The trap: Quoting PropTypes.string.isRequired as the answer in 2026 dates you. Mention it as history, then answer with TypeScript.
CSS
The box model, the cascade and the layout and naming tools that come up in front-end screens.
01Describe the layout of the CSS Box Model and briefly describe each component.MidCSSdemo
Every element is a rectangular box of four layers, from the inside out: content (the text or image, sized by width/height), padding (space inside the border, which shows the background), border, and margin (transparent space outside the border, separating the box from its neighbours). box-sizing decides whether width measures the content alone (content-box, the default) or content + padding + border (border-box).
- Content, padding, border, margin
box-sizing: content-boxvsborder-box- Margin collapsing
- Outline takes no space
- Most resets apply
*, *::before, *::after { box-sizing: border-box; }so widths mean what they say. - Vertical margins collapse: two adjacent block margins of 24px and 16px give a 24px gap, not 40px. Flex and grid items don't collapse.
outlineandbox-shadoware drawn outside the border but take no space in the layout.- Inline elements (
<span>) ignorewidth,heightand vertical margins.
width measures.02Can you describe how CSS specificity works?SeniorCSSdemo
When several rules set the same property on an element, the most specific selector wins. Specificity is a three-part score (A, B, C): A counts IDs; B counts classes, attributes and pseudo-classes; C counts element types and pseudo-elements. Scores are compared column by column from the left, so one ID beats any number of classes. Equal scores fall back to source order: the later rule wins.
- (ID, class, type) columns
- Compared left to right
- Inline styles and
!important - Source order for ties
:is/:not/:hasvs:where- Cascade layers
- Inline styles (
style="...") beat any selector. !importantmoves a declaration to a higher layer of the cascade; among important declarations, specificity applies again.- Before specificity, the cascade checks origin and importance, then cascade layers (
@layer): a rule in a later layer wins over a more specific rule in an earlier one. :not(),:is()and:has()count as their most specific argument;:where()lets libraries ship styles that are trivial to override.
The trap: Fighting specificity with more IDs or !important escalates. Keep selectors flat (one class, as in BEM), and use @layer to order third-party, base and component styles.
03What is the difference between + and ~ sibling selectors?SeniorCSSdemo
Both match elements that come after another element under the same parent. A + B (adjacent sibling) matches a B that comes immediately after an A. A ~ B (general sibling) matches every B that comes anywhere after an A. Neither looks backwards or into other parents.
+immediately next sibling~all following siblings- Same parent, forward only
:has()for the reverse
To style an element based on what comes after it (a "previous sibling" selector), use :has(): h2:has(+ p) matches an h2 immediately followed by a p.
04What is the difference between em and rem units?MidCSSdemo
Both are relative to a font size. rem is relative to the root element's font size (<html>, 16px by default), so 1.5rem means the same everywhere. em is relative to the current element's font size (and, for font-size itself, the parent's), so it compounds when elements are nested.
rem: root font sizeem: element's (parent's for font-size)emcompounds when nested- Respect user font settings
- When to use each
remfor type scale and spacing: predictable, and it follows the user's browser font-size setting.emfor things that should scale with their component's text: padding and icon sizes in buttons, so a.button.largegrows as a whole.- Setting
html { font-size: 62.5% }to make1rem = 10pxworks but fights user settings; most teams now keep 100% and think in rems. - Media queries in
emorremuse the browser's default font size, not your root style.
em boxes compound, rem boxes don't.05Using flexbox, create a 3-column layout where each column takes up a col-{n} / 12 ratio of the container.JuniorCSSdemo
Show the answerHide the answer
Make .row a flex container and give each column a flex-grow equal to its n, with a zero basis: .col-2 { flex: 2 }, .col-7 { flex: 7 }, .col-3 { flex: 3 }. The shorthand flex: n means n 1 0%, so the space is shared in the ratio 2 : 7 : 3, which here is exactly n/12.
display: flexon the rowflex: nsets the grow ratioflex: n=n 1 0%- Gap and basis caveats
- Grid alternative
flex-grow alone shares the remaining space, and only matches n/12 when the columns add up to 12. A grid-system answer that fixes each column to n/12 of the row regardless of its siblings sets the basis instead:
- With
gap, the percentage version overflows: subtract the gaps or use theflex-growversion, which absorbs them. - Content can stop an item from shrinking below its minimum content width;
min-width: 0on the column fixes overflowing text. - CSS Grid says the same thing more directly:
grid-template-columns: 2fr 7fr 3fr, orrepeat(12, 1fr)withgrid-column: span n.
06What is a focus ring? What is the correct solution to handle them?SeniorCSSAccessibilitydemo
The focus ring is the outline a browser draws around the focused element. Keyboard users depend on it to know where they are, so removing it (outline: none) without a replacement fails WCAG. Style it with :focus-visible, which the browser applies when focus should be visible (keyboard navigation, text inputs) and skips for a mouse click on a button.
- Visible indicator of keyboard focus
- Never remove without a replacement
:focus-visibleoutlineoverbox-shadowfor forced colors- WCAG focus criteria
:focus-visibleis supported in every modern browser (since 2022), so the old JavaScript polyfill is no longer needed.- Use
outlinerather thanbox-shadow: Windows high-contrast (forced colors) mode keeps outlines and removes shadows. - WCAG 2.2 adds Focus Not Obscured (2.4.11): a sticky header must not hide the focused element. Make the ring contrast at least 3:1 with its background.
:focus-withinstyles a container while anything inside it has focus.
07Can you name the four types of @media properties?JuniorCSSdemo
The four media types are all (every device, the default), print (printed pages and print preview), screen (anything with a screen) and speech (speech synthesisers). Most real queries combine a type with media features, such as width, orientation, hover and prefers-color-scheme.
all,print,screen,speech- Media features
- Range syntax
- User-preference queries
- Container queries
- Older types (
tv,handheld,projection,aural, ...) are deprecated and match nothing. speechis defined but browsers and screen readers don't apply it; don't rely on it for accessibility.- The range syntax
(width >= 48rem)replaces(min-width: 48rem)and is supported everywhere current. - For component-level responsiveness, container queries (
@container) respond to the parent's size instead of the viewport's.
08What are the advantages of using CSS sprites and how are they utilized?MidCSSdemo
A sprite sheet combines many small images into one file. Each element shows its slice by setting the sheet as background-image and moving it with background-position, inside a box the size of one icon. One file means fewer HTTP requests and no flicker on hover, because every state is already loaded.
- Fewer HTTP requests
background-positionto select- Hover states preloaded
- HTTP/2 reduces the benefit
- SVG
<symbol>sprites today
- Why it mattered: under HTTP/1.1, browsers opened about six connections per host and every request paid a round trip. HTTP/2 and HTTP/3 multiplex many requests on one connection, so the gain is now much smaller.
- Downsides: editing the sheet means regenerating it and its coordinates; one changed icon invalidates the cache for all of them; sprites as backgrounds have no
alttext. - Today: SVG icons are the common choice, either inline or as an SVG sprite of
<symbol>s used with<svg><use href="#search"/></svg>, which scale crisply and takecurrentColor. - For high-density screens, ship a 2× sheet and set
background-sizeto the 1× size.
background-position changes.09What is CSS BEM?JuniorCSS
BEM (Block, Element, Modifier) is a class-naming convention: .block for a standalone component, .block__element for a part of it, and .block--modifier (or .block__element--modifier) for a variant. Every selector is a single class, so specificity stays flat, names don't collide, and the markup says how pieces relate.
- Block, Element, Modifier
__and--separators- Single-class, flat specificity
- Avoids naming collisions
- Alternatives: CSS Modules, utility CSS
- Elements belong to the block, not to each other:
.search__icon, never.search__button__icon. - A modifier is used together with the base class:
class="search search--compact". - The benefit is predictability in large shared stylesheets. CSS Modules, scoped styles,
@scopeand utility-first CSS (Tailwind) solve the same collision problem in other ways, which is why BEM is less common in component-based apps.
Interview line: BEM trades verbose class names for flat specificity and zero guessing about where a style applies.
10What are the advantages of using CSS preprocessors?JuniorCSS
Preprocessors like Sass, Less and Stylus add programming features to CSS and compile to plain CSS: variables, nesting, mixins, functions, loops, maths, and partials combined into one file. They made large stylesheets easier to organise and keep consistent. Native CSS has since gained custom properties, nesting, color-mix(), calc() and cascade layers, so many projects now need much less of them.
- Variables, nesting, mixins, functions, partials
- Compile to CSS
- Maintainability and DRY
- Native CSS now covers much of it
- Static variables vs custom properties
- Sass variables are static, resolved at build time; custom properties are live: they cascade, can change per element or media query, and can be set from JavaScript.
- Mixins and loops still have no direct native equivalent, which is where Sass keeps an edge in design-system code.
- PostCSS is a different tool: plugins that transform standard CSS (autoprefixing, future syntax), rather than a new language.
HTML and the document
Markup, the DOM it becomes, how scripts and caches load it, and where the specification ends and the browser begins.
01What is the DOM?MidHTMLJavaScriptdemo
The Document Object Model is the tree of objects a browser builds from an HTML document, and the API for reading and changing it. Each element, piece of text and comment becomes a node with a parent and children; JavaScript uses the DOM to find nodes, change their content, attributes and styles, and listen for events, and the browser re-renders to match.
- Tree of nodes built from HTML
- API for JavaScript
- Not the same as the source
- Node types, elements vs nodes
- Live vs static collections
- The DOM is not the HTML source: the parser fixes broken markup (adds
<tbody>, closes tags), and scripts change it after load. DevTools' Elements panel shows the DOM, not the source. - Node types:
Document,Element,Text,Comment,DocumentFragment.childrenlists element children only;childNodesincludes text and comments. - Live vs static:
getElementsByClassNamereturns a live collection that updates;querySelectorAllreturns a static snapshot. - Cost: reading layout (
offsetHeight) right after writing styles forces a synchronous layout. Batch reads and writes.
02What is the purpose of the alt attribute on images?JuniorHTMLAccessibilitydemo
alt is the image's text alternative. Screen readers read it instead of the image, browsers show it when the image fails to load, and search engines index it. Describe what the image conveys in context; for purely decorative images use an empty alt="", which tells assistive technology to skip it.
- Text alternative for screen readers
- Shown when the image fails
- Empty
altfor decorative images - Describe purpose in context
- WCAG 1.1.1
- A missing
altis not the same as an empty one: screen readers may read the file name instead ("IMG underscore 2041 dot jpg"). - Don't start with "Image of": the role is already announced.
- Complex images (charts, diagrams) need a longer description nearby, linked with
aria-describedbyor a<figcaption>. - WCAG 1.1.1 (Non-text Content, level A) requires a text alternative for every meaningful image.
03What are defer and async attributes on a <script> tag?MidHTMLJavaScriptdemo
A plain <script src> stops the HTML parser while the file downloads and runs. With async, it downloads in parallel and runs as soon as it arrives, pausing the parser briefly, in no guaranteed order. With defer, it downloads in parallel and runs after the document has been parsed, in document order, just before DOMContentLoaded. Module scripts are deferred by default.
- Plain scripts block parsing
async: parallel download, runs ASAP, unordereddefer: parallel download, runs after parsing, ordered- Modules defer by default
- When to use which
- Use
defer(or modules) for application code that touches the DOM;asyncfor independent scripts such as analytics. - Both are ignored on inline scripts (except
asyncon inline modules). - A plain script in
<head>also waits for preceding stylesheets, because it might read styles.
04What is the purpose of cache busting and how can you achieve it?JuniorHTMLdemo
Browsers and CDNs cache static files (CSS, JavaScript, images) so pages load fast. After a deploy, those caches can keep serving the old files. Cache busting makes the URL change whenever the content changes, so the new file is fetched while unchanged ones stay cached. The standard way is a content hash in the file name, which bundlers do for you: app.3f9a1c.js.
- Caches serve stale files after deploys
- Change the URL when content changes
- Content hashes in file names
- Long cache +
immutablefor hashed files - Short cache for HTML
- Content hash in the file name (
app.[hash].js): only changed files get new URLs. Vite, webpack and Next.js do it automatically. - Query strings (
app.js?v=42) work in browsers but a few proxies and CDNs ignore or strip them, and a version number busts every file at once. - The HTML must not be cached long, or users never learn the new file names.
The trap: Hashing assets but caching index.html for a day means a deploy reaches users a day late.
06Briefly describe the correct usage of the following HTML5 semantic elements: <header>, <article>, <section>, <footer>JuniorHTMLAccessibilitydemo
<header>: introductory content for the page or a section, such as a logo, title and navigation. <article>: a self-contained piece that would make sense on its own, such as a post, a comment or a product card. <section>: a thematic group of content, normally with its own heading. <footer>: closing information for its section, such as the author, copyright or related links.
header: introductory contentarticle: self-containedsection: thematic, with a headingfooter: closing info- Benefits for accessibility and SEO
- Use
<section>only when the content has a heading; otherwise it is a<div>. A<section>becomes aregionlandmark only when it has an accessible name (aria-labelledby). - An
<article>can be syndicated or reused elsewhere; nested articles (comments inside a post) are fine. - Related elements:
<main>for the main content,<nav>for major navigation,<aside>for tangential content,<search>for a search form.
Interview line: Semantic elements give structure to screen readers, search engines and reader modes for free, which a pile of divs with class names does not.
07What is HTML5 Web Storage? Explain localStorage and sessionStorage.SeniorHTMLJavaScriptdemo
Web Storage is a simple, synchronous key-value store of strings, scoped to an origin. localStorage persists until it is cleared, and is shared by every tab of that origin. sessionStorage lives for one tab (top-level browsing context): it survives reloads but is gone when the tab closes, and each tab has its own. Both hold roughly 5 MB per origin and are never sent to the server.
- Origin-scoped key-value strings
localStoragepersists;sessionStorageper tab- ~5 MB, not sent to the server
storageevent- Synchronous; JSON for objects
- Not for secrets
- Calls can throw: storage can be full, disabled, or blocked in some private modes. Wrap reads and writes in
try/catch. - It is synchronous and on the main thread: fine for small values, not for megabytes.
The trap: Anything in Web Storage is readable by any script on the page, so one XSS bug exposes it. Don't store auth tokens there; an HttpOnly cookie is out of JavaScript's reach.
08Discuss the differences between an HTML specification and a browser's implementation thereof.MidHTMLdemo
The specification (today the WHATWG HTML Living Standard) defines what documents should contain and exactly how browsers must parse and process them, including how to recover from invalid markup. Each browser engine (Blink, WebKit, Gecko) implements it separately and on its own schedule, so features ship at different times, and occasionally with bugs or deviations. Developers bridge the gap with feature detection, compatibility data and progressive enhancement.
- Spec defines behaviour, browsers implement it
- WHATWG Living Standard
- Error handling is specified
- Uneven support across engines
- Feature detection, Baseline, polyfills
- Living standard: HTML is no longer versioned ("HTML5" was the last big number); it changes continuously as browsers agree.
- Error handling is specified: since HTML5, every browser parses broken markup the same way. Earlier, each engine guessed differently.
- Where to check: MDN's compatibility tables, caniuse.com, and Baseline labels, which mark features available in all major browsers.
- Gaps are filled with polyfills (code implementing a missing API) and transpilers for syntax.
09What are some differences that XHTML has compared to HTML?MidHTMLdemo
XHTML is HTML written as XML and, when served as application/xhtml+xml, parsed by an XML parser. That parser is strict: tags and attributes are case-sensitive and lowercase, every element must be closed (<br />), elements must nest properly, attribute values must be quoted, attributes can't be minimised (checked="checked"), and special characters must be escaped. One error and the page fails to render, where the HTML parser would recover.
- HTML serialized as XML
- Strict parsing; errors are fatal
- Lowercase, closed, quoted, no minimisation
- MIME type picks the parser
- JSX resembles XHTML
Content type decides the parser, not the doctype: an XHTML document served as text/html is parsed as HTML. JSX borrows XHTML's habits: every tag closes, <br /> and camelCase attributes.
Accessibility
How assistive technology sees a page, the standards it is measured against, and the habits that keep an app usable without a mouse or a screen.
01What is ARIA and when should you use it?MidAccessibilitydemo
WAI-ARIA (Accessible Rich Internet Applications) is a W3C set of attributes that add roles (role="tab"), states (aria-expanded, aria-selected) and properties (aria-label, aria-controls) to elements, so assistive technology can understand custom widgets. Use it only when native HTML can't express the widget: the first rule of ARIA is to use a native element instead, and ARIA changes what is announced, not how anything behaves.
- Roles, states and properties
- Native HTML first
- ARIA doesn't add behaviour
- Keep states in sync
- Live regions
- Authoring Practices Guide
- No ARIA is better than bad ARIA: a
role="button"on a<div>promises keyboard behaviour that you then have to build (tabindex="0", Enter and Space handlers). - States must be kept in sync with JavaScript: an
aria-expanded="false"on an open menu is worse than nothing. - Live regions (
aria-live="polite",role="status") announce changes that happen away from focus, such as "Saved" or search result counts. - Patterns for common widgets (tabs, combobox, dialog, menu) are in the ARIA Authoring Practices Guide.
The trap: aria-label overrides the visible text for screen readers. If the two differ, voice-control users who say what they see can't activate the control.
02What is the Accessibility Tree?MidAccessibilitydemo
A tree the browser derives from the DOM (plus CSS and ARIA) and exposes to assistive technology through the platform's accessibility APIs. Each node has a role (button, heading, link), a name ("Save"), a state (checked, expanded) and sometimes a value. It is smaller than the DOM: purely presentational wrappers are dropped, and hidden content is removed.
- Derived from the DOM
- Exposed to assistive technology
- Role, name, state, value
- Fewer nodes than the DOM
- Semantic HTML builds it correctly
- Removed:
display: none,visibility: hidden, thehiddenattribute,aria-hidden="true", andalt=""images. - Names come from content,
<label>,alt,aria-labelledbyandaria-label, in a specified order (the accessible name computation). - Inspect it in Chrome or Firefox DevTools' Accessibility panel; Testing Library's
getByRolequeries are built on the same idea.
Interview line: Semantic HTML builds a good accessibility tree for free; ARIA patches it where HTML falls short.
03What is the minimum recommended ratio of contrast between foreground text and background to comply with WCAG? Why does this matter?JuniorAccessibilityCSSdemo
4.5:1 for normal text, and 3:1 for large text (at least 24px, or about 18.7px bold), under WCAG success criterion 1.4.3, level AA. Level AAA (1.4.6) raises it to 7:1 and 4.5:1. Low-contrast text is hard or impossible to read for people with low vision or colour-vision deficiencies, and for everyone in sunlight, on a dim screen or with ageing eyes.
- 4.5:1 normal text, 3:1 large text (AA)
- 7:1 for AAA
- 3:1 for UI components
- Low vision and colour deficiency
- Not colour alone
The ratio is (L1 + 0.05) / (L2 + 0.05), where L1 and L2 are the relative luminances of the lighter and darker colours. It runs from 1:1 (identical) to 21:1 (black on white). Pure #777 grey on white is 4.47:1 (checkers round down), just under AA.
- Contrast is not the only signal: information must not depend on colour alone (1.4.1), so pair a red border with an icon or text.
- Text over images needs a scrim or text shadow; the check applies to the worst part of the image behind the text.
04What are some of the tools available to test the accessibility of a website or web application?MidAccessibility
Automated checkers find the mechanical problems: axe (the DevTools extension, and axe-core in Playwright, Cypress or jest-axe), Lighthouse, WAVE and eslint-plugin-jsx-a11y in the editor. They catch only part of the issues, so manual testing is essential: use the keyboard alone, test with a screen reader (VoiceOver, NVDA, JAWS, TalkBack), zoom to 200% and 400%, and check focus order and contrast.
- Automated tools (axe, Lighthouse, WAVE)
- Linting and tests
- Manual keyboard testing
- Screen readers
- Tools miss many issues
The trap: A clean Lighthouse score is not "accessible": tools can tell that an image has alt text, not whether the text makes sense.
05What is WCAG? What are the differences between A, AA, and AAA compliance?JuniorAccessibilitydemo
The Web Content Accessibility Guidelines, published by the W3C's Web Accessibility Initiative. They are organised under four principles (content must be Perceivable, Operable, Understandable and Robust) into testable success criteria, each at a conformance level: A is the essential minimum, AA adds the criteria most laws and policies require, and AAA is the strictest, not expected for whole sites. The current version is WCAG 2.2.
- W3C guidelines
- POUR principles
- A, AA, AAA levels
- AA is the common legal target
- WCAG 2.2
- Each level includes the ones below it: AA conformance means every A and AA criterion passes.
- WCAG 2.2 (2023) added criteria such as Focus Not Obscured (2.4.11), Target Size minimum 24×24px (2.5.8) and Accessible Authentication (3.3.8).
- AA is referenced by the ADA case law and Section 508 in the US, EN 301 549 and the European Accessibility Act in the EU.
06What are landmark roles and how can they be useful?MidAccessibilityHTMLdemo
Landmarks mark the major regions of a page: banner, navigation, main, complementary, contentinfo, search, form and region. Screen reader users can list them and jump straight to one (to the main content, or the navigation) instead of reading the page from the top. Semantic HTML elements create most of them without any ARIA.
- Regions of the page
- Navigation for screen reader users
- HTML elements map to landmarks
- Label duplicates
- All content inside landmarks
- When a landmark appears more than once (two
<nav>s), label each one so the list reads "Primary navigation" and "Footer navigation". - All content should sit inside some landmark, so nothing is skipped when users navigate by them.
- Writing
role="main"on a<div>works, but<main>says it without ARIA.
07Describe your thoughts on how a single page web app should handle focus when changing routesMidAccessibilityReactdemo
In an SPA, a route change swaps content without loading a new page, so the browser does none of what it does on a real navigation: focus stays on the link that was clicked (which may no longer exist), and screen readers announce nothing. The app should move focus to the new content, usually its main <h1> (made focusable with tabindex="-1"), update document.title, and let the change be announced.
- Client-side navigation skips the browser's reset
- Focus stays on a stale element
- Move focus to the new heading (
tabindex=-1) - Update the title and announce
- Scroll restoration
- Why the heading: focus lands at the top of the new content, and the screen reader reads its name, which tells the user where they are. Focusing a skip link, or announcing the new title through a live region, are the other approaches people use; Gatsby's 2019 user research (linked below) compared them with real users.
- Remove the outline on that heading only if it's distracting (
.route-heading:focus { outline: none }): it is not an interactive control. - Restore scroll to the top on new pages, and to the previous position on Back.
- Framework support: Next.js's App Router includes a route announcer that reads the new page's title; check what your router does before adding your own.
The trap: Moving focus to <body> or not moving it at all leaves keyboard users at a stale position halfway down the page.
Node.js and APIs
Node's async conventions, its event loop, and the REST style most front ends talk to.
01What is the event loop in Node.js?SeniorNode.jsJavaScriptdemo
The loop, provided by libuv, that lets single-threaded JavaScript handle many concurrent operations. JavaScript runs on one thread; I/O is handed to the operating system or libuv's thread pool, and when it completes, its callback is queued. Each turn of the loop runs through phases: timers (setTimeout), pending callbacks, poll (I/O callbacks, waiting for new events), check (setImmediate) and close callbacks. After every callback, Node drains the process.nextTick queue, then the promise microtask queue.
- libuv
- Single JS thread, non-blocking I/O
- The phases in order
nextTickand microtasks between callbackssetImmediatevssetTimeout- Thread pool and blocking
Inside an I/O callback the loop is in the poll phase, so the check phase (setImmediate) comes before the loop wraps round to timers. In the main module the order of setTimeout(fn, 0) and setImmediate is not guaranteed: it depends on how long the process took to start.
- Thread pool: file system, DNS lookup,
cryptoandzlibrun on libuv's pool (4 threads by default); network sockets use the OS's non-blocking I/O. - Blocking the loop with CPU work (a big
JSON.parse, a sync hash) stalls every request. Move it toworker_threads. - Recursive
process.nextTickcan starve the loop: the queue is drained completely before moving on.
02NodeJS often uses a callback pattern where if an error is encountered during execution, this error is passed as the first argument to the callback. What are the advantages of this pattern?MidNode.jsJavaScript
Show the answerHide the answer
An error can't be thrown back to the caller from an asynchronous callback: by the time it runs, the caller's try/catch is long gone. Passing it as the first argument makes failure an ordinary value. Because every API follows the same shape ((err, result)), handling the error is the first thing you see, success and failure share one callback, and tools such as util.promisify can convert any of them to promises mechanically.
- Errors can't be thrown across async boundaries
- Consistent
(err, data)signature - Error checked first
nullon successutil.promisifyrelies on it
- Consistency: one convention across core modules and the ecosystem.
- Explicit handling: the
if (err) return ...guard is hard to forget when the error is the first parameter. nullfor success: callers test truthiness, so passnull, notundefinedorfalse.- Call exactly once, and never throw from inside the callback-taking function after starting async work: report through the callback.
Interview line: Today I'd use the promise APIs (node:fs/promises) with async/await, and keep error-first callbacks for older libraries, wrapped with promisify.
03How can you avoid callback hells?SeniorNode.jsJavaScript
Show the answerHide the answer
Use promises and async/await so dependent steps read top to bottom instead of nesting, with one try/catch for all of them. Before promises, the fixes were naming and extracting the callbacks into separate functions and using a control-flow library. Steps that don't depend on each other should run in parallel with Promise.all.
- Promises and async/await
- Named, modular functions
Promise.allfor parallel stepsutil.promisify- Centralised error handling
- Promisify callback APIs (
util.promisify, or the promise versions such asnode:fs/promises). - Flatten with chaining if you can't use
async:getData().then(getMoreData).then(getMoreData). - Parallelise independent work:
const [user, orders] = await Promise.all([getUser(), getOrders()]). - Extract named functions for each step; the nesting goes away and each piece is testable.
The trap: Callback hell is mostly about error handling: each level needs its own if (err) check, and forgetting one swallows the error. That is the strongest argument for promises.
04What is REST?MidNode.jsdemo
REpresentational State Transfer, an architectural style for networked APIs described by Roy Fielding in 2000. Data is modelled as resources identified by URLs (/posts/42), and clients act on them with the standard HTTP methods (GET, POST, PUT, PATCH, DELETE), exchanging representations such as JSON. Requests are stateless, responses say whether they can be cached, and status codes report the outcome.
- Resources identified by URLs
- HTTP methods as operations
- Stateless, cacheable
- Status codes
- Idempotency
- Alternatives: GraphQL, gRPC
- Nouns in URLs, verbs in methods:
POST /posts, notPOST /createPost. - Safe and idempotent: GET changes nothing; GET, PUT and DELETE can be repeated with the same result; POST cannot. That matters for retries.
- Stateless: every request carries what the server needs (for example an
Authorizationheader), which makes servers easy to scale. - Alternatives: GraphQL (client chooses the fields, one endpoint), gRPC (binary, typed contracts), tRPC (TypeScript end to end).
/posts API and compare methods, status codes and repeat behaviour.Web security
Two attacks every front-end developer should be able to explain, and the defaults that stop them.
01What is a cross-site scripting attack (XSS) and how do you prevent it?MidSecurityJavaScriptdemo
XSS is when an attacker gets their script to run in your page, in other users' browsers, with your origin's privileges: it can read the page, act as the user and steal tokens. It happens when untrusted input is inserted into HTML, attributes, URLs or scripts without the right escaping. Prevent it by escaping output (frameworks like React do it by default for text), avoiding raw-HTML APIs, sanitising the HTML you must render, and adding a Content Security Policy.
- Injected script runs with the site's privileges
- Stored, reflected, DOM-based
- Escape output by context
- Avoid
innerHTMLandeval; sanitise - Content Security Policy
HttpOnlycookies
- Escape by context: HTML text, attribute values, URLs and inline scripts each need different encoding. Templating engines and React handle the common cases.
- Dangerous sinks:
innerHTML,outerHTML,insertAdjacentHTML,document.write,eval,new Function,setTimeout(string), andhref/srcattributes holding user-suppliedjavascript:URLs, which escaping doesn't neutralise: validate the scheme. - Sanitise required HTML with a maintained library (DOMPurify); never with your own regex.
- Content Security Policy:
script-src 'self' 'nonce-...'stops injected inline scripts from running; Trusted Types can lock down DOM sinks. - Limit the damage:
HttpOnlycookies can't be read by scripts.
02Where and why is the rel="noopener" attribute used?MidHTMLSecurity
On links that open in a new tab (target="_blank"). Without it, the opened page gets window.opener, a reference to your page, and can navigate it, for example to a look-alike login page while the user is looking at the new tab ("reverse tabnabbing"). rel="noopener" sets window.opener to null. Modern browsers now apply it by default to target="_blank" links, but it is still written explicitly, and needed for window.open.
- Links with
target="_blank" window.openeraccess- Reverse tabnabbing
noreferrertoo- Now the browser default, still worth writing
noreferreralso omits theRefererheader, and impliesnoopener.- Browsers changed the default around 2020–2021 (Chrome 88, Firefox 79, Safari 12.1):
target="_blank"behaves as ifnoopenerwere set unless you writerel="opener". - A side benefit: without an opener link, the browser can put the new page in a separate process.
The trap: rel="noopener" protects your page from the one you link to, not the reverse. User-generated links are the risky ones.