📰 Beginner JavaScript Interview Questions
This lesson covers the fundamental JavaScript concepts every web developer must know. Master data types, scope, closures, the event loop, prototypes, ES6+ features, array methods, and asynchronous patterns. These questions reflect what interviewers ask at junior and entry-level JavaScript roles.
Questions & Answers
01 What are JavaScript’s data types? ►
Core JavaScript has eight data types split into two categories: primitives (stored by value) and objects (stored by reference).
Primitive types (7): undefined, null, boolean, number, bigint, string, symbol
Object type (1): object โ includes plain objects, arrays, functions, Date, Map, Set, etc.
// Primitives -- copied by value
let a = 42;
let b = a;
b = 99;
console.log(a); // 42 -- unchanged
// Objects -- copied by reference
const obj1 = { x: 1 };
const obj2 = obj1; // obj2 points to the SAME object
obj2.x = 99;
console.log(obj1.x); // 99 -- obj1 was mutated!
// typeof operator
typeof undefined // "undefined"
typeof null // "object" โ famous JS quirk (bug from 1995)
typeof true // "boolean"
typeof 42 // "number"
typeof 42n // "bigint"
typeof "hello" // "string"
typeof Symbol() // "symbol"
typeof {} // "object"
typeof [] // "object" โ arrays are objects
typeof function(){} // "function" โ functions are objects but typeof returns "function"
// Precise type check for arrays
Array.isArray([]) // true
Object.prototype.toString.call([]) // "[object Array]"
02 What is the difference between var, let, and const? ►
Scope
// var -- function-scoped, hoisted to top of function, re-declarable
function example() {
console.log(x); // undefined (hoisted, not ReferenceError)
var x = 10;
if (true) {
var x = 20; // same variable! leaks out of the block
}
console.log(x); // 20
}
// let -- block-scoped, temporal dead zone (TDZ), not re-declarable
{
// console.log(y); // ReferenceError: Cannot access 'y' before initialisation (TDZ)
let y = 10;
y = 20; // reassignment OK
}
// console.log(y); // ReferenceError: y is not defined (block scope)
// const -- block-scoped, must be initialised, cannot be reassigned
const PI = 3.14159;
// PI = 3; // TypeError: Assignment to constant variable
// const with objects -- reference is const, contents are mutable
const arr = [1, 2, 3];
arr.push(4); // OK -- mutating the object, not the binding
// arr = []; // TypeError -- can't reassign the binding
// Best practices (modern JS):
// Default to const -- signals intent that value won't be reassigned
// Use let when reassignment is needed
// Avoid var in new code (unpredictable scoping)
03 What is hoisting in JavaScript? ►
Core Hoisting is JavaScript’s behaviour of moving declarations to the top of their scope during compilation. Only declarations are hoisted, not initialisations.
// Function declarations -- fully hoisted (name AND body)
sayHello(); // "Hello!" -- works before the declaration
function sayHello() { console.log("Hello!"); }
// var declarations -- hoisted but initialised as undefined
console.log(name); // undefined (NOT ReferenceError)
var name = "Alice";
console.log(name); // "Alice"
// let and const -- hoisted but NOT initialised (TDZ)
// console.log(age); // ReferenceError: Cannot access 'age' before initialisation
let age = 30;
// Function expressions -- only the variable is hoisted (as undefined)
// greet(); // TypeError: greet is not a function
var greet = function() { return "Hi!"; };
// Arrow functions -- same as function expressions
// hello(); // TypeError: hello is not a function
var hello = () => "Hello!";
// Class declarations -- hoisted but in TDZ (like let)
// const c = new Car(); // ReferenceError
class Car {}
// Practical rule: always declare before use
// Use const/let to avoid var hoisting surprises
04 What is a closure in JavaScript? ►
Scope A closure is a function that retains access to its enclosing scope even after that scope has finished executing. Inner functions “close over” the variables of their outer function.
// Basic closure
function makeCounter() {
let count = 0; // enclosed variable
return function() {
count++;
return count;
};
}
const counter = makeCounter();
counter(); // 1
counter(); // 2
counter(); // 3
// count is private -- not accessible from outside
// Closure factory pattern
function multiplier(factor) {
return (number) => number * factor; // closes over 'factor'
}
const double = multiplier(2);
const triple = multiplier(3);
double(5); // 10
triple(5); // 15
// Classic closure bug with var in loops
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
} // prints: 3 3 3 (all share the same 'i', which is 3 after the loop)
// Fix 1: use let (block scope creates a new binding per iteration)
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
} // prints: 0 1 2
// Fix 2: IIFE to capture current value
for (var i = 0; i < 3; i++) {
((j) => setTimeout(() => console.log(j), 100))(i);
} // prints: 0 1 2
05 What is the difference between == and === in JavaScript? ►
Core
==(loose equality) โ performs type coercion before comparison. Converts operands to the same type then compares values.===(strict equality) โ no type coercion. Must be same type AND same value. Always prefer===.
// == with coercion (surprising results)
0 == false // true (false coerces to 0)
1 == true // true (true coerces to 1)
"" == false // true (both coerce to 0)
0 == "" // true (both coerce to 0)
0 == "0" // true ("0" coerces to 0)
"0" == false // true (both coerce to 0)
null == undefined // true (special case)
null == 0 // false (null only == undefined/null)
// === no coercion (predictable)
0 === false // false (different types: number vs boolean)
1 === true // false
"" === false // false
null === undefined // false
// NaN is special -- not equal to anything, even itself
NaN === NaN // false
Number.isNaN(NaN) // true (correct check)
// Object equality (both == and === check REFERENCE, not content)
const a = { x: 1 };
const b = { x: 1 };
a == b // false (different objects)
a === b // false
// Rule: always use === except when intentionally checking null OR undefined
if (value == null) { ... } // catches both null and undefined (acceptable use of ==)
06 What is the event loop in JavaScript? How does asynchronous code work? ►
Async JavaScript is single-threaded but handles asynchronous operations via the event loop. The event loop continuously checks the call stack and processes tasks from the callback queue when the stack is empty.
// Architecture:
// Call Stack -- where synchronous code executes (LIFO)
// Web APIs -- browser-provided async services (setTimeout, fetch, DOM events)
// Macrotask Queue -- callbacks from setTimeout, setInterval, I/O events
// Microtask Queue -- Promise callbacks, queueMicrotask (processed before macrotasks!)
console.log("1"); // call stack
setTimeout(() => console.log("2"), 0); // macrotask queue (even with 0ms delay)
Promise.resolve().then(() => console.log("3")); // microtask queue
console.log("4"); // call stack
// Output: 1, 4, 3, 2
// Reason:
// 1. Sync: "1", then "4"
// 2. Stack empty: drain microtask queue โ "3"
// 3. Next event loop tick: macrotask โ "2"
// Microtasks (higher priority) include:
// Promise.then/.catch/.finally, queueMicrotask(), MutationObserver
// Macrotasks (lower priority) include:
// setTimeout, setInterval, setImmediate (Node), requestAnimationFrame
// Async/Await is syntactic sugar over Promises
async function fetchUser(id) {
const response = await fetch(`/api/users/${id}`); // suspends here
const user = await response.json(); // suspends again
return user;
}
// Each 'await' resumes as a microtask when the Promise resolves
07 What are Promises and how do you use them? ►
Async A Promise represents a value that may be available now, in the future, or never. It has three states: pending, fulfilled, or rejected.
// Creating a Promise
const fetchData = (id) => new Promise((resolve, reject) => {
setTimeout(() => {
if (id > 0)
resolve({ id, name: "Alice" });
else
reject(new Error("Invalid ID"));
}, 1000);
});
// .then / .catch / .finally chaining
fetchData(1)
.then(user => user.name.toUpperCase())
.then(name => console.log(name)) // "ALICE"
.catch(err => console.error(err))
.finally(() => console.log("Done"));
// Async/await (cleaner for complex flows)
async function getUser(id) {
try {
const user = await fetchData(id);
return user.name.toUpperCase();
} catch (err) {
console.error(err);
}
}
// Promise combinators
// Promise.all -- wait for ALL (rejects if ANY rejects)
const [user, posts, comments] = await Promise.all([
fetchUser(1), fetchPosts(1), fetchComments(1)
]);
// Promise.allSettled -- wait for ALL, never rejects
const results = await Promise.allSettled([p1, p2, p3]);
// [{ status: "fulfilled", value: ... }, { status: "rejected", reason: ... }]
// Promise.race -- first to settle wins (fulfilled OR rejected)
const first = await Promise.race([fetchFast(), fetchSlow()]);
// Promise.any -- first to FULFILL (rejects only if ALL reject)
const first2 = await Promise.any([p1, p2, p3]);
08 What is prototype-based inheritance in JavaScript? ►
OOP JavaScript uses prototype chains for inheritance. Every object has an internal [[Prototype]] link. When you access a property, JS walks up the chain until it finds it or reaches null.
// Prototype chain
const animal = {
speak() { return `${this.name} makes a sound`; }
};
const dog = Object.create(animal); // dog's [[Prototype]] = animal
dog.name = "Rex";
dog.fetch = () => "fetching!";
dog.speak(); // "Rex makes a sound" (found on animal via chain)
dog.fetch(); // "fetching!" (found directly on dog)
dog.toString(); // found on Object.prototype (top of chain)
// Object.getPrototypeOf(dog) === animal // true
// Constructor functions (old style)
function Person(name) { this.name = name; }
Person.prototype.greet = function() { return `Hi, I'm ${this.name}`; };
const alice = new Person("Alice");
alice.greet(); // "Hi, I'm Alice"
// ES6 class syntax (syntactic sugar over prototypes)
class Animal {
constructor(name) { this.name = name; }
speak() { return `${this.name} makes a sound`; }
}
class Dog extends Animal {
speak() { return `${this.name} barks!`; } // overrides parent
fetch() { return `${this.name} fetches!`; }
}
const d = new Dog("Rex");
d.speak(); // "Rex barks!"
d instanceof Dog // true
d instanceof Animal // true
Object.getPrototypeOf(d) === Dog.prototype // true
09 What is the this keyword in JavaScript? ►
Core this is determined by how a function is called, not where it is defined (except for arrow functions which inherit this from their enclosing lexical scope).
// 1. Global context
console.log(this); // window (browser) or global (Node.js)
// In strict mode: undefined inside functions
// 2. Method call -- this = the object before the dot
const person = {
name: "Alice",
greet() { return `Hi, I'm ${this.name}`; }
};
person.greet(); // "Hi, I'm Alice"
// 3. Regular function -- depends on call site
function whoAmI() { return this; }
whoAmI(); // window / undefined (strict mode)
// 4. Arrow function -- inherits this from enclosing scope (lexical this)
const obj = {
name: "Alice",
greet: () => `Hi, I'm ${this.name}`, // โ this = window (not obj)
greetFixed() {
const inner = () => `Hi, I'm ${this.name}`; // โ
this = obj
return inner();
}
};
// 5. Explicit binding
function greet(greeting) { return `${greeting}, ${this.name}`; }
greet.call({ name: "Bob" }, "Hello"); // "Hello, Bob" (call with args)
greet.apply({ name: "Bob" }, ["Hi"]); // "Hi, Bob" (apply with array)
const boundGreet = greet.bind({ name: "Carol" }); // returns new function
boundGreet("Hey"); // "Hey, Carol"
// 6. new keyword -- this = newly created object
class Timer {
constructor() { this.seconds = 0; }
start() { setInterval(() => this.seconds++, 1000); }
}
const t = new Timer(); // this inside constructor = new Timer instance
10 What are arrow functions and how do they differ from regular functions? ►
ES6
// Regular function
function square(x) { return x * x; }
const square2 = function(x) { return x * x; };
// Arrow function (concise)
const square3 = x => x * x; // implicit return
const add = (a, b) => a + b;
const getObj = () => ({ name: "Alice" }); // wrap in () for object literal
const multi = (a, b) => { return a * b; }; // block body, explicit return
// Key differences:
// 1. Lexical 'this' (no own this binding)
class Counter {
constructor() { this.count = 0; }
start() {
// Regular: this would be lost inside setInterval callback
setInterval(function() { this.count++; }, 1000); // โ this = window
// Arrow: inherits this from start() (the Counter instance)
setInterval(() => { this.count++; }, 1000); // โ
}
}
// 2. No 'arguments' object (use rest params instead)
function regular() { console.log(arguments); } // works
const arrow = (...args) => console.log(args); // use rest params
// 3. Cannot be used as constructors (no 'new')
const Foo = () => {};
// new Foo(); // TypeError: Foo is not a constructor
// 4. No 'prototype' property
const fn = () => {};
fn.prototype; // undefined
// When NOT to use arrow functions:
// - Object methods (this would be wrong)
// - Event handlers where 'this' should be the element
// - Prototype methods
// - When you need 'arguments'
11 What is destructuring in JavaScript? ►
ES6 Destructuring extracts values from arrays or properties from objects into distinct variables with concise syntax.
// Array destructuring
const [first, second, , fourth] = [1, 2, 3, 4];
// Skip elements with empty comma
// Default values
const [a = 10, b = 20] = [1];
// a = 1, b = 20 (default used)
// Swap variables
let x = 1, y = 2;
[x, y] = [y, x]; // x=2, y=1
// Rest element
const [head, ...tail] = [1, 2, 3, 4];
// head = 1, tail = [2, 3, 4]
// Object destructuring
const { name, age, city = "London" } = { name: "Alice", age: 30 };
// name = "Alice", age = 30, city = "London" (default)
// Rename on destructure
const { name: personName, age: personAge } = { name: "Bob", age: 25 };
// personName = "Bob", personAge = 25
// Nested destructuring
const { address: { street, postcode } } = { address: { street: "Main St", postcode: "SW1A" } };
// In function parameters (very common in React)
function greet({ name, greeting = "Hello" }) {
return `${greeting}, ${name}!`;
}
greet({ name: "Alice" }); // "Hello, Alice!"
// Rest in objects
const { name: n, ...rest } = { name: "Alice", age: 30, city: "London" };
// n = "Alice", rest = { age: 30, city: "London" }
12 What are the spread and rest operators? ►
ES6 Both use ... syntax but serve opposite purposes. Spread expands an iterable into individual elements. Rest collects multiple elements into an array/object.
// Spread operator -- expands iterables
// Copy an array (shallow)
const original = [1, 2, 3];
const copy = [...original];
copy.push(4);
console.log(original); // [1, 2, 3] -- unchanged
// Merge arrays
const merged = [...[1, 2], ...[3, 4], 5]; // [1, 2, 3, 4, 5]
// Spread in function call
Math.max(...[1, 5, 3, 9, 2]); // 9
// Copy an object (shallow)
const obj = { a: 1, b: 2 };
const copy2 = { ...obj, c: 3 }; // { a:1, b:2, c:3 }
const override = { ...obj, b: 99 }; // { a:1, b:99 } -- b overridden
// Rest parameters -- collect remaining arguments
function sum(first, ...rest) {
return rest.reduce((acc, n) => acc + n, first);
}
sum(1, 2, 3, 4); // 10
// Rest in destructuring
const [head, ...tail] = [1, 2, 3, 4];
const { a: aa, ...others } = { a:1, b:2, c:3 };
// aa = 1, others = { b:2, c:3 }
// Spread strings (splits into characters)
[..."hello"] // ['h','e','l','l','o']
// Convert NodeList to array
const divs = [...document.querySelectorAll('div')];
13 What are common array methods in JavaScript? ►
Arrays
const nums = [1, 2, 3, 4, 5]; // Transformation (return new array) nums.map(n => n * 2) // [2, 4, 6, 8, 10] nums.filter(n => n % 2 === 0) // [2, 4] nums.reduce((acc, n) => acc + n, 0) // 15 (sum) nums.flatMap(n => [n, n * 2]) // [1,2,2,4,3,6,4,8,5,10] // Search nums.find(n => n > 3) // 4 (first match) nums.findIndex(n => n > 3) // 3 (index of first match) nums.indexOf(3) // 2 nums.includes(3) // true nums.some(n => n > 4) // true (at least one) nums.every(n => n > 0) // true (all match) // Sorting and ordering [3, 1, 4, 1, 5].sort((a, b) => a - b) // [1,1,3,4,5] ascending [3, 1, 4].sort((a, b) => b - a) // [4,3,1] descending [...nums].reverse() // [5,4,3,2,1] (use spread to avoid mutation) // Slice and splice nums.slice(1, 3) // [2, 3] (non-mutating) const a = [1,2,3,4,5]; a.splice(2, 1, 99) // removes 1 at index 2, inserts 99 โ a=[1,2,99,4,5] // Flatten [[1,2],[3,[4,5]]].flat() // [1,2,3,[4,5]] (one level) [[1,2],[3,[4,5]]].flat(Infinity) // [1,2,3,4,5] (all levels) // Mutation methods (modify in place) nums.push(6) // add to end nums.pop() // remove from end nums.unshift(0) // add to start nums.shift() // remove from start nums.fill(0, 2, 4) // fill indices 2-3 with 0
14 What are template literals? ►
ES6 Template literals use backticks (`) and allow embedded expressions, multi-line strings, and tagged templates.
const name = "Alice";
const age = 30;
// String interpolation (replaces ugly string concatenation)
const msg = `Hello, ${name}! You are ${age} years old.`;
const calc = `2 + 2 = ${2 + 2}`; // expressions work
const upper = `${name.toUpperCase()} logged in`; // method calls work
const nested = `${age >= 18 ? "adult" : "minor"}`;
// Multi-line strings (no \n needed)
const html = `
<div class="card">
<h2>${name}</h2>
<p>Age: ${age}</p>
</div>
`;
// Tagged templates -- process template with a function
function highlight(strings, ...values) {
return strings.reduce((result, str, i) =>
`${result}${str}${values[i] !== undefined ? `<em>${values[i]}</em>` : ""}`, "");
}
highlight`Hello, ${name}! You are ${age} years old.`;
// "Hello, <em>Alice</em>! You are <em>30</em> years old."
// Real-world tagged templates:
// - css`color: red;` (styled-components)
// - html`<div>${content}</div>` (lit-html, sanitised rendering)
// - sql`SELECT * FROM users WHERE id = ${userId}` (parameterised queries)
15 What is the difference between null and undefined in JavaScript? ►
Core
- undefined โ a variable has been declared but not assigned a value. Also the default return value of functions that return nothing. Assigned by the JavaScript engine automatically.
- null โ an intentional absence of a value. Assigned explicitly by the programmer to represent “no value”.
let x;
console.log(x); // undefined (declared, not assigned)
console.log(typeof x); // "undefined"
function noReturn() {}
console.log(noReturn()); // undefined
const obj = { name: "Alice" };
console.log(obj.age); // undefined (property doesn't exist)
let y = null; // programmer intentionally set to "no value"
console.log(y); // null
console.log(typeof y); // "object" (the infamous JS bug)
// Comparisons
null == undefined // true (loose -- they are "equal" to each other)
null === undefined // false (strict -- different types)
// Nullish coalescing ??
const value = null ?? "default"; // "default" (only null/undefined trigger)
const zero = 0 ?? "default"; // 0 (0 is NOT nullish)
const empty = "" ?? "default"; // "" (empty string is NOT nullish)
// Optional chaining ?. (avoids TypeError on null/undefined)
const street = user?.address?.street; // undefined instead of TypeError
const firstItem = items?.[0]; // safe array access
const result = fn?.(); // safe function call
16 What are ES6 modules (import / export)? ►
ES6
// Named exports -- can have multiple per file
// math.js
export const PI = 3.14159;
export function add(a, b) { return a + b; }
export function subtract(a, b) { return a - b; }
// Named import
import { add, subtract } from "./math.js";
import { add as sum } from "./math.js"; // rename
import * as math from "./math.js"; // namespace import
math.add(1, 2);
// Default export -- one per file
// greet.js
export default function greet(name) { return `Hello, ${name}!`; }
// Default import (any name you want)
import greet from "./greet.js";
import sayHello from "./greet.js"; // same thing, different name
// Mix named and default
export default class User { ... }
export const MAX_USERS = 100;
import User, { MAX_USERS } from "./user.js";
// Re-export (barrel file pattern)
// index.js
export { add, subtract } from "./math.js";
export { default as User } from "./user.js";
export * from "./utils.js";
// Dynamic import (lazy loading)
const { default: module } = await import("./heavy-module.js");
// Loaded on demand -- not bundled at startup
// Module characteristics:
// - Always in strict mode
// - Own scope (variables not global)
// - Evaluated once (subsequent imports return cached exports)
// - Static analysis (enables tree-shaking)
17 What are Map and Set in JavaScript? How do they differ from objects and arrays? ►
ES6
// Map -- key-value pairs (any type as key, ordered, iterable)
const map = new Map();
map.set("name", "Alice");
map.set(42, "the answer");
map.set({ id: 1 }, "obj key"); // objects CAN be keys (unlike plain objects)
map.get("name"); // "Alice"
map.has(42); // true
map.size; // 3
map.delete(42);
map.forEach((value, key) => console.log(key, value));
// Convert
const fromObj = new Map(Object.entries({ a: 1, b: 2 }));
const toObj = Object.fromEntries(map);
// Map vs Object:
// Map: any key type, ordered, iterable directly, .size, no prototype pollution
// Object: string/symbol keys only, less ergonomic, faster for simple lookups
// Set -- unique values (ordered, iterable)
const set = new Set([1, 2, 3, 2, 1]); // {1, 2, 3} -- duplicates removed
set.add(4);
set.has(2); // true
set.delete(3);
set.size; // 3
// Deduplicate an array
const unique = [...new Set([1, 2, 2, 3, 3, 3])]; // [1, 2, 3]
// Set operations
const a = new Set([1, 2, 3, 4]);
const b = new Set([3, 4, 5, 6]);
const union = new Set([...a, ...b]); // {1,2,3,4,5,6}
const intersection = new Set([...a].filter(x => b.has(x))); // {3,4}
const difference = new Set([...a].filter(x => !b.has(x))); // {1,2}
// WeakMap and WeakSet -- hold WEAK references (allow GC)
const wm = new WeakMap(); // keys must be objects, not iterable
// Useful for: storing metadata about objects without preventing GC
18 What is event delegation in JavaScript? ►
DOM Event delegation attaches a single event listener to a parent element instead of many listeners on each child. It works because events bubble up the DOM tree.
// Without delegation -- listener on every button (bad for 1000 items)
document.querySelectorAll('.btn').forEach(btn =>
btn.addEventListener('click', handleClick)
);
// With delegation -- ONE listener on the parent
document.getElementById('button-list')
.addEventListener('click', (event) => {
// Check the actual target
if (event.target.matches('.btn')) {
console.log('Clicked:', event.target.dataset.id);
}
// target = element that was actually clicked
// currentTarget = element with the listener (button-list)
});
// Works for dynamically added elements (don't need to re-bind)
// Adding new buttons to #button-list automatically gets the listener
// Event propagation phases:
// 1. Capture phase: event travels from window DOWN to target
// 2. Target phase: event reaches the target
// 3. Bubble phase: event bubbles UP from target to window
// Most listeners use bubble phase (default, third arg false/omitted)
// Capture phase listeners: addEventListener(event, handler, true)
// Stop propagation
event.stopPropagation(); // stop bubbling/capturing
event.stopImmediatePropagation(); // also prevents other handlers on same element
// Prevent default browser behaviour
event.preventDefault(); // e.g., prevent form submission, link navigation
19 What is the difference between synchronous and asynchronous code? ►
Async
// Synchronous -- blocks execution until complete
function fetchSync(url) {
// Imagine this blocks for 2 seconds
const data = readFileSync(url); // blocks the entire thread
return data; // nothing else can run during those 2 seconds
}
// Asynchronous -- non-blocking; execution continues while waiting
// Pattern 1: Callbacks (old style)
fs.readFile("data.txt", "utf8", (err, data) => {
if (err) throw err;
console.log(data);
});
console.log("This runs before the file is read!");
// Pattern 2: Promises
fetch("/api/user/1")
.then(res => res.json())
.then(user => console.log(user));
console.log("This runs before fetch completes");
// Pattern 3: Async/await (most readable)
async function getUser() {
const res = await fetch("/api/user/1"); // suspended here, no blocking
const user = await res.json();
console.log(user);
}
getUser();
console.log("This runs before getUser finishes");
// Callback hell problem (nested callbacks)
getUser(id, (user) => {
getPosts(user.id, (posts) => {
getComments(posts[0].id, (comments) => {
// deeply nested, hard to read and error-handle
});
});
});
// Solved with async/await
const user = await getUser(id);
const posts = await getPosts(user.id);
const comments = await getComments(posts[0].id);
// Linear, readable, proper try/catch error handling
20 What is JSON and how do you work with it in JavaScript? ►
Core JSON (JavaScript Object Notation) is a lightweight text format for data exchange. It is language-agnostic but JavaScript-inspired.
// JSON syntax rules:
// Keys must be double-quoted strings
// Values: string, number, boolean, null, array, or object (no undefined, no functions)
// No trailing commas, no comments
const jsonString = '{"name":"Alice","age":30,"hobbies":["JS","chess"],"active":true}';
// Parsing: JSON string โ JavaScript object
const obj = JSON.parse(jsonString);
obj.name; // "Alice"
obj.hobbies; // ["JS", "chess"]
// Serialising: JavaScript object โ JSON string
const user = { name: "Bob", age: 25, greet() { return "Hi!"; } };
JSON.stringify(user);
// '{"name":"Bob","age":25}' -- functions are OMITTED
// Formatting (pretty-print)
JSON.stringify(user, null, 2); // 2-space indented
// Replacer -- filter/transform properties
JSON.stringify(user, ["name", "age"]); // only include name and age
JSON.stringify(user, (key, val) => key === "password" ? undefined : val); // omit password
// Reviver -- transform when parsing
JSON.parse('{"date":"2026-04-22"}', (key, val) =>
key === "date" ? new Date(val) : val
);
// Deep clone with JSON (works for serialisable data)
const clone = JSON.parse(JSON.stringify(obj));
// โ Loses: undefined, functions, Date (becomes string), Map, Set, circular refs
// Better deep clone (modern)
const clone2 = structuredClone(obj); // handles Dates, Maps, Sets, etc.
21 What is the optional chaining operator (?.) and nullish coalescing (??)? ►
ES2020
const user = {
name: "Alice",
address: {
street: "123 Main St"
// no city property
}
};
// Without optional chaining -- verbose and error-prone
const city = user && user.address && user.address.city;
// With optional chaining -- short-circuits to undefined if any step is null/undefined
const city2 = user?.address?.city; // undefined (no error)
const zip = user?.address?.postcode; // undefined
// Method calls
const upper = user?.getName?.(); // undefined if getName doesn't exist
const len = user?.hobbies?.length; // undefined if hobbies is null/undefined
// Array access
const first = user?.hobbies?.[0]; // undefined if hobbies is null
// Nullish coalescing ?? (only triggers on null or undefined, NOT 0 or "")
const city3 = user?.address?.city ?? "London"; // "London" (city is undefined)
const count = user?.postCount ?? 0; // 0 default
// ?? vs || (logical OR)
0 || "default" // "default" (0 is falsy, || triggers)
0 ?? "default" // 0 (0 is not null/undefined, ?? does NOT trigger)
"" || "default" // "default" (empty string is falsy)
"" ?? "default" // "" (?? only cares about null/undefined)
// Nullish assignment ??=
user.nickname ??= "Anonymous"; // assigns only if user.nickname is null/undefined
// Logical assignment
user.score ||= 100; // assigns if falsy
user.score &&= user.score * 2; // assigns if truthy
22 What is a Symbol in JavaScript and when do you use it? ►
ES6 Symbol creates a guaranteed-unique value. Symbols are never accidentally equal to anything else โ useful for private-like object keys and well-known protocol hooks.
// Every Symbol is unique
const s1 = Symbol("label");
const s2 = Symbol("label");
s1 === s2 // false (even with the same description!)
// Use as unique object property keys (won't clash with string keys)
const ID = Symbol("id");
const user = {
name: "Alice",
[ID]: 12345 // Symbol as computed key
};
user[ID]; // 12345
user.name; // "Alice"
// Symbols are NOT enumerable (don't appear in for...in or Object.keys)
Object.keys(user); // ["name"] -- Symbol is hidden
Object.getOwnPropertySymbols(user); // [Symbol(id)]
// Well-known Symbols -- customise language behaviour
class Range {
constructor(start, end) { this.start = start; this.end = end; }
// Make Range iterable with for...of
[Symbol.iterator]() {
let current = this.start;
const end = this.end;
return {
next() {
return current <= end
? { value: current++, done: false }
: { done: true };
}
};
}
}
for (const n of new Range(1, 5)) console.log(n); // 1 2 3 4 5
[...new Range(1, 3)]; // [1, 2, 3]
// Other well-known Symbols:
// Symbol.toPrimitive -- customise type conversion
// Symbol.hasInstance -- customise instanceof behaviour
// Symbol.toStringTag -- customise Object.prototype.toString output