console.log(5 == "5"); // true (Coercion)
console.log(5 === "5"); // false (Strict)
// From Lodash .eq source
function eq(value, other) {
return value === other || (value !== value && other !== other);
}
// Uses strict equality but handles NaN (value !== value)
== (Loose Equality): Performs type coercion before comparing. 5 == "5" is true.
=== (Strict Equality): Checks both value and type. 5 === "5" is false.
Senior Note: Always use ===. The only exception might be checking for null/undefined simultaneously with if (x == null), but even then, explicit checks are preferred.
const name = user.name || "Guest"; // Fallback
const age = user.age ?? 18; // Nullish check
return (
<div>
{isLoggedIn && <UserProfile />}
{errorMessage || <DefaultMessage />}
</div>
);
Used heavily in React for conditional rendering.
// AND (&&): Returns first falsy or last truthy
const show = true && "Display Me"; // "Display Me"
// OR (||): Returns first truthy
const name = inputName || "Default"; // "Default" if inputName is ""
// Nullish Coalescing (??): Returns right side ONLY if left is null/undefined
const count = 0 ?? 10; // 0 (because 0 is not null)
const count2 = 0 || 10; // 10 (because 0 is falsy)
let count = 1;
count = 2; // OK
const pi = 3.14;
pi = 3; // TypeError: Assignment to constant variable.
// 'state' is const because we don't reassign it directly.
// We use the setter function to trigger updates.
const [state, setState] = useState(0);
const user = { name: "A" };
user.name = "B"; // Allowed (Mutation)
user = { name: "C" }; // Error (Re-assignment)
function outer() {
let x = 10;
return () => x; // Remembers x
}
const getX = outer();
console.log(getX()); // 10
useEffect(() => {
const timer = setInterval(() => {
console.log(count); // Closure captures 'count'
}, 1000);
return () => clearInterval(timer);
}, [count]); // Dependency array updates the closure
A closure is a function that remembers the variables from the scope where it was defined, even after that scope has closed.
function createCounter() {
let count = 0; // Private variable
return function() {
count++;
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
// 'count' is inaccessible from outside
console.log(x); // undefined (var is hoisted)
var x = 5;
console.log(y); // ReferenceError (let is in TDZ)
let y = 10;
// Function declarations are hoisted, allowing top-down reading
export default function App() {
return <Helper />;
}
function Helper() { // Defined below, used above
return <div>Helper</div>;
}
JavaScript's behavior of moving declarations to the top of the scope. var and function declarations are hoisted. let and const are hoisted but stay in the "Temporal Dead Zone" (TDZ) until initialized.
console.log(1);
setTimeout(() => console.log(2), 0); // Macrotask
Promise.resolve().then(() => console.log(3)); // Microtask
console.log(4);
// Output: 1, 4, 3, 2
// Deferring heavy calculation to avoid freezing UI
function handleHeavyTask() {
setTimeout(() => {
heavyCalculation(); // Runs in next tick
}, 0);
}
JS is single-threaded. The Event Loop checks if the Call Stack is empty. If yes, it moves items from the Callback Queue (Macrotasks) or Microtask Queue (Promises) to the Call Stack to be executed.
const obj = {
name: 'Me',
say: function() { console.log(this.name) },
arrow: () => console.log(this.name)
};
obj.say(); // 'Me'
obj.arrow(); // undefined (inherits global/window)
class Button extends React.Component {
handleClick = () => {
// Arrow function auto-binds 'this' to the instance
console.log(this.props.label);
}
}
It refers to the object that is executing the current function. In an arrow function, `this` is lexically scoped (inherited from parent). In a regular function, it depends on how the function is called.
const animal = { eats: true };
const rabbit = Object.create(animal);
console.log(rabbit.eats); // true (inherited)
if (!Array.prototype.includes) {
Array.prototype.includes = function(item) {
// Add method to all Arrays via prototype
return this.indexOf(item) !== -1;
};
}
Objects in JS have a hidden property `[[Prototype]]`. If you access a property that doesn't exist on the object, JS looks up the prototype chain until it finds it or hits null.
const add = a => b => a + b;
add(1)(2); // 3
const logger = store => next => action => {
console.log('dispatching', action);
return next(action);
};
Transforming a function that takes multiple arguments into a sequence of functions that each take a single argument. Useful for partial application.
// Promise
fetch(url).then(r => r.json()).then(d => console.log(d));
// Async/Await
const res = await fetch(url);
const data = await res.json();
useEffect(() => {
async function load() {
try {
const data = await api.get();
setData(data);
} catch (e) { setError(e); }
}
load();
}, []);
Async/Await is syntactic sugar over Promises. It makes async code look synchronous.
// Promise Chain
fetch('/api')
.then(res => res.json())
.then(data => console.log(data))
.catch(err => console.error(err));
// Async/Await (Cleaner)
async function getData() {
try {
const res = await fetch('/api');
const data = await res.json();
console.log(data);
} catch (err) {
console.error(err);
}
}
// Declaration (Hoisted)
function add(a, b) { return a + b; }
// Expression (Not Hoisted)
const sub = function(a, b) { return a - b; };
// Named export (Declaration)
export function Button() {}
// Default export (Expression often used)
const Card = () => {};
export default Card;
Declarations are hoisted (can be called before definition). Expressions are not (must be defined before use).
(function() {
const privateVar = "secret";
})();
// privateVar is not accessible here
useEffect(() => {
// IIFE to run async code in useEffect
(async () => {
await fetchData();
})();
}, []);
Immediately Invoked Function Expression. Used to create a private scope and avoid polluting the global namespace.
(function() {
// Private code
})();
function Regular() { this.val = 1; }
const Arrow = () => { this.val = 1; }; // Error if used as constructor
// Regular function needs binding in classes
this.handleClick = this.handleClick.bind(this);
// Arrow function inherits 'this' automatically
handleClick = () => { this.setState(...) };
Arrow functions do not have their own `this`; they inherit it from the parent scope. Regular functions have a dynamic `this` depending on how they are called.
getData(a => {
getMore(a, b => {
getFinal(b, c => {
console.log(c);
});
});
});
// Flattened with Async/Await
const a = await getData();
const b = await getMore(a);
const c = await getFinal(b);
Deeply nested callbacks that make code hard to read and debug. Solved by Promises and Async/Await.
function greet(lang) { console.log(lang, this.name); }
const user = { name: 'Dev' };
greet.call(user, 'JS'); // "JS Dev"
greet.apply(user, ['JS']); // "JS Dev"
const newFn = greet.bind(user); newFn('JS');
// Borrowing Array methods for arguments object
const args = Array.prototype.slice.call(arguments);
call(thisArg, arg1, arg2): Invokes function immediately.apply(thisArg, [args]): Invokes immediately, takes array of args.bind(thisArg): Returns a new function with `this` bound.const original = { a: { b: 1 } };
const shallow = { ...original }; // shallow.a === original.a
const deep = JSON.parse(JSON.stringify(original)); // New object
// Must return new object references for updates
return {
...state,
nested: { ...state.nested, value: 1 }
};
Shallow: Copies top-level properties. Nested objects are still references. ({...obj}, Object.assign).
Deep: Copies all levels. (JSON.parse(JSON.stringify(obj)) or structuredClone(obj)).
const arr = [1, 2, 3];
const doubled = arr.map(x => x * 2); // [2, 4, 6]
arr.forEach(x => console.log(x)); // Returns undefined
// map is used to transform data into elements
{items.map(item => (
<li key={item.id}>{item.name}</li>
))}
map returns a new array. forEach returns undefined (used for side effects).
const nums = [1, 2, 3, 4];
const sum = nums.reduce((acc, curr) => acc + curr, 0); // 10
// Convert array to object map
const userMap = users.reduce((acc, user) => {
acc[user.id] = user;
return acc;
}, {});
Transforms an array into a single value (number, object, another array) by applying a function to an accumulator and each element.
const nums = [1, 2, 3, 4];
const evens = nums.filter(n => n % 2 === 0); // [2, 4]
const firstEven = nums.find(n => n % 2 === 0); // 2
// Filter for search results
const results = items.filter(i => i.name.includes(query));
// Find for specific item selection
const selected = items.find(i => i.id === selectedId);
filter returns an array of all matching elements. find returns the first matching element (or undefined).
const arr = [3, 1, 2];
arr.sort(); // Mutates arr to [1, 2, 3]
const sorted = arr.toSorted(); // New array (ES2023)
// Avoid mutation in React
// Bad: state.push(item)
// Good:
setState([...state, item]);
Mutating: push, pop, shift, unshift, splice, sort, reverse.
Non-Mutating: map, filter, reduce, slice, concat, toSorted (ES2023).
const obj = { x: 1 };
obj.x = 2; // Allowed
Object.freeze(obj);
obj.x = 3; // Error in strict mode
const CONFIG = Object.freeze({
API_URL: 'https://api.example.com',
TIMEOUT: 5000
});
const prevents reassignment of the variable identifier. Object.freeze prevents modification of the object's properties.
const street = user?.address?.street;
// Equivalent to:
// user && user.address && user.address.street
// Handling incomplete data
const avatar = data?.user?.profile?.avatarUrl || defaultAvatar;
Allows reading the value of a property located deep within a chain of connected objects without having to expressly validate that each reference in the chain is valid. user?.address?.street.
const [first, second] = [10, 20];
const { name, age } = { name: 'A', age: 25 };
function UserCard({ name, email, ...rest }) {
return <div {...rest}>{name} ({email})</div>;
}
Extracting values from arrays or properties from objects into distinct variables. const { name } = user;.
// Spread
const combined = [...arr1, ...arr2];
// Rest
function sum(...args) { return args.reduce((a,b)=>a+b); }
setUser(prev => ({
...prev, // Spread existing properties
name: 'New Name' // Overwrite one
}));
Spread: Expands an iterable into elements. [...arr].
Rest: Collects multiple elements into an array. function(...args) {}.
const unique = new Set([1, 1, 2]); // {1, 2}
const map = new Map();
map.set(keyObj, "value");
const uniqueTags = [...new Set(allTags)];
Set: Collection of unique values.
Map: Collection of key-value pairs where keys can be any type (objects, functions), unlike Objects where keys are strings/symbols.
// Bubbling (Default): Child -> Parent
// Capturing: Parent -> Child
element.addEventListener('click', handler, { capture: true });
// Capture all clicks on the page for tracking
document.addEventListener('click', (e) => {
trackClick(e.target);
}, true); // Use capture to ensure it runs before stopPropagation
Bubbling: Event starts at target and goes up to window (default).
Capturing: Event goes down from window to target.
document.getElementById('list').addEventListener('click', (e) => {
if (e.target.tagName === 'LI') {
console.log('Clicked item:', e.target.textContent);
}
});
// React uses delegation automatically at the root.
// But in vanilla JS, this saves memory for large lists.
const table = document.querySelector('table');
table.onclick = function(event) {
let target = event.target.closest('td');
if (!target) return;
highlight(target);
};
Attaching a single event listener to a parent element to manage events for all its children (using bubbling). Efficient for dynamic lists.
form.onsubmit = (e) => {
e.preventDefault(); // Stop reload
e.stopPropagation(); // Stop bubbling to parent
};
// Prevent closing modal when clicking inside content
<div onClick={closeModal}>
<div onClick={e => e.stopPropagation()}>
Content
</div>
</div>
preventDefault: Stops the browser's default behavior (e.g., form submission).
stopPropagation: Stops the event from bubbling up the DOM.
localStorage.setItem('theme', 'dark'); // Forever
sessionStorage.setItem('tabData', '123'); // Tab life
document.cookie = "user=John; max-age=3600"; // Server sent
// HttpOnly Cookie is best for security (XSS protection)
// LocalStorage is okay for non-sensitive preferences
const theme = localStorage.getItem('theme') || 'light';
// Debounce: Search input
const debouncedSearch = debounce((text) => api.search(text), 300);
// Throttle: Scroll event
const throttledScroll = throttle(() => checkPosition(), 100);
import { debounce } from 'lodash';
// Prevent API spam on every keystroke
<input onChange={e => debouncedSearch(e.target.value)} />
Debounce: Wait for a pause in events before running (e.g., Search input).
Throttle: Run at most once every X ms (e.g., Scroll event).
let user = { name: "John" };
user = null; // Object is now unreachable -> GC collects it
// Detached DOM nodes
let detached = document.createElement('div');
// If we keep a reference to 'detached' in a global array,
// it will never be collected even if not in DOM.
JS engine automatically frees memory. "Mark and Sweep" algorithm marks reachable objects and sweeps (deletes) unreachable ones.
// Forgotten Timer
setInterval(() => {
// This runs forever if not cleared
}, 1000);
useEffect(() => {
const sub = api.subscribe();
// FORGETTING THIS causes leak on unmount:
return () => sub.unsubscribe();
}, []);
Common causes: Global variables, Uncleared intervals/timeouts, Event listeners not removed, Closures holding references.
// CJS
const fs = require('fs');
module.exports = fn;
// ESM
import fs from 'fs';
export default fn;
// ESM allows bundlers to remove unused code
import { Button } from 'library'; // Only bundles Button
CommonJS: Node.js default. require/module.exports. Synchronous.
ESM: Browser standard. import/export. Asynchronous/Static analysis.
function* idMaker() {
let index = 0;
while(true) yield index++;
}
const gen = idMaker();
console.log(gen.next().value); // 0
function* fetchUser(action) {
try {
const user = yield call(Api.fetchUser, action.payload.userId);
yield put({type: "USER_FETCH_SUCCEEDED", user: user});
} catch (e) {
yield put({type: "USER_FETCH_FAILED", message: e.message});
}
}
Functions that can be paused and resumed. Used in Redux-Saga.
let obj = { id: 1 };
const weakMap = new WeakMap();
weakMap.set(obj, "metadata");
obj = null; // Entry in weakMap is automatically removed
const privateData = new WeakMap();
class User {
constructor(name) {
privateData.set(this, { secret: '123' });
}
}
WeakMap keys must be objects and are weakly held, meaning they don't prevent garbage collection if the object is not used elsewhere.
console.log(Number("abc")); // NaN
console.log(NaN === NaN); // false
function safeDivide(a, b) {
const res = a / b;
if (Number.isNaN(res)) return 0;
return res;
}
Result of invalid math. typeof NaN === 'number'. NaN !== NaN (Use Number.isNaN()).
let a; // undefined
let b = null; // null
// undefined: Field missing from JSON
// null: Field exists but has no value
{ "name": "John", "middleName": null }
undefined: Variable declared but not assigned.
null: Intentional absence of value.
console.log(1 + "2"); // "12"
console.log("5" - 1); // 4
console.log([] + []); // ""
// API returns string "100"
const total = price + tax; // "10010" if not parsed!
const safeTotal = Number(price) + Number(tax);
Automatic conversion of values. 1 + "2" = "12". "5" - 1 = 4.
"use strict";
x = 3.14; // ReferenceError (x is not defined)
// ES Modules are strict by default.
// You don't need to add "use strict" in React/Vite projects.
Enforces stricter parsing and error handling. Prevents using undeclared variables, duplicate parameter names, etc.
try {
throw new Error("Oops");
} catch (e) {
console.error(e);
} finally {
console.log("Cleanup");
}
setLoading(true);
try {
await fetchData();
} catch (e) {
setError(e);
} finally {
setLoading(false); // Runs even if error occurs
}
finally block executes regardless of whether an error occurred or not. Useful for cleanup.
class AuthError extends Error {
constructor(msg) {
super(msg);
this.name = "AuthError";
}
}
if (response.status === 401) {
throw new AuthError("Session expired");
}
// Catch block can check: if (e instanceof AuthError) ...
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
}
}
const sym1 = Symbol('foo');
const sym2 = Symbol('foo');
console.log(sym1 === sym2); // false
const ID = Symbol('id');
user[ID] = 123; // Not visible in for...in loops or JSON.stringify
A primitive data type that is guaranteed to be unique. Often used for object property keys to avoid collisions.
const myIter = {
[Symbol.iterator]() {
let n = 0;
return {
next() { return { value: n++, done: n > 3 } }
};
}
};
for (const n of myIter) console.log(n); // 0, 1, 2
// Making a custom LinkedList iterable so you can use
// for...of loop on it.
An object is an iterator if it has a next() method that returns { value, done }.
const proto = { greet() { return "Hi"; } };
const obj = Object.create(proto);
console.log(obj.greet()); // "Hi"
// Creating objects without constructor functions
// for cleaner composition.
Creates a new object, using an existing object as the prototype of the newly created object.
const obj = { x: 1 };
Reflect.set(obj, 'x', 2);
console.log(obj.x); // 2
const handler = {
get(target, prop, receiver) {
console.log(`Getting ${prop}`);
return Reflect.get(target, prop, receiver);
}
};
Built-in object that provides methods for interceptable JavaScript operations. Used with Proxies.
const target = {};
const proxy = new Proxy(target, {
get: (obj, prop) => prop in obj ? obj[prop] : 'Not Found'
});
console.log(proxy.foo); // "Not Found"
// Validate property setting
set: (obj, prop, value) => {
if (prop === 'age' && typeof value !== 'number') return false;
obj[prop] = value;
return true;
}
Enables you to create a proxy for another object, which can intercept and redefine fundamental operations for that object (like getting/setting properties).
{
// TDZ starts here
console.log(x); // ReferenceError
let x = 10; // TDZ ends here
}
// Helps catch bugs where variables are used before initialization
// unlike 'var' which would just be undefined.
The time between entering a scope and the actual declaration of a let or const variable. Accessing it throws a ReferenceError.
console.log(globalThis);
// Window (Browser) or Global (Node)
// Writing code that runs on both Server (SSR) and Client
if (typeof globalThis.window === 'undefined') {
// Server side logic
}
Standard way to access the global object across environments (window in browser, global in Node).
const big = 9007199254740991n;
const sum = big + 1n;
// High precision calculations where floating point math fails
// 0.1 + 0.2 !== 0.3 (Float)
// BigInt avoids this for integers.
Primitive for integers larger than 2^53 - 1. Created by appending 'n' to an integer literal.