DEV SCRIPTS

JavaScript Developer FAQs

JavaScript Interview FAQ
Basic — Values, Types & Built-ins

There are exactly 8 falsy values: false, 0, -0, 0n (BigInt zero), "" (empty string), null, undefined, and NaN. Everything else — including empty arrays, empty objects, and the string "false" — is truthy. Knowing this list prevents subtle bugs in conditionals and short-circuit expressions.

javascript
// All falsy
Boolean(false)      // false
Boolean(0)          // false
Boolean("")         // false
Boolean(null)       // false
Boolean(undefined)  // false
Boolean(NaN)        // false

// Surprises — these are TRUTHY
Boolean([])         // true  ← empty array!
Boolean({})         // true  ← empty object!
Boolean("false")    // true  ← non-empty string!
Boolean("0")        // true  ← non-empty string!

// Practical: checking for a real value
const name = "";
if (name) { /* skipped — empty string is falsy */ }

// Use explicit checks when 0 is a valid value
const count = 0;
if (count !== undefined) { /* runs correctly ✓ */ }

parseInt() reads characters from left to right, stops at the first non-numeric character, and always returns an integer. parseFloat() does the same but preserves decimals. Number() converts the entire value strictly — any non-numeric character in a string returns NaN. Always pass a radix to parseInt() to avoid octal surprises.

javascript
parseInt("42px",   10)  // 42   — stops at "p"
parseInt("3.9",    10)  // 3    — truncates decimal
parseFloat("3.9")       // 3.9
parseFloat("3.9em")     // 3.9  — stops at "e"

Number("42")            // 42
Number("42px")          // NaN  — strict, entire string must be numeric
Number("")              // 0    ← surprising!
Number(null)            // 0    ← surprising!
Number(undefined)       // NaN
Number(true)            // 1
Number(false)           // 0

// Safe check
Number.isFinite(Number("42px"))  // false
Number.isNaN(NaN)                // true (better than global isNaN)

Strings are immutable — every method returns a new string. The most-used methods cover searching (includes, indexOf, startsWith, endsWith), extraction (slice), transformation (replace, toUpperCase, trim), and splitting (split). Knowing which returns what prevents hours of debugging.

javascript
const s = "  Hello, World!  ";

s.trim()                    // "Hello, World!"
s.toLowerCase()             // "  hello, world!  "
s.includes("World")         // true
s.startsWith("  Hello")     // true
s.indexOf("o")              // 4  (first occurrence)
s.lastIndexOf("o")          // 9
s.slice(2, 7)               // "Hello"
s.slice(-8, -2)             // "World!"  — negative indices
s.replace("World", "JS")    // "  Hello, JS!  "
s.replaceAll("l", "L")      // "  HeLLo, WorLd!  "
s.split(", ")               // ["  Hello", "World!  "]
"ha".repeat(3)              // "hahaha"
s.trimEnd().endsWith("!")   // true

slice() is non-destructive — it returns a new array from a range of indices without touching the original. splice() is destructive — it modifies the original array in place, removing, replacing, or inserting elements at a given position and returning the removed items.

javascript
const fruits = ["apple", "banana", "cherry", "date", "elderberry"];

// slice — original untouched ✓
const middle = fruits.slice(1, 4);   // ["banana", "cherry", "date"]
console.log(fruits.length);          // 5 — unchanged

// splice(start, deleteCount, ...itemsToInsert)
const removed = fruits.splice(1, 2, "blueberry");
// removed = ["banana", "cherry"]
// fruits  = ["apple", "blueberry", "date", "elderberry"]

// splice to insert without removing
fruits.splice(2, 0, "coconut");
// fruits = ["apple", "blueberry", "coconut", "date", "elderberry"]

Math is a built-in object (not a constructor) with static methods for common mathematical operations. The most frequently needed are round, floor, ceil, abs, min, max, pow, sqrt, and random. Understanding Math.random()‘s range and how to scale it is essential for games, simulations, and shuffle algorithms.

javascript
Math.round(4.5)    // 5   — rounds to nearest integer
Math.floor(4.9)    // 4   — always rounds down
Math.ceil(4.1)     // 5   — always rounds up
Math.abs(-7)       // 7
Math.max(3, 9, 1)  // 9
Math.min(3, 9, 1)  // 1
Math.pow(2, 10)    // 1024  (same as 2 ** 10)
Math.sqrt(16)      // 4
Math.trunc(-4.9)   // -4   — removes decimal, toward zero

// Math.random() returns [0, 1)
Math.random()                         // e.g. 0.7341...

// Random integer between min (inclusive) and max (exclusive)
const randInt = (min, max) => Math.floor(Math.random() * (max - min)) + min;
randInt(1, 7);  // simulates a die roll: 1–6
Built-in Data Structures

A Map preserves insertion order, accepts any value (including objects and functions) as a key, and exposes a reliable .size property. A plain object coerces all keys to strings, inherits prototype properties that can cause key collisions, and requires Object.keys() to count entries. For frequent add/delete operations, Map is generally faster.

javascript
const map = new Map();
const keyObj = { id: 1 };

map.set(keyObj, "object as key");  // ✓ any type as key
map.set(42,     "number key");
map.set(true,   "boolean key");

console.log(map.size);             // 3
console.log(map.get(keyObj));      // "object as key"

// Plain object — keys always become strings
const obj = {};
obj[keyObj] = "value";
console.log(Object.keys(obj));     // ["[object Object]"] ⚠️

A Set stores only unique values — adding a duplicate is silently ignored. Arrays allow duplicates and support index-based access, while Set has no indexes but offers O(1) lookups via .has(). Sets are ideal for deduplication and membership testing.

javascript
const set = new Set([1, 2, 2, 3, 3, 3]);
console.log([...set]);          // [1, 2, 3]  — duplicates removed

// Fast membership check
set.has(2);                     // true  (O(1))

// Deduplicate an array
const tags = ["js", "css", "js", "html", "css"];
const unique = [...new Set(tags)];  // ["js", "css", "html"]

// Set operations (ES2024 native)
const a = new Set([1, 2, 3]);
const b = new Set([2, 3, 4]);
a.intersection(b);  // Set {2, 3}

A WeakMap holds weak references to its object keys — if no other reference to a key exists, the garbage collector can reclaim both the key and its value automatically. It is not iterable and has no .size. Use it to attach private metadata to objects without preventing garbage collection.

javascript
const cache = new WeakMap();

function processUser(user) {
  if (cache.has(user)) return cache.get(user);

  const result = expensiveComputation(user);
  cache.set(user, result);   // key is the user object
  return result;
}

// When user goes out of scope → entry is GC'd automatically
// No manual cleanup needed ✓

for...in iterates over an object’s enumerable property keys (including inherited ones), making it useful for plain objects but dangerous on arrays. for...of iterates over the values of any iterable (arrays, strings, Maps, Sets) and ignores prototype properties entirely.

javascript
const arr = [10, 20, 30];

for (const key of arr)  console.log(key);  // 10  20  30  ✓
for (const key in arr)  console.log(key);  // "0" "1" "2" (indices, strings!)

const user = { name: "Ana", age: 28 };
for (const key in user) console.log(key, user[key]);
// name Ana
// age  28

// for...of on a Map gives [key, value] pairs
const map = new Map([["a", 1], ["b", 2]]);
for (const [k, v] of map) console.log(k, v);
Modern Syntax & Patterns

Optional chaining short-circuits and returns undefined the moment it encounters a null or undefined in a property access chain, instead of throwing a TypeError. It works on property access, method calls, and bracket notation.

javascript
const user = { profile: { address: { city: "Tel Aviv" } } };

// Old way — verbose guard
const city = user && user.profile && user.profile.address
             && user.profile.address.city;

// Optional chaining
const city = user?.profile?.address?.city;   // "Tel Aviv"
const zip  = user?.profile?.address?.zip;    // undefined (no throw)

// Works on methods and arrays too
user?.getPermissions?.();
user?.roles?.[0];

?? returns the right-hand operand only when the left-hand side is null or undefined. Unlike ||, it does not treat falsy values such as 0, "", or false as fallback triggers — making it safer for legitimate zero or empty-string values.

javascript
// Problem with ||
const volume = 0;
console.log(volume || 50);   // 50 ⚠️ — 0 is falsy, wrong default!

// ?? only falls back on null / undefined
console.log(volume ?? 50);   // 0  ✓

// Common with optional chaining
const timeout = config?.retry?.timeout ?? 3000;

// Nullish assignment (??=)
user.name ??= "Guest";   // assigns only if user.name is null/undefined

Getters and setters let you define computed or validated properties that look like plain property access from the outside. A getter runs when the property is read; a setter runs when it is assigned. They are useful for lazy computation, input validation, and encapsulating private state.

javascript
class Temperature {
  #celsius;                          // private field

  constructor(c) { this.#celsius = c; }

  get fahrenheit() {                 // computed on read
    return this.#celsius * 9/5 + 32;
  }

  set fahrenheit(f) {                // validated on write
    if (f < -459.67) throw new RangeError("Below absolute zero");
    this.#celsius = (f - 32) * 5/9;
  }
}

const t = new Temperature(100);
console.log(t.fahrenheit);    // 212
t.fahrenheit = 32;
console.log(t.fahrenheit);    // 32
Iterators & Generators

An iterator is any object with a .next() method that returns { value, done }. An object is iterable when it implements the Symbol.iterator method that returns an iterator. Arrays, strings, Maps, and Sets are all built-in iterables. Custom iterables work with for...of, spread, and destructuring.

javascript
// Custom range iterable
const range = {
  from: 1, to: 5,
  [Symbol.iterator]() {
    let current = this.from;
    const last  = this.to;
    return {
      next() {
        return current <= last
          ? { value: current++, done: false }
          : { value: undefined, done: true };
      }
    };
  }
};

console.log([...range]);              // [1, 2, 3, 4, 5]
for (const n of range) console.log(n); // 1 2 3 4 5

A generator (declared with function*) is a function that can pause its execution at each yield statement and resume on the next .next() call. Each call returns { value, done }. Generators produce lazy sequences — values are computed only when requested, making them efficient for infinite streams.

javascript
function* fibonacci() {
  let [a, b] = [0, 1];
  while (true) {
    yield a;
    [a, b] = [b, a + b];
  }
}

const fib = fibonacci();
fib.next().value;  // 0
fib.next().value;  // 1
fib.next().value;  // 1
fib.next().value;  // 2
fib.next().value;  // 3

// Take first N values
function take(gen, n) {
  return [...Array(n)].map(() => gen.next().value);
}
take(fibonacci(), 7);  // [0, 1, 1, 2, 3, 5, 8]

A Symbol is a unique, immutable primitive value — no two symbols are ever equal, even if created with the same description. They are used as guaranteed-unique object property keys to avoid naming collisions, and as well-known hooks into the language runtime (e.g. Symbol.iterator, Symbol.toPrimitive).

javascript
const id = Symbol("id");
const user = { name: "Alice", [id]: 42 };

console.log(user[id]);              // 42
console.log(Object.keys(user));     // ["name"]  — symbol hidden ✓

// Every symbol is unique
Symbol("x") === Symbol("x");       // false

// Well-known symbol — custom toString coercion
class Money {
  constructor(amount) { this.amount = amount; }
  [Symbol.toPrimitive](hint) {
    return hint === "string" ? `$${this.amount}` : this.amount;
  }
}
const price = new Money(99);
console.log(`Total: ${price}`);     // "Total: $99"
console.log(price + 1);             // 100
Error Handling

Code in try runs normally. If any exception is thrown, execution jumps immediately to catch, which receives the error object. finally runs unconditionally — whether or not an error occurred — making it ideal for cleanup (closing files, releasing locks). A return inside finally overrides any earlier return value.

javascript
async function loadData(url) {
  let response;
  try {
    response = await fetch(url);
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    return await response.json();
  } catch (err) {
    if (err instanceof TypeError) {
      console.error("Network failure:", err.message);
    } else {
      console.error("Server error:", err.message);
    }
    return null;
  } finally {
    console.log("Request finished");   // always runs ✓
  }
}

Extend the built-in Error class to create typed, domain-specific errors. Custom errors let callers use instanceof checks to handle different failure modes distinctly without relying on string parsing of the message field.

javascript
class ValidationError extends Error {
  constructor(field, message) {
    super(message);
    this.name  = "ValidationError";
    this.field = field;
  }
}

class NetworkError extends Error {
  constructor(status, message) {
    super(message);
    this.name   = "NetworkError";
    this.status = status;
  }
}

try {
  throw new ValidationError("email", "Invalid format");
} catch (err) {
  if (err instanceof ValidationError) {
    console.log(`Field "${err.field}": ${err.message}`);
  } else if (err instanceof NetworkError) {
    console.log(`HTTP ${err.status}: ${err.message}`);
  }
}

In synchronous code, unhandled errors bubble up the call stack. In async code, a rejected Promise without a .catch() or try/catch becomes an unhandled rejection that can crash Node.js or fire a browser warning. Always handle errors at the boundary where you have enough context to act meaningfully.

javascript
// ❌ Unhandled rejection — dangerous
async function bad() { throw new Error("oops"); }
bad();   // UnhandledPromiseRejection ⚠️

// ✓ Handled at call site
bad().catch(err => console.error(err));

// ✓ Global safety net (Node.js)
process.on("unhandledRejection", (reason) => {
  console.error("Unhandled rejection:", reason);
  process.exit(1);
});

// ✓ Global safety net (browser)
window.addEventListener("unhandledrejection", (e) => {
  console.error("Unhandled rejection:", e.reason);
});
DOM & Browser APIs

Event delegation attaches a single listener to a parent element instead of to each child. Because events bubble up the DOM tree, the parent catches events fired by any descendant. It is more efficient for large or dynamically generated lists, and the listener works for elements added after page load.

javascript
// ❌ One listener per item — expensive for 1000 rows
document.querySelectorAll(".item").forEach(el => {
  el.addEventListener("click", handleClick);
});

// ✓ One listener on the parent
document.getElementById("list").addEventListener("click", (e) => {
  const item = e.target.closest(".item");
  if (!item) return;                // click was outside an item
  console.log("Clicked:", item.dataset.id);
});

// Works for dynamically added items too ✓
list.insertAdjacentHTML("beforeend",
  '
  • New
  • ');

    localStorage persists until explicitly cleared — data survives page reloads, tab closes, and browser restarts. sessionStorage is tied to the browser tab's lifetime — closing the tab wipes the data. Both are synchronous, string-only, and origin-scoped with a ~5 MB limit; neither is a substitute for secure server-side storage of sensitive data.

    javascript
    // localStorage — persists across sessions
    localStorage.setItem("theme", "dark");
    localStorage.getItem("theme");       // "dark"
    localStorage.removeItem("theme");
    
    // sessionStorage — cleared when tab closes
    sessionStorage.setItem("draft", JSON.stringify({ title: "WIP" }));
    const draft = JSON.parse(sessionStorage.getItem("draft"));
    
    // Both only store strings — serialize objects manually
    localStorage.setItem("user", JSON.stringify({ id: 1, name: "Ana" }));
    const user = JSON.parse(localStorage.getItem("user"));

    setTimeout runs the callback once after a delay. setInterval runs it repeatedly at fixed intervals. Both return an ID for cancellation. Use recursive setTimeout when execution time varies — setInterval fires regardless of whether the previous run has finished, which can cause overlap.

    javascript
    // setInterval — fires every 1s even if work takes longer
    const id = setInterval(() => fetchStats(), 1000);
    clearInterval(id);   // cancel
    
    // Recursive setTimeout — next tick starts only after work finishes
    function poll() {
      fetchStats().then(data => {
        render(data);
        setTimeout(poll, 1000);   // schedule AFTER completion ✓
      });
    }
    poll();
    Functional Programming

    A pure function always returns the same output for the same inputs, and produces no side effects — it does not modify external state, perform I/O, or mutate its arguments. Pure functions are easy to test, cache (memoize), and reason about because their behavior is fully predictable from their arguments alone.

    javascript
    // ❌ Impure — mutates external state
    let total = 0;
    function addToTotal(n) { total += n; }
    
    // ❌ Impure — depends on external state
    function getDiscount(price) {
      return price * currentUser.discountRate;  // side dependency
    }
    
    // ✓ Pure — same input → same output, no side effects
    function add(a, b) { return a + b; }
    
    function applyDiscount(price, rate) {
      return price * (1 - rate);                // all inputs explicit
    }
    
    // ✓ Pure array operations (return new arrays)
    const doubled = [1, 2, 3].map(x => x * 2); // [2, 4, 6]

    Currying transforms a function that takes multiple arguments into a sequence of functions each taking a single argument. It enables partial application — locking in some arguments upfront to produce specialized, reusable functions without repetition.

    javascript
    // Regular function
    const multiply = (a, b) => a * b;
    
    // Curried version
    const curriedMultiply = a => b => a * b;
    
    const double  = curriedMultiply(2);   // partial application
    const triple  = curriedMultiply(3);
    
    double(5);   // 10
    triple(5);   // 15
    
    // Practical example — reusable formatters
    const formatCurrency = currency => amount =>
      new Intl.NumberFormat("en-US", { style: "currency", currency })
        .format(amount);
    
    const formatUSD = formatCurrency("USD");
    const formatEUR = formatCurrency("EUR");
    
    formatUSD(1234.5);   // "$1,234.50"
    formatEUR(1234.5);   // "€1,234.50"

    Function composition combines two or more functions so that the output of one becomes the input of the next. This builds complex transformations from small, focused, testable pieces. pipe applies functions left-to-right; compose applies right-to-left.

    javascript
    const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);
    
    const trim        = s => s.trim();
    const toLowerCase = s => s.toLowerCase();
    const slugify     = s => s.replace(/\s+/g, "-");
    
    const toSlug = pipe(trim, toLowerCase, slugify);
    
    toSlug("  Hello World  ");   // "hello-world"
    
    // Each step is independently testable ✓
    // Add / remove steps without touching others ✓
    Meta-programming & Object Control

    A Proxy wraps an object and intercepts fundamental operations — property reads, writes, function calls, and more — via traps. This enables validation, logging, auto-defaults, reactive systems (like Vue 3's reactivity), and access control without modifying the original object.

    javascript
    function createValidator(target, schema) {
      return new Proxy(target, {
        set(obj, prop, value) {
          if (schema[prop] && typeof value !== schema[prop]) {
            throw new TypeError(`${prop} must be a ${schema[prop]}`);
          }
          obj[prop] = value;
          return true;
        }
      });
    }
    
    const user = createValidator({}, { name: "string", age: "number" });
    
    user.name = "Alice";   // ✓
    user.age  = 30;        // ✓
    user.age  = "thirty";  // TypeError: age must be a number ⚠️

    Object.freeze() prevents adding, removing, and changing properties — the object becomes fully immutable at the top level. Object.seal() allows changing existing property values but prevents adding or removing properties. Neither operates deeply on nested objects.

    javascript
    // freeze — nothing can change
    const config = Object.freeze({ host: "localhost", port: 3000 });
    config.port = 8080;    // silently ignored (TypeError in strict mode)
    config.debug = true;   // silently ignored
    console.log(config.port);  // 3000
    
    // seal — values changeable, structure locked
    const settings = Object.seal({ theme: "dark", lang: "en" });
    settings.theme = "light";  // ✓  changing existing value
    settings.zoom  = 1.5;      // ✗  ignored — can't add new keys
    console.log(Object.isSealed(settings));   // true
    console.log(Object.isFrozen(config));     // true

    All three return arrays of an object's own enumerable string-keyed properties, excluding inherited ones. keys() returns property names, values() returns property values, and entries() returns [key, value] pairs. entries() is particularly useful for converting objects to Maps or iterating with both key and value.

    javascript
    const scores = { alice: 92, bob: 85, carol: 97 };
    
    Object.keys(scores);    // ["alice", "bob", "carol"]
    Object.values(scores);  // [92, 85, 97]
    Object.entries(scores); // [["alice",92], ["bob",85], ["carol",97]]
    
    // Find top scorer
    const top = Object.entries(scores)
      .sort(([, a], [, b]) => b - a)[0];
    console.log(`${top[0]}: ${top[1]}`);  // "carol: 97"
    
    // Convert object to Map
    const map = new Map(Object.entries(scores));
    
    // Rebuild object from entries (transform values)
    const curved = Object.fromEntries(
      Object.entries(scores).map(([k, v]) => [k, Math.min(v + 5, 100)])
    );

    A tagged template prefixes a template literal with a function. That function receives the static string parts and the interpolated values as separate arguments, giving full control over how the result is assembled. Common uses include SQL query builders, HTML sanitizers, internationalization (i18n), and styled-components in React.

    javascript
    // Safe HTML escaping tag
    function html(strings, ...values) {
      const escape = s => String(s)
        .replace(/&/g, "&")
        .replace(//g, ">")
        .replace(/"/g, """);
    
      return strings.reduce((result, str, i) =>
        result + str + (values[i] !== undefined ? escape(values[i]) : ""), "");
    }
    
    const userInput = '<script>alert("xss")</script>';
    const safeHTML  = html`

    Hello, ${userInput}!

    `; //

    Hello, <script>alert("xss")</script>!

    Array Tricks

    flatMap() runs a mapping function on each element and then flattens the result by one level. It is equivalent to .map(...).flat(1) but more efficient in a single pass. Use it whenever each element can produce zero, one, or many output items — for example, splitting words into characters, or expanding a dataset.

    javascript
    // map alone creates nested arrays
    ["Hello", "World"].map(w => w.split(""));
    // [["H","e","l","l","o"], ["W","o","r","l","d"]]
    
    // flatMap flattens one level automatically
    ["Hello", "World"].flatMap(w => w.split(""));
    // ["H","e","l","l","o","W","o","r","l","d"]
    
    // Practical: expand orders into line items
    const orders = [
      { id: 1, items: ["book", "pen"] },
      { id: 2, items: ["laptop"] },
    ];
    orders.flatMap(o => o.items);  // ["book", "pen", "laptop"]
    
    // Return [] to filter items out, value to keep
    const parsed = ["1", "two", "3"].flatMap(s => {
      const n = Number(s);
      return isNaN(n) ? [] : [n];   // skip non-numbers
    });
    // [1, 3]

    .at() accepts negative indices, where -1 means the last element, -2 the second-to-last, and so on. With bracket notation you have to write arr[arr.length - 1] every time. .at() also works on strings and typed arrays.

    javascript
    const scores = [10, 20, 30, 40, 50];
    
    // Old way
    scores[scores.length - 1];   // 50
    scores[scores.length - 2];   // 40
    
    // at() — clean and readable
    scores.at(-1);   // 50
    scores.at(-2);   // 40
    scores.at(0);    // 10  (positive index also works)
    
    // Works on strings too
    "hello".at(-1);  // "o"
    
    // Chaining without saving to a variable
    [1, 2, 3].map(x => x * 2).at(-1);  // 6

    Array.from() accepts any iterable or array-like object and converts it to a real array. Passing { length: N } as the first argument creates a sparse-free array of N slots. The optional second argument is a mapping function applied to each index — making it a concise range generator.

    javascript
    // Generate a range [0..4]
    Array.from({ length: 5 }, (_, i) => i);
    // [0, 1, 2, 3, 4]
    
    // Generate [1..10]
    Array.from({ length: 10 }, (_, i) => i + 1);
    // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    
    // Convert NodeList to array (DOM)
    const divs = Array.from(document.querySelectorAll("div"));
    divs.filter(el => el.classList.contains("active"));
    
    // Convert Set to array
    Array.from(new Set([1, 2, 2, 3]));  // [1, 2, 3]

    findLast() searches an array from right to left and returns the last element that satisfies the predicate. findLastIndex() does the same but returns the index instead of the value. They save you from reversing the array or writing workarounds when you need the last match.

    javascript
    const logs = [
      { level: "info",  msg: "started" },
      { level: "error", msg: "timeout" },
      { level: "info",  msg: "retrying" },
      { level: "error", msg: "failed" },
    ];
    
    // Last error — no reverse() needed
    logs.findLast(l => l.level === "error");
    // { level: "error", msg: "failed" }
    
    logs.findLastIndex(l => l.level === "error");
    // 3
    
    // Old workaround (avoid this)
    [...logs].reverse().find(l => l.level === "error");
    Operator Shortcuts

    Logical assignment combines a logical operator with assignment, but only assigns when the condition would act. x &&= y assigns only if x is truthy. x ||= y assigns only if x is falsy. x ??= y assigns only if x is null or undefined. They short-circuit — the right side is never evaluated if the condition isn't met.

    javascript
    // &&= — update only if property exists/truthy
    user.name &&= user.name.trim();
    // same as: if (user.name) user.name = user.name.trim();
    
    // ||= — provide default for falsy values
    settings.theme ||= "light";
    // same as: settings.theme = settings.theme || "light";
    
    // ??= — provide default only for null/undefined
    config.retries ??= 3;
    // same as: config.retries = config.retries ?? 3;
    // NOTE: config.retries = 0 would NOT be overwritten by ??= ✓

    && stops and returns the first falsy value; if all are truthy it returns the last value. || stops and returns the first truthy value. This means you can use them for conditional execution, default values, and guarded function calls — without writing a full if statement.

    javascript
    // && — run only if condition is truthy
    isLoggedIn && renderDashboard();
    
    // || — first truthy wins (default values)
    const port = process.env.PORT || 3000;
    
    // Conditional rendering pattern (common in React JSX)
    const ui = isLoading && "<Spinner />";
    
    // Chained guard
    user && user.profile && user.profile.avatar && showAvatar(user.profile.avatar);
    // cleaner with optional chaining:
    user?.profile?.avatar && showAvatar(user.profile.avatar);

    ES6 destructuring makes swapping a one-liner. The right-hand side is evaluated first as an array, then unpacked into the left-hand side variables simultaneously — no temporary variable needed. This also works for swapping elements inside an array.

    javascript
    // Old way — temp variable
    let a = 1, b = 2;
    let tmp = a; a = b; b = tmp;
    
    // ES6 destructuring swap
    let x = 1, y = 2;
    [x, y] = [y, x];
    console.log(x, y);  // 2 1
    
    // Swap elements inside an array
    const arr = [10, 20, 30, 40];
    [arr[0], arr[3]] = [arr[3], arr[0]];
    console.log(arr);   // [40, 20, 30, 10]
    Async Patterns

    Promise.allSettled() waits for all promises to finish — fulfilled or rejected — and returns an array of result objects with a status field. Unlike Promise.all(), it never short-circuits on failure. Use it when you need to process every result regardless of whether some failed.

    javascript
    const requests = [
      fetch("/api/users"),
      fetch("/api/posts"),
      fetch("/api/broken-endpoint"),
    ];
    
    // Promise.all — throws on first failure, others ignored
    // Promise.allSettled — waits for all ✓
    const results = await Promise.allSettled(requests);
    
    results.forEach(result => {
      if (result.status === "fulfilled") {
        console.log("OK:", result.value);
      } else {
        console.warn("Failed:", result.reason);
      }
    });
    
    // Extract only successful ones
    const ok = results
      .filter(r => r.status === "fulfilled")
      .map(r => r.value);

    AbortController provides a signal you pass to fetch. Calling .abort() on the controller cancels the request and causes the fetch promise to reject with an AbortError. This is essential for preventing stale results in search-as-you-type UIs and cleaning up on component unmount.

    javascript
    async function searchUsers(query) {
      const controller = new AbortController();
    
      // Cancel after 5 seconds
      const timeout = setTimeout(() => controller.abort(), 5000);
    
      try {
        const res = await fetch(`/api/users?q=${query}`, {
          signal: controller.signal,
        });
        clearTimeout(timeout);
        return await res.json();
      } catch (err) {
        if (err.name === "AbortError") {
          console.log("Request was cancelled");
        } else {
          throw err;
        }
      }
    }
    
    // Cancel previous search on new input (React pattern)
    useEffect(() => {
      const controller = new AbortController();
      fetchData(query, controller.signal);
      return () => controller.abort();   // cleanup ✓
    }, [query]);

    queueMicrotask() schedules a callback in the microtask queue, which drains before any macrotask (including setTimeout). Use it when you need to defer work until after the current synchronous execution finishes, but before the browser renders or runs timers — for example, batching DOM updates.

    javascript
    console.log("1 – sync start");
    
    setTimeout(() => console.log("4 – macrotask"), 0);
    
    queueMicrotask(() => console.log("3 – microtask"));
    
    Promise.resolve().then(() => console.log("2 – promise microtask"));
    
    console.log("1 – sync end");
    
    // Output order:
    // 1 – sync start
    // 1 – sync end
    // 2 – promise microtask   (microtask queue)
    // 3 – microtask           (microtask queue)
    // 4 – macrotask           (macrotask queue — runs last)
    Object Tips & Tricks

    Property shorthand lets you omit the value when a variable has the same name as the desired key. Computed property names let you use any expression inside [] as a key at object creation time. Together they enable highly dynamic, readable object construction.

    javascript
    const name = "Alice";
    const age  = 30;
    
    // Shorthand — no need to write { name: name, age: age }
    const user = { name, age };   // { name: "Alice", age: 30 }
    
    // Computed property names
    const field = "email";
    const profile = { [field]: "alice@example.com" };
    // { email: "alice@example.com" }
    
    // Dynamic key from expression
    const prefix = "get";
    const api = {
      [`${prefix}User`]:   () => fetch("/user"),
      [`${prefix}Orders`]: () => fetch("/orders"),
    };
    api.getUser();

    Object.hasOwn(obj, key) is a static method that safely checks whether an object has a property as its own (not inherited). obj.hasOwnProperty() can fail if the object has a null prototype (created with Object.create(null)) or if the property hasOwnProperty has been overridden — Object.hasOwn() avoids both pitfalls.

    javascript
    // Dangerous — can be shadowed
    const obj1 = { hasOwnProperty: () => false };
    obj1.hasOwnProperty("key");    // false ⚠️ (method was overridden)
    
    // Dangerous — null-prototype objects have no method
    const obj2 = Object.create(null);
    obj2.key = "value";
    obj2.hasOwnProperty("key");    // TypeError ⚠️
    
    // Safe in both cases ✓
    Object.hasOwn(obj1, "hasOwnProperty");  // true
    Object.hasOwn(obj2, "key");             // true
    
    // Common use: guard before accessing a property
    if (Object.hasOwn(config, "timeout")) {
      applyTimeout(config.timeout);
    }

    padStart(targetLength, fillString) pads the beginning of a string until it reaches the target length. padEnd() does the same at the end. Both are non-destructive and return a new string. They shine for formatting numbers, aligning table columns, and building fixed-width identifiers.

    javascript
    // Zero-pad invoice numbers
    String(42).padStart(6, "0");       // "000042"
    String(1234).padStart(6, "0");     // "001234"
    
    // Align a table
    const rows = [["Alice", "92"], ["Bob", "8"], ["Carol", "100"]];
    rows.forEach(([name, score]) => {
      console.log(name.padEnd(10) + score.padStart(4));
    });
    // "Alice      92"
    // "Bob         8"
    // "Carol     100"
    
    // Mask sensitive data
    const card = "4111111111111234";
    const masked = card.slice(-4).padStart(card.length, "*");
    // "************1234"
    Browser Observer APIs

    IntersectionObserver fires a callback when a target element enters or leaves the viewport (or a specified root). It replaces expensive scroll event listeners with getBoundingClientRect() polling. Common uses: lazy-loading images, infinite scroll, animation triggers on scroll, and ad visibility tracking.

    javascript
    // Lazy-load images when they enter the viewport
    const observer = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          const img = entry.target;
          img.src = img.dataset.src;    // load actual image
          observer.unobserve(img);      // stop watching once loaded
        }
      });
    }, { threshold: 0.1 });            // trigger at 10% visibility
    
    document.querySelectorAll("img[data-src]")
      .forEach(img => observer.observe(img));

    ResizeObserver notifies you whenever a specific element's size changes — not the window, but any DOM element. This is essential for component-level responsive behaviour (charts, canvas, text overflow), replacing fragile window resize listeners that don't catch container size changes caused by layout shifts.

    javascript
    const chart = document.getElementById("chart");
    
    const ro = new ResizeObserver(entries => {
      for (const entry of entries) {
        const { width, height } = entry.contentRect;
        console.log(`Chart is now ${width}×${height}px`);
        redrawChart(width, height);    // re-render on every size change
      }
    });
    
    ro.observe(chart);
    
    // Stop watching
    ro.unobserve(chart);
    // or disconnect all observations:
    ro.disconnect();

    MutationObserver watches for changes in the DOM tree — added/removed nodes, attribute changes, and text content changes. It is the modern replacement for the deprecated DOM mutation events. Use it to react to third-party DOM changes, implement undo history, or detect when a specific element appears.

    javascript
    const observer = new MutationObserver(mutations => {
      mutations.forEach(mutation => {
        if (mutation.type === "childList") {
          mutation.addedNodes.forEach(node => {
            if (node.matches?.(".ad-banner")) {
              node.remove();   // auto-remove injected elements
            }
          });
        }
        if (mutation.type === "attributes") {
          console.log(`Attr "${mutation.attributeName}" changed`);
        }
      });
    });
    
    observer.observe(document.body, {
      childList:  true,    // watch for added/removed children
      subtree:    true,    // include all descendants
      attributes: true,    // watch attribute changes
    });
    
    observer.disconnect();  // stop when done
    JavaScript Succinctly

    Intermediate & Advanced Topics

    Intermediate — Functions, Objects & Scope

    When you call a function with new, JavaScript performs four steps automatically: (1) creates a fresh empty object, (2) links that object's [[Prototype]] to the constructor's prototype, (3) runs the constructor with this pointing to the new object, (4) returns the new object — unless the constructor explicitly returns a different object.

    javascript
    function Person(name, age) {
      // 'this' is the newly created object
      this.name = name;
      this.age  = age;
    }
    Person.prototype.greet = function () {
      return `Hi, I'm ${this.name}`;
    };
    
    const alice = new Person("Alice", 30);
    alice.greet();                           // "Hi, I'm Alice"
    alice instanceof Person;                 // true
    Object.getPrototypeOf(alice) === Person.prototype; // true
    
    // What new does internally (manual equivalent)
    function myNew(Constructor, ...args) {
      const obj = Object.create(Constructor.prototype); // steps 1-2
      const result = Constructor.apply(obj, args);       // step 3
      return result instanceof Object ? result : obj;    // step 4
    }

    Object.create(proto) creates a new empty object whose [[Prototype]] is set to proto — with no constructor call involved. Pass null to create a truly blank object with no prototype at all (no toString, no hasOwnProperty). It is the most direct way to set up prototype delegation without needing classes or constructor functions.

    javascript
    const animal = {
      describe() { return `I am a ${this.type}`; }
    };
    
    // dog inherits from animal — no constructor needed
    const dog = Object.create(animal);
    dog.type = "dog";
    dog.describe();  // "I am a dog"
    
    Object.getPrototypeOf(dog) === animal;  // true
    
    // Null prototype — pure dictionary, no inherited methods
    const dict = Object.create(null);
    dict.key = "value";
    // dict.toString  → undefined (intentionally, no prototype) ✓
    // Useful for caches/maps where inherited keys could cause bugs

    In non-arrow functions, arguments is an array-like object (not a real array) containing all passed arguments, even undeclared ones. It is outdated: it does not work in arrow functions, has no array methods, and makes intent unclear. Always prefer rest parameters (...args), which are real arrays and work everywhere.

    javascript
    // Old way — arguments object
    function sumOld() {
      let total = 0;
      for (let i = 0; i < arguments.length; i++) {
        total += arguments[i];
      }
      return total;
    }
    sumOld(1, 2, 3);  // 6
    
    // ❌ arguments is array-like, not a real array
    arguments.map    // undefined — no array methods!
    arguments.reduce // undefined
    
    // ✓ Modern — rest parameters are a real Array
    function sumNew(...nums) {
      return nums.reduce((a, b) => a + b, 0);
    }
    sumNew(1, 2, 3, 4);  // 10
    nums.map(n => n * 2); // works ✓

    typeof returns a string for primitive types and "object" for most reference types — it cannot distinguish between an array, a Date, or a plain object. instanceof walks the prototype chain to check whether an object was created by a specific constructor — it works across class hierarchies but fails across iframes.

    javascript
    // typeof — good for primitives
    typeof "hello"    // "string"
    typeof 42         // "number"
    typeof true       // "boolean"
    typeof undefined  // "undefined"
    typeof null       // "object"  ← bug!
    typeof []         // "object"  ← can't distinguish!
    typeof {}         // "object"
    typeof function(){} // "function"  ← special case
    
    // instanceof — good for objects
    [] instanceof Array     // true
    [] instanceof Object    // true  (Array inherits from Object)
    new Date() instanceof Date  // true
    
    // Best-in-class type detection
    Array.isArray([])        // true  ← use for arrays
    Object.prototype.toString.call(new Date())  // "[object Date]"
    Object.prototype.toString.call([])          // "[object Array]"

    When a variable is declared in an inner scope with the same name as one in an outer scope, the inner variable shadows the outer one within that scope. JavaScript resolves variable names by walking up the scope chain from the innermost scope outward until it finds a match or reaches the global scope.

    javascript
    let message = "global";
    
    function outer() {
      let message = "outer";   // shadows global
    
      function inner() {
        let message = "inner"; // shadows outer
        console.log(message);  // "inner"
      }
    
      inner();
      console.log(message);    // "outer"
    }
    
    outer();
    console.log(message);      // "global"
    
    // Accidental shadowing is a common bug source
    function processUser(user) {
      if (user.isAdmin) {
        const user = getAdminData(); // ← shadows param! bug-prone
        console.log(user);
      }
      console.log(user);  // original user — not affected in this case
    }
    Advanced — Prototypes, Descriptors & Patterns

    Object.defineProperty() adds or modifies a property with fine-grained control via a descriptor. You can make a property non-writable (read-only), non-enumerable (hidden from loops and JSON.stringify), and non-configurable (can't be deleted or re-defined). This is the low-level mechanism that Object.freeze() uses internally.

    javascript
    const config = {};
    
    Object.defineProperty(config, "API_URL", {
      value:        "https://api.example.com",
      writable:     false,   // cannot be reassigned
      enumerable:   false,   // hidden from for...in / Object.keys
      configurable: false,   // cannot be deleted or redefined
    });
    
    config.API_URL = "https://evil.com";  // silently fails (strict → TypeError)
    console.log(config.API_URL);          // "https://api.example.com" — unchanged ✓
    
    Object.keys(config);     // []  — not enumerable, hidden ✓
    
    // Multiple at once
    Object.defineProperties(config, {
      VERSION: { value: "2.0", writable: false, enumerable: true },
      DEBUG:   { value: false,  writable: true,  enumerable: true },
    });

    [[Prototype]] is the internal hidden link every object has — it is what the engine walks when looking up a missing property. .prototype is a plain property on function objects that becomes the [[Prototype]] of objects created with new. __proto__ is a legacy accessor that exposes [[Prototype]] — use Object.getPrototypeOf() instead.

    javascript
    function Dog(name) { this.name = name; }
    Dog.prototype.bark = function () { return "Woof!"; };
    
    const rex = new Dog("Rex");
    
    // .prototype — property on the constructor function
    Dog.prototype;                         // { bark: f, constructor: Dog }
    
    // [[Prototype]] of rex — exposed via getPrototypeOf
    Object.getPrototypeOf(rex) === Dog.prototype;  // true
    
    // __proto__ — legacy, deprecated, avoid
    rex.__proto__ === Dog.prototype;       // true (but don't use this)
    
    // Lookup chain for rex.bark:
    // rex → Dog.prototype → Object.prototype → null
    rex.bark();                            // "Woof!"  (found on Dog.prototype)
    rex.toString();                        // found on Object.prototype

    Classical inheritance (Java, C++) copies behavior from a parent class into child classes at compile time — the relationship is rigid. JavaScript's prototypal delegation is a live link: objects delegate to other objects at runtime. Adding a method to a prototype instantly makes it available on all existing objects that delegate to it, with no copying involved.

    javascript
    const vehicle = {
      describe() { return `A ${this.type} going ${this.speed} km/h`; }
    };
    
    const car = Object.create(vehicle);
    car.type  = "car";
    car.speed = 120;
    
    const bike = Object.create(vehicle);
    bike.type  = "bike";
    bike.speed = 30;
    
    car.describe();    // "A car going 120 km/h"
    bike.describe();   // "A bike going 30 km/h"
    
    // Add to the prototype AFTER objects were created — they get it instantly
    vehicle.stop = function () { return `${this.type} stopped.`; };
    
    car.stop();   // "car stopped."  ← method wasn't there when car was created ✓

    The Revealing Module Pattern uses an IIFE to create a private scope and returns only the public API, "revealing" selected names. It solved the global-namespace pollution problem before native modules existed. ES Modules formalized this idea — each file has its own scope, and only exported names are accessible from outside.

    javascript
    // Revealing Module Pattern (pre-ES6)
    const CartModule = (() => {
      let items = [];                          // private
    
      function add(item)    { items.push(item); }
      function remove(item) { items = items.filter(i => i !== item); }
      function total()      { return items.reduce((s, i) => s + i.price, 0); }
    
      return { add, remove, total };           // reveal public API only
    })();
    
    CartModule.add({ name: "Book", price: 12 });
    CartModule.items;  // undefined — private ✓
    
    // Modern ES Module equivalent (cart.js)
    let items = [];
    export const add    = item  => items.push(item);
    export const remove = item  => items = items.filter(i => i !== item);
    export const total  = ()    => items.reduce((s, i) => s + i.price, 0);

    Modern JavaScript engines use mark-and-sweep: starting from root objects (global, current stack frames), the GC marks everything reachable, then sweeps away everything unmarked. An object is collected when no reachable reference points to it — not when a reference counter hits zero (which fails with circular references). This is why closures, detached DOM nodes, and forgotten event listeners can cause leaks.

    javascript
    // ✓ Eligible for GC — nothing references it after function exits
    function create() {
      let data = new Array(100_000).fill(0);
      return data.reduce((s, n) => s + n, 0);
    }
    create(); // data is collected after function returns
    
    // ❌ Leak — closure keeps data alive indefinitely
    let leak;
    function setup() {
      const data = new Array(100_000).fill(0);
      leak = () => data.length;  // leak holds a reference to data forever
    }
    
    // ❌ Leak — detached DOM node kept in memory
    let detached = document.getElementById("removed-element");
    document.body.removeChild(detached);
    // detached variable still holds the node — GC can't free it
    
    // ✓ Fix
    detached = null;  // release the reference
    Prev
    Next
    Drag
    Map
    HTML Snippets Powered By : XYZScripts.com
    Select the fields to be shown. Others will be hidden. Drag and drop to rearrange the order.
    • Image
    • SKU
    • Rating
    • Price
    • Stock
    • Availability
    • Add to cart
    • Description
    • Content
    • Weight
    • Dimensions
    • Additional information
    Click outside to hide the comparison bar
    Compare