Learn

Basics

Showing 52 topics

  • It is a just-in-time compiled language that runs everywhere, from browsers to servers to mobile apps.
  • JS supports dynamic typing, meaning variables don't need a type declaration. It introduces type safety concerns, but now we have TS, a superset of JS has static typing also with better IDE support.
  • It's prototype-based, meaning objects inherit directly from other objects.
  • It's single-threaded; JS uses the event loop for concurrency.
  • It supports first-class function, meaning function can be treated as a variable (Stored in variables; Passed as arguments, returned from other functions)
  • JS engines like V8(Chrome), SpiderMonkey(Firefox) use a JIT compiler to convert JS to optimized machine code.
  • JS gets more powerful after ES6.
  • JS is managing two parallel systems within each Execution Context - a lexical system for variables (static, predictable) and a dynamic system for object context (changes with each call). Closures extend the lexical system's lifetime, while call/apply/bind give you control over the dynamic system.
JavaScript Engine:
├── Single Call Stack (synchronous execution)
├── Two Parallel Systems per Context:
│   ├── Lexical System (variables, scope, closures)
│   └── Dynamic System ('this' binding)
├── Event Loop (asynchronous coordination)
│   ├── Web APIs (external operations)
│   ├── Microtask Queue (high priority)
│   └── Callback Queue (standard priority)
└── Function Types:
    ├── Regular Functions (own 'this', own execution context)
    └── Arrow Functions (inherited 'this', own execution context)
  • Class: the syntactic sugar over prototype inheritance
  • spread operator: shallow copy, convert set/string to array
  • Destructuring: extract multiple values at once
  • Symbols are a primitive data type that creates unique identifiers. For example, a library might use symbols for internal methods that users shouldn't accidentally override.
  • Iterator: a standard way to traverse through a collection of data one at a time. For example, for...of uses iterators under the hood, calls Symbol.iterator to get an iterator object
  • Generator: a special function that can pause and resume its execution with 'yield', lazy evaluation, and generate value on demand, not all at once.
  • Template literal: embedding expressions and variables directly within the string
  • Arrow function
  • Execution context: A container that holds all the information JavaScript needs to run code - variables, functions, this value, and scope references.
  • Call stack: A stack of these containers, showing which code is currently running and what called it, LIFO.
function first() {
  console.log("First function");
  second();
}

function second() {
  console.log("Second function");
}

first();

// Call Stack progression:
// 1. Global Context
// 2. Global Context → first() Context
// 3. Global Context → first() Context → second() Context
// 4. Global Context → first() Context (second pops off)
// 5. Global Context (first pops off)

Inside execution context

  • Variable environment - Where variables and functions are stored
  • Lexical Environment - Where the scope chain is managed
  • this Binding - What this refers to in this context

Lexical Scope (The concept)

  • The rule that says variables are accessible based on where they're written in the code, not where they're called.
function outer() {
  const x = 10;
  function inner() {
    console.log(x); // Can access x due to lexical scope
  }
  inner();
}
  • Lexical Scope: The rule that variables are accessible based on where they're written in the code, not where they're called from.
  • Lexical Environment: The data structure that implements lexical scope - contains current scope's variables plus a reference to the outer scope.
  • Scope Chain: The lookup mechanism that JavaScript uses to find variables by walking up through nested scopes until it finds the variable or reaches global scope.
const global = "I'm global";

function outer() {
  const outerVar = "I'm outer";

  function inner() {
    const innerVar = "I'm inner";
    console.log(innerVar); // Found in current lexical environment
    console.log(outerVar); // Found via scope chain, due to lexical scope
    console.log(global); // Found via scope chain, due to lexical scope
  }

  inner();
}

outer();
  • Closure: When an inner function retains access to variables from its outer function, even after the outer function has finished executing.
  • Closures are created when a function is defined inside another function and references variables from the outer function.
function createCounter() {
  let count = 0;
  return function () {
    return ++count; // Closure preserves 'count'
  };
}

const counter1 = createCounter();

console.log(counter1()); // 1
console.log(counter1()); // 2
  • IIFE (Immediately Invoked Function Expression): A function that runs immediately when defined, often used to create private scope and avoid polluting global namespace.
const module = (function () {
  const private = "secret"; // scoped inside IIFE only

  return {
    getPrivate: () => private, // closure keeps access to it
  };
})();

console.log(module.getPrivate()); // ✅ "secret"
console.log(module.private); // ❌ undefined
console.log(private); // ❌ ReferenceError
  • Currying: A technique that transforms a function taking multiple arguments into a series of functions each taking a single argument, using closures to remember previous arguments.
function curriedAdd(a) {
  return function (b) {
    return a + b;
  };
}
console.log(curriedAdd(2)(3)); // 5

const add2 = curriedAdd(2); // fix 'a = 2'
console.log(add2(5)); // 7
console.log(add2(10)); // 12
  • Instead of IIFE, Modern best practice is to use let/const with block scope, modules, or private class fields for encapsulation.
  • 'this': A reference to an object. Can be different each time the same function is called.
  • 'this' binding: The process of determining what object this points to at runtime based on how the function was invoked.
  • call(): A method that immediately invokes a function with a specified this value and individual argument.
  • apply(): A method that immediately invokes a function with a specified this value and an array of arguments.
  • bind(): A method that creates a new function with this bound to the specified value.
function greet(greeting) {
  return `${greeting}, ${this.name}!`;
}

const person = { name: "Bob" };

// explicit this binding.
greet.call(person, "Hello"); // "Hello, Bob!"
greet.apply(person, ["Hi"]); // "Hi, Bob!"
const boundGreet = greet.bind(person);
boundGreet("Hey"); // "Hey, Bob!"
  • Regular Function: this depends on HOW the function is called. Method call, this refers to the object. Regular function call, this refers to global object, undefined in strict mode.
  • Arrow Function: A function that doesn't have its own this - instead inherits this from the enclosing scope and cannot be used as constructors.
const obj = {
  name: "Alice",

  regularMethod: function () {
    console.log(this.name); // "Alice" - 'this' bound to obj, implicit this binding

    function inner() {
      console.log(this.name); // undefined - 'this' rebound
    }

    const arrow = () => {
      console.log(this.name); // "Alice" - 'this' inherited
    };

    inner();
    arrow();
  },
};

undefined vs null

  • undefined means variable has been declared but not assigned a value. typeof undefined is undefined
  • null is an intentional assignment representing "no value", typeof null is object.
  • Event Loop: The mechanism that allows JavaScript to handle asynchronous operations despite being single-threaded.
  • Call Stack:showing which code is currently running and what called it
  • Macrotask queue: A queue that holds callback functions from completed asynchronous operations, waiting to be executed. For example, setTimeout, DOM event. (Instead of a giant macrotask queue, it breaks down to multiple task queues, interaction queue, timer task queue etc)
  • Microtask Queue: A higher priority queue for Promise callbacks and other microtasks - always processed before the Macrotask queue.
console.log("1"); // Synchronous

fetch("https://jsonplaceholder.typicode.com/users")
  .then((res) => res.json())
  .then((data) => console.log("2")); // Microtasks when fetch resolves

setTimeout(() => console.log("3"), 0); // Macrotask

Promise.resolve().then(() => console.log("4")); // Microtask

console.log("5"); // Synchronous

// Output: 1 5 4 3 2
// Synchronous > Microtask > Macrotask

Event loop process

The event loop continuously monitors the call stack.

If the call stack is empty:

a. It first processes all microtasks in the microtask queue, one by one, until the microtask queue is empty.

b. Then it picks one macrotask from the macrotask queue (also called the callback queue) and executes it.

After the macrotask completes, the event loop goes back to check the microtask queue again before moving on to the next macrotask.

  • Asynchronous behavior means some operation take time to complete, not immediately(fetch data from a server), but we don't want to freeze the program.

Callback - the original solution

  • A callback is simply a function passed to another function.
  • Callback hell: when you have multiple asynchronous functions that depends on each other, nested callback, hard to read and maintain
function fetchUserData(userId, callback) {
  setTimeout(() => {
    const userData = { id: userId, name: "Alice" };
    callback(null, userData); // First param: error, second: data
  }, 1000);
}

fetchUserData(123, (error, user) => {
  if (error) {
    console.log("Error:", error);
  } else {
    console.log("User:", user);
  }
});
// User: { id: 123, name: 'Alice' }

Promise - a better way

  • Promise is the eventual completion of asynchronous operation.
  • It has 3 states, pending, rejected, fulfilled.
  • Promise is chainable, attach .then() to handle result, .catch() to handle error or rejection
function wait(ms) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(`Waited ${ms}ms`);
    }, ms);
  });
}

wait(1000).then(console.log); // "Waited 1000ms" after 1 second
// wait(1000).then(data => console.log(data));

//can use async/await:
async function demo() {
  console.log("Start");
  const msg = await wait(5000); // ⏸ pauses here
  console.log(msg); // " Waited 5000ms"
  console.log("End");
}

demo();

Add function to microtask queue

Promise.resolve().then(fn);

Promise object

const promiseObj = new Promise((resolve) => resolve("Hello!"));
promiseObj.then((result) => console.log(result));

Promise.all

  • Takes an array of promises.
  • Returns a single promise that:
  • Resolves → when all promises succeed → with an array of results.
  • Rejects → immediately when any one promise fails.

Promise.allSettled

  • Takes an array of promises.
  • Returns a single promise that always resolves

Async/await - syntactic sugar of promise

  • It makes asynchronous code look synchronous and still use promise behind the scene, use with try/catch.
  • An async function always returns a Promise, and await pauses the execution until the Promise resolves.
  • Early days: XMLHttpRequest (XHR) is the original browser API for making HTTP requests. Verbose and callback-based
  • ES6+: Fetch API, promise-based, cleaner syntax
  • Libraries: Axios, automatic JSON handling, automatically catches 404, 500 errors, request and response interceptors (middleware function)

Promise Style

function fetchData(url) {
  return fetch(url)
    .then((response) => {
      if (!response.ok) {
        throw new Error("❌ Network response was not ok");
      }
      return response.json();
    })
    .then((data) => {
      console.log("✅ Data:", data);
      return data; // pass data to the next .then() if needed
    })
    .catch((error) => {
      console.error("❌ Fetch error:", error);
    });
}

Promise utilities

const promise = new Promise((resolve, reject) => {
  setTimeout(() => resolve("done"), 1000);
});

promise.then((result) => console.log(result));
promise.catch((error) => console.error(error));
promise.finally(() => console.log("cleanup"));

// Promise utilities
Promise.all([promise1, promise2]); // wait for all
Promise.allSettled([promise1, promise2]); // wait for all, don't fail fast
Promise.race([promise1, promise2]); // first to resolve/reject
Promise.resolve(value); // resolved promise
Promise.reject(error); // rejected promise

Async/await Style

async function fetchData(url) {
  try {
    const response = await fetch(url);
    if (!response.ok) {
      throw new Error("❌ Network response was not ok");
    }
    const data = await response.json();
    console.log("✅ Data:", data);
    return data; // still returns a Promise
  } catch (error) {
    console.error("❌ Fetch error:", error);
  }
}
  • DOM: The programming interface for HTML documents - represents the page structure as a tree of objects that JavaScript can manipulate.
  • DOM Node: An individual element in the DOM tree (elements, text, attributes, etc.).
// Selecting elements
const button = document.getElementById("myButton");
const paragraphs = document.querySelectorAll("p");
const firstDiv = document.querySelector(".container");

// Manipulating elements
button.textContent = "Click me!";
button.style.backgroundColor = "blue";
button.classList.add("active");

// Creating and adding elements
const newParagraph = document.createElement("p");
newParagraph.textContent = "New paragraph";
document.body.appendChild(newParagraph);
  • Event: An action or occurrence detected by the browser (clicks, key presses, page loads, etc.).
  • Event Handler: A function that responds to a specific event.
  • Event Listener: A method to register event handlers that listen for specific events on DOM elements.
// Method 1: Inline event handler (avoid this)
// <button onclick="handleClick()">Click</button>

// Method 2: Property assignment
button.onclick = function () {
  console.log("Button clicked!");
};

// Method 3: Event listeners (preferred)
button.addEventListener("click", function () {
  console.log("Button clicked with listener!");
});

// Multiple listeners on same element
button.addEventListener("click", handler1);
button.addEventListener("click", handler2);
button.addEventListener("mouseenter", handler3);
  • Event Object: An object containing information about the event that occurred (target, type, coordinates, etc.).
<button id="myButton">Click Me!</button>;
const button = document.getElementById("myButton");

button.addEventListener("click", function (event) {
  console.log("event.target:", event.target); // <button id="myButton">Click Me!</button>
  console.log("event.type:", event.type); // "click"
  console.log("event.target.innerText:", event.target.innerText); // "Click Me!"

  console.log("event.preventDefault()"); // Stops default behavior
  console.log("event.stopPropagation()"); // Stops event bubbling
});

Event Flow has 3 Phase

  • Capture Phase: Event travels down from document root to target element.
  • Target Phase: Event reaches the actual target element
  • Bubble Phase: Event travels up from target element back to document root

Default behavior: Events bubble up (child → parent → grandparent)

Bubbling: The default event listener behavior is bubbling, which enables event delegation

Event Delegation

Event delegation is a technique where instead of attaching an event listener to each child element, you attach a single event listener to a common ancestor and use the event’s bubbling to detect which child triggered it.

<ul id="list">
  <li>Apple</li>
  <li>Banana</li>
  <li>Cherry</li>
</ul>

<script>
document.getElementById('list').addEventListener('click', (e) => {
  if (e.target.tagName === 'LI') {
    console.log('You clicked:', e.target.innerText);
  }
});
</script>

stopPropagation(): event.stopPropagation() stops the bubbling, The event will not reach parent elements anymore. For example,

A modal has a close button — clicking the button shouldn’t also trigger the modal’s background click handler.

event.preventDefault(): Stops the browser's default behavior for that event.

// prevent page refresh
form.addEventListener("submit", function (event) {
  event.preventDefault();
});

// stop navigation
link.addEventListener("click", function (event) {
  event.preventDefault();
});

// right click - disable context menu, no Back, Reload, Inspect
document.addEventListener("contextmenu", function (event) {
  event.preventDefault();
});
  • Prototype: An object directly inherit from other object. An object can be designated as a prototype for another object.
  • Prototype Chain: The lookup mechanism JavaScript uses to find properties - if not found on the object, it looks up the prototype chain.

The internal property that points to an object's prototype (accessed via __proto__ or Object.getPrototypeOf()).

Every Object Has a Prototype:

const obj = {};
console.log(obj.__proto__); // Object.prototype
console.log(Object.getPrototypeOf(obj)); // Object.prototype (preferred way)
console.log(obj.__proto__ === Object.prototype); // true

const arr = [];
console.log(arr.__proto__); // Array.prototype
console.log(arr.__proto__.__proto__); // Object.prototype
console.log(arr.__proto__.__proto__.__proto__); // null (end of chain)
  • Constructor Function: A function used with new keyword to create objects and set up prototype relationships.

prototype property: A property on constructor functions that becomes the prototype for instances created by that constructor.

ES6 Class Syntax:

class Animal {
  constructor(name) {
    this.name = name;
  }

  speak() {
    return `${this.name} makes a sound`;
  }

  sleep() {
    return `${this.name} is sleeping`;
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name); // Call parent constructor
    this.breed = breed;
  }

  speak() {
    return `${this.name} barks`;
  }

  fetch() {
    return `${this.name} fetches the ball`;
  }
}

const buddy = new Dog("Buddy", "Golden Retriever");
console.log(buddy.speak()); // "Buddy barks"
console.log(buddy.sleep()); // "Buddy is sleeping"

// Under the hood, this creates the same prototype chain as before!

After React 16.8, it introduced Hooks.

  • First-class function (can be treated as variable) enables higher-order function, which enables currying
  • HOF: Functions that take functions as arguments and/or return functions, for example .map() .forEach(), .map(n => n*2), n => n*2 is the callback function

Static method vs Instance method

Static methods: For utility functions, factory methods, or operations that don't depend on instance state

Instance methods: For operations that work with or modify the specific data of an object instance

// Static - Called on `Array` constructor
Array.isArray(something);
Array.from(nodeList); // Creates array from array-like object
Array.of(1, 2, 3); // Creates array from arguments

// Instance - works with existing array
const myArray = [1, 2, 3];
myArray.push(4); // Modifies the array
myArray.length; // Accesses array property
  • HTTP is the protocol that defines how browsers and servers communicate.
  • Request: A message sent from client (browser) to server asking for resources or actions.
  • Response: A message sent back from server to client containing the requested data or result.
  • Headers: Metadata about the request/response
  • Async: HTTP requests are asynchronous and work with Promises/async-await
  • Error Handling: Check response.ok and handle different status codes appropriately

HTTP Methods

  • GET: Retrieve data from the server, doesn’t change the server’s state, idempotent (making the same GET request multiple times will have the same effect and return the same result).
  • POST: Create new resources on the server, not idempotent (Sending the same request twice can create duplicate resources)
  • PUT: Update a resource or create it if it doesn’t exist (sometimes), idempotent.
  • DELETE: Remove a resource from the server. Idempotent (Deleting a resource multiple times has the same effect as deleting it once).
// example POST request
const response = await fetch("/api/users", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Accept: "application/json",
  },
  body: JSON.stringify(userData),
});

const result = await response.json();

Status Code Categories:

  • 1xx: Informational
  • 2xx: Success (200 OK, 201 Created)
  • 3xx: Redirection (301 Moved, 304 Not Modified)
  • 4xx: Client Error (400 Bad Request, 401 Unauthorized, 404 Not Found)
  • 5xx: Server Error (500 Internal Server Error, 503 Service Unavailable)

Data Types

  • Primitive types: string, number, boolean, null, undefined, symbol, bigint (passed by value)
  • When access method str.length, JS temporarily create a wrapper object, allowing access to String.prototype methods, then unboxes it, new String('hello').length.
  • They are immutable, meaning value cannot be changed once created, predictable and behave consistently.
  • Reference types: object, array, function (passed by reference), mutable.
// 7 primitive types
const string = "hello";
const number = 42;
const boolean = true;
const undefined = undefined;
const nullValue = null;
const symbol = Symbol("id");
const bigint = 123n;

// Primitives are passed by value
let a = 5;
let b = a; // Copy of value
a = 10;
console.log(b); // Still 5

// Objects, arrays, functions are reference types
const obj1 = { name: "Alice" };
const obj2 = obj1; // Reference Assignment
const obj3 = { ...obj1 }; // Shallow copying

obj1.name = "Bob";
console.log(obj2.name); // "Bob" - same object!
console.log(obj3.name); // "Alice"

== vs ===

  • == (Loose Equality): Compares values after type coercion.
  • === (Strict Equality): Compares values without type coercion.

Falsy value

// Falsy values (only 8), everything else is truthy, "0", [], {}
console.log(Boolean(false));
console.log(Boolean(0));
console.log(Boolean(-0));
console.log(Boolean(0n));
console.log(Boolean(""));
console.log(Boolean(null));
console.log(Boolean(undefined));
console.log(Boolean(NaN));

typeof vs instanceof

  • typeof works with Primitive types and function.
  • instanceof returns true/false, works with Reference types
typeof 42;          // "number"
typeof undefined;   // "undefined"
typeof null;        // "object"     (quirk in JS!)
typeof [];          // "object"   (arrays are objects)

[] instanceof Array       // true
[] instanceof Object      // true (since Array extends Object)
new Date() instanceof Date // true
({}) instanceof Object    // true

Pure Function

  • A pure function is a function that always returns the same output for the same input and has no side effects (doesn't modify external state, make API calls, or log to console). Pure functions are predictable, testable, and essential in functional programming.

Function Declaration

function greet(name) {}

Function Expression

const bye = function (name) {};

Arrow Functions

const add = (a, b) => a + b;

JS behavior of moving variable and function declarations to the top.

  • var hoisted and initialized to undefined and can be redeclared, function-scoped
  • Both let and const also hoisted, but not initialized, you can ReferenceError, block-scoped {}
  • let can be reassign
  • const cannot be reassign but you can modify the properties of const object
  • Each loop iteration with let creates a new lexical environment, which closures can capture separately.

Strict Mode

  • 'use strict';
  • no variable declaration is ReferenceError
  • Prevent duplicate parameters
  • this is undefined, instead of global object
function example() {
  undeclaredVar = 10; // ReferenceError in strict mode

  // Prevents duplicate parameters
  function bad(a, b, a) {}

  // console.log(globalThis) in ES2020
  console.log(this); // undefined (instead of global object)
}

Number Creation and Checking

isInteger(), paseInt(), parseFloat()

const num = 42;

Number.isInteger(42); // true
Number.parseInt("f", 16); // 15, radix can be 2-36
Number.parseInt("12abc", 10); // 12, preferred
Number.parseInt("1.999"); // 1
Number.parseFloat("1.00"); // 1, 1 === 1.00
parseInt("12abc"); // 12, global function

Number Formatting

toFixed()

const num = 3.99;

num.toFixed(1); // "4.0", return a string, round up

Math Operations

abs(), floor(), min(), pow(), random()

Math.abs(-5); // 5
Math.floor(3.7); // 3
Math.min(1, 2, 3); // 1
Math.pow(2, 3); // 8

Math.random(); // 0 ≤ result < 1
Math.floor(Math.random() * 10); // 0 to 9
Math.floor(Math.random() * (max - min + 1)) + min; // inclusive

Date Creation and Methods

getTime(), getFullYear(), getMonth(), getDate(), getDay()

const now = new Date();
const date = new Date("2001-01-22"); // 'YYYY-MM-DD'
new Date(2001, 0, 22); // January 22, 2001

console.log(now); //2001-01-22T01:03:51.761Z

date.getTime(); // Timestamp, 980121600000
date.getFullYear(); // 2001
date.getMonth(); // 0 (0-indexed)
date.getDate(); // 22
date.getDay(); // 1 (0=Sunday)

String Creation and Basic Operations

repeat(), padStart(), padEnd()

const str1 = "hello";
const str2 = String(123);
const str3 = `template ${str1}`;

const str4 = str1.repeat(3); // "hellohellohello"
const str5 = str1.padStart(10, "0"); // "00000hello"
const str6 = str1.padEnd(10, "!"); // "hello!!!!!"

String Searching

indexOf(), lastIndexOf(), includes(), startWith(), endsWith()

const str = "hello world";
str.indexOf("o"); // 4 (first occurrence)
str.lastIndexOf("o"); // 7 (last occurrence)

str.includes("world"); // true
str.startsWith("hello"); // true
str.endsWith("world"); // true

String Transformation

trim(), trimStart(), toLowerCase(), toUpperCase(), replace(), replaceAll()

const str = "  Hello World  ";
const trimStart = str.trimStart(); // "Hello World  "

str
  .trim()
  .toLowerCase()
  .toUpperCase()
  .replace("WORLD", "JS")
  .replaceAll("L", "l");
// HEllO JS

String Splitting and Slicing

split(), slice(), substring(), charAt()

const str = "a,b,c,d";
str.split(",").join(" "); // ['a', 'b', 'c', 'd'] - "a b c d"
str.slice(2, 5); // "b,c"
str.slice(-1); // d, can take negative indices, most common

str.substring(2, 5); // "b,c"
str.charAt(0); // "a"

String() vs toString()

  • String(): global function, safe, never throws error, most common.
  • toString(): method, throws error on null or undefined, accepts radix parameter.
String(null); // "null"
String(undefined); // "undefined"

null.toString(); // TypeError
(15).toString(16); // "f", need ()

Array Creation and Basic Operations

Array.of(), Array.from(), push(), pop(), unshift(), shift(), toSpliced()

const arr1 = [1, 2, 3];
const arr2 = new Array(3); // [ <3 empty items> ]
const arr3 = Array.of(1, 2, 3); // [1,2,3]
const arr4 = Array.from({ length: 3 }, (_, i) => i); // [0,1,2]

// Adding/removing elements
arr.push(4); //  returns new length
arr.pop(); // returns removed element

arr.unshift(0); //  returns new length
arr.shift(); // returns removed element

// start, remove count, add
arr.splice(1, 1, "new"); // modify original, [1, "new", 3]

// keep the original
function toSpliced(arr, start, deleteCount, ...items) {
    const result = [...arr]; // Create copy
    result.splice(start, deleteCount, ...items); // Modify copy
    return result;
}
const newArr = toSpliced(arr, 1, 1, "new");

Array Searching and Checking

indexOf(), lastIndexOf(), includes(), find(), findIndex(), some(), every()

const arr = [1, 2, 3, 4, 5];
arr.indexOf(3); // 2 (first occurrence)
arr.lastIndexOf(3); // 2 (last occurrence)
arr.includes(3); // true
arr.find((x) => x > 3); // 4 (first match)
arr.findIndex((x) => x > 3); // 3 (index of first match)
arr.some((x) => x > 4); // true (at least one matches)
arr.every((x) => x > 0); // true (all match)

Array Transformation

map(), filter(), reduce(), reduceRight(), flatMap()

const arr = [1, 2, 3, 4, 5];
arr.map((x) => x * 2); // [2, 4, 6, 8, 10]
arr.filter((x) => x > 2); // [3, 4, 5]
arr.reduce((sum, x) => sum + x, 0); // 15
arr.reduceRight((sum, x) => sum + x, 0); // 15 (right to left)
arr.flatMap((x) => [x, x * 2]); // [1, 2, 2, 4, 3, 6, 4, 8, 5, 10]
  • map()
array.map((element, index, array) => {
    // element: current item being processed
    // index: current index (0, 1, 2, ...)
    // array: the original array being mapped
});

[1, 2, 3].map((num, i, arr) => {
    const isLast = i === arr.length - 1;
    return isLast ? `${num} (last)` : num.toString();
});
// ['1', '2', '3 (last)']

Array Sorting and Reversing

sort(), reverse()

const arr = [3, 1, 4, 1, 5];
arr.sort(); // [1, 1, 3, 4, 5] (mutates original)
arr.sort((a, b) => b - a); // [5, 4, 3, 1, 1] (descending)
arr.reverse(); // [5, 1, 4, 1, 3] (mutates original)
[...arr].sort(); // non-mutating sort

Array Slicing and Joining

slice(), join(), concat()

const arr = [1, 2, 3, 4, 5];
arr.slice(1, 3); // [2, 3] (from index 1 to 3, exclusive)
arr.slice(-2); // [4, 5] (last 2 elements)
arr.join("-"); // "1-2-3-4-5"
arr.concat([6, 7]); // [1, 2, 3, 4, 5, 6, 7]

Array Flattening and Copying

flat(), shallow copying, unique array

// Flattening arrays
const nested = [1, [2, 3], [4, [5, 6]]];
const flat1 = nested.flat(); // [1, 2, 3, 4, [5, 6]] - one level
const flatAll = nested.flat(Infinity); // [1, 2, 3, 4, 5, 6] - all levels

// Shallow copying arrays
const arr = [1, 2, 3];
const copy1 = [...arr]; // spread operator
const copy2 = Array.from(arr); // Array.from()
const copy3 = arr.slice(); // slice()

// Converting array to Set (removes duplicates)
const arr = [1, 2, 2, 3, 3, 4];
const uniqueSet = new Set(arr); // Set {1, 2, 3, 4}
const uniqueArray = [...new Set(arr)]; // [1, 2, 3, 4]

Object Creation and Property Operations

Object.create(), hasOwnProperty(), in, getOwnPropertyNames()

const obj1 = { a: 1, b: 2 };
const obj2 = new Object();
const obj3 = Object.create(null); // no prototype
const obj4 = Object.create(obj1); // inherits from obj1

obj1.hasOwnProperty("a"); // true
"a" in obj1; // true
Object.getOwnPropertyNames(obj1); // ['a', 'b']

Object Manipulation

Spread Operator, assign(), freeze(), seal(), preventExtensions()

const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3, d: 4 };

// copying and merging
const copy = { ...obj1 };
const merge = { ...obj1, ...obj2 };
const assigned = Object.assign({}, obj1, obj2);

// Freezing and sealing (shallow copy, only top level)
Object.freeze(obj); // prevents all changes
Object.seal(obj); // prevents adding/removing properties
Object.preventExtensions(obj); // prevents adding properties

Object Iteration

Object.keys(), Object.values(), Object.entries(), forEach()

const obj = { a: 1, b: 2, c: 3 };

const keys = Object.keys(obj); // ['a', 'b', 'c']
const values = Object.values(obj); // [1, 2, 3]
const entries = Object.entries(obj); // [['a', 1], ['b', 2], ['c', 3]]

// iteration methods
for (const key in obj) {
  /* iterate keys */
}
for (const [key, value] of Object.entries(obj)) {
  /* iterate entries */
}

Object.values(obj).forEach((value) => {
  /* iterate values */
});
Object.entries(obj).forEach(([key, value]) => {
  /* iterate entries*/
});

for...of vs for...in

  • for...of works on iterable (Map, Set, Array, String, Object.entries)
  • for...in works on Objects, iterating on Keys

Convert Map to Object

Object.fromEntries()

const map = new Map([
  ["language", "JavaScript"],
  ["type", "dynamic"],
]);

const obj = Object.fromEntries(map);
console.log(obj);
// { language: "JavaScript", type: "dynamic" }

JSON.stringify() vs JSON.parse()

  • JSON.stringify(): sending data to server, use it to convert object to string
  • JSON.parse(): receiving data from server, use it to convert string to object. (.json() in fetch)
const obj = { name: "John", age: 30 };

// JavaScript object → JSON string
const json = JSON.stringify(obj); // '{"name":"John","age":30}'

// JSON string → JavaScript object
const parsed = JSON.parse(json); // {name: "John", age: 30}

JSON.stringify() with replacer

  • Hiding sensitive fields before sending API responses.
const obj = { name: "Alice", age: 25, password: "secret" };

// Only serialize selected keys
const json = JSON.stringify(obj, ["name", "age"]); // '{"name":"Alice","age":25}'

// Using a replacer function
const json2 = JSON.stringify(obj, (key, value) =>
  key === "password" ? undefined : value
); // '{"name":"Alice","age":25}'

JSON.stringify with spacing (pretty-print)

  • null means "include all properties"
  • 2 means "indent with 2 spaces"
  • Debugging or writing config files.
const obj = { name: "Alice", age: 25 };
const pretty = JSON.stringify(obj, null, 2);
console.log(pretty);

/* Output:
{
  "name": "Alice",
  "age": 25
}
*/

JSON.parse with reviver

  • transform values while parsing
const json = '{"name":"Alice","birth":"1990-01-01"}';

const parsed = JSON.parse(json, (key, value) => {
  if (key === "birth") return new Date(value);
  return value;
});

console.log(parsed.birth instanceof Date); // true

Deep Copying via JSON, structuredClone()

const obj = { a: 1, b: { c: 2 } };

const copy = JSON.parse(JSON.stringify(obj));
// Loses functions, undefined, symbols. Converts dates to strings

const copy2 = structuredClone(obj);

Set Creation and Methods

add(), has(), delete(), clear(), size

const set = new Set([1, 2, 3, 3]); // {1, 2, 3}
set.add(4); // add element

set.has(3); // true
set.delete(2); // remove element
set.clear(); // remove all
set.size; // number of elements

Array.from(set); // convert to array
[...set]; // spread to array

Set Operations

  • Union, Intersection, Difference
const set1 = new Set([1, 2, 3]);
const set2 = new Set([3, 4, 5]);

// Union
const union = new Set([...set1, ...set2]); // {1, 2, 3, 4, 5}

// Intersection
const intersection = new Set([...set1].filter((x) => set2.has(x))); // {3}

// Difference
const difference = new Set([...set1].filter((x) => !set2.has(x))); // {1, 2}

Map Creation and Methods

set(), get(), has(), delete(), clear(), size

const map = new Map();
map.set("key1", "value1");
map.set("key2", "value2");
map.get("key1"); // 'value1'

map.has("key1"); // true
map.delete("key1"); // remove entry
map.clear(); // remove all
map.size; // number of entries

// From array of arrays
const map2 = new Map([
  ["a", 1],
  ["b", 2],
]);

Map Iteration

for...of, forEach()

const map = new Map([
  ["a", 1],
  ["b", 2],
]);
for (const [key, value] of map) {
  /* iterate entries */
}
for (const key of map.keys()) {
  /* iterate keys */
}
for (const value of map.values()) {
  /* iterate values */
}

map.forEach((value, key) => {
  /* iterate */
});

Convert Map to Array to Object

  • Array.from(map) or [...map]
const objArr = Array.from(map, ([key, value]) => ({ key, value }));
console.log(objArr);
// [{ key: "a", value: 1 }, { key: "b", value: 2 }, { key: "c", value: 3 }]

RegExp Creation and Methods

test(), exec(), Capture Groups(), match(), replace(), search(),

const regex = /hello/gi; // global, case-insensitive
const regex2 = new RegExp("hello");

// Checks if the pattern exists in a string.
const email = /\w+@\w+\.\w+/;
console.log(email.test("user@example.com")); // true
console.log(email.test("invalid-email")); // false

// Get match details, Capture Groups ()
const regex = /(\w+)@(\w+\.\w+)/;
const result = regex.exec("user@example.com");

console.log(result[0]); // "user@example.com" (full match)
console.log(result[1]); // "user" (first group)
console.log(result[2]); // "example.com" (second group)

String Methods with Regex

  • match() - Find matches
const text = "Phone: 123-456-7890 or 987-654-3210";
const phoneRegex = /\d{3}-\d{3}-\d{4}/g;

console.log(text.match(phoneRegex));
// ["123-456-7890", "987-654-3210"]
  • replace()- Replace matches, $ means capture group
const text = "Hello World";
console.log(text.replace(/world/i, "JavaScript"));
// "Hello JavaScript"

// With capture groups
const name = "John Doe";
console.log(name.replace(/(\w+) (\w+)/, "$2, $1"));
// "Doe, John"
  • search() - Find position
const text = "Find the word here";

console.log(text.search(/word/)); // 9 (index position)
console.log(text.search(/xyz/)); // -1 (not found)
  • split() - Split string, [] for multiple punctuation
const csv = "apple,banana;orange:grape";
console.log(csv.split(/[,;:]/));
// ["apple", "banana", "orange", "grape"]

Common Patterns

  • Extract Numbers
const text = "I have 5 apples and 10 oranges";
const numbers = text.match(/\d+/g);
console.log(numbers); // ["5", "10"]
  • Remove All Numbers/Letters
const text = "0He1ll2o345!";

const noNumber = text.replace(/\d+/g, "");
const noNumber2 = text.split(/[0-9]/g).join("");

const noLetter = text.split(/[a-z]/g).join("");

const noWord = text.split(/\w+/g).join("");

Common Patterns Cheat Sheet

/\d/        // Any digit (0-9)          /*/         // Zero or more
/\w/        // Any word character (a-z, A-Z, 0-9, _)
/\s/        // Any whitespace
/./         // Any character except newline
/^/         // Start of string          /?/         // Zero or one
/$/         // End of string            /{3}/       // Exactly 3
/[a-z]/     // Any lowercase letter     /{2,5}/     // Between 2 and 5
/[^a-z]/    // Anything except lowercase letters
/+/         // One or more              /(abc)/     // Capture group

Differences between ESM and CommonJS

  • ESM: asynchronous loading (doesn't block execution), good support of tree shaking (bundler can determine where to eliminate dead code), works natively in browser <script type="module">
  • CommonJS: synchronous loading, fully support ESM after version 12

ES6 Modules (ESM)

import/export

CommonJS (Node.js Traditional)

require/module.exports

// math.js - CommonJS exports
const PI = 3.14159;
function add(a, b) {
  return a + b;
}

module.exports = {
  PI,
  add,
  subtract: (a, b) => a - b,
};

// app.js - CommonJS imports
const { PI, add, subtract } = require("./math.js");
const math = require("./math.js");

console.log(PI); // 3.14159
console.log(math.add(2, 3)); // 5

Local Storage & Session Storage

  • Persistent client-side data storage that survives page reloads and browser sessions.
// localStorage persists until manually cleared
localStorage.setItem("user", JSON.stringify({ name: "Alice", id: 123 }));
const user = JSON.parse(localStorage.getItem("user"));
localStorage.removeItem("user");
localStorage.clear(); // removes all

// sessionStorage persists only for the browser tab session
sessionStorage.setItem("tempData", "value");
const temp = sessionStorage.getItem("tempData");

// Storage events (listen for changes in other tabs)
window.addEventListener("storage", (e) => {
  console.log("Storage changed:", e.key, e.newValue);
});

URL & URLSearchParams

  • Modern way to work with URLs and query parameters.
// URL manipulation
const url = new URL("https://example.com/path?name=john&age=30");
console.log(url.hostname); // 'example.com'
console.log(url.pathname); // '/path'
console.log(url.search); // '?name=john&age=30'

// URLSearchParams for query string manipulation
const params = new URLSearchParams(url.search);
params.get("name"); // 'john'
params.set("city", "NYC");
params.delete("age");
params.toString(); // 'name=john&city=NYC'

// Common pattern: updating URL without page reload
const newUrl = new URL(window.location);
newUrl.searchParams.set("tab", "profile");
window.history.pushState({}, "", newUrl);

Intersection Observer

  • Efficiently observe when elements enter/exit the viewport - great for lazy loading and infinite scroll.
const observer = new IntersectionObserver(
  (entries) => {
    entries.forEach((entry) => {
      if (entry.isIntersecting) {
        console.log("Element is visible:", entry.target);
        // Lazy load image
        entry.target.src = entry.target.dataset.src;
        observer.unobserve(entry.target);
      }
    });
  },
  {
    threshold: 0.1, // trigger when 10% visible
    rootMargin: "50px", // trigger 50px before entering viewport
  }
);

// Observe multiple elements
document.querySelectorAll("img[data-src]").forEach((img) => {
  observer.observe(img);
});
  • Mutation Observer: Watch for DOM changes - useful for responding to dynamic content updates.
  • Geolocation API: Access user's location (requires user permission).
  • Clipboard API: Modern way to interact with the system clipboard.
  • Notification API: Show desktop notifications (requires user permission).
  • File API: Handle file uploads and reading file contents.
  • Resize Observer: Efficiently observe element size changes without polling.
  • Page Visibility API: Detect when page becomes visible/hidden (useful for pausing video, animations).
  • History API: Manipulate browser history without page reloads (Single Page Applications).

Debounce & Throttle Patterns

  • Essential techniques for controlling function execution frequency.
// Debounce: Execute function only after delay period of inactivity
function debounce(func, delay) {
  let timeoutId;
  return function (...args) {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => func.apply(this, args), delay);
  };
}

// Throttle: Execute function at most once per time period
function throttle(func, limit) {
  let inThrottle;
  return function (...args) {
    if (!inThrottle) {
      func.apply(this, args);
      inThrottle = true;
      setTimeout(() => (inThrottle = false), limit);
    }
  };
}

Memory Leaks

  • Remove event listeners when components unmount.
  • Use weak reference (WeakMap/WeakSet, objects can be garbage collected)
In Chrome DevTool, memory tab
Take Heap Snapshot,
Perform actions that might cause memory leaks
Take another snapshot
Compare snapshots to see what increased

Loading Performance

  • Lazy Loading: Load resources only when needed.
  • Code Splitting: Break bundles into smaller chunks.
  • Preloading Critical Resources: Load important resources early.
  • Caching Strategies: Implement smart caching to avoid repeated work.

React Core Philosophy

  • React is a JS UI library, focusing on creating reusable components and managing application state efficiently

Declarative UI

  • React treats UI as a function of state.
  • You describe what UI look like based on the current state, and it handles how.

Component-based Architecture

  • Applications are built as a tree of components, building UI from small and isolated pieces, each managing its own state and rendering logic.

Virtual DOM

  • React maintains a lightweight JS representation of the real DOM.
  • When state/props changes, React
  • Creates a new Virtual DOM.
  • Uses Diffing Algorithm to compare new one with previous Virtual DOM.
  • Calculates the minimal changes needed.
  • Updates only changed DOM nodes.

This process is call reconciliation and makes React fast.

Unidirectional Data Flow

  • Data flows from parent to child (top-down) through props, which makes app logic more predictable and easier to debug.

React Fiber

  • React's reconciliation engine (introduce in React 16) that enables:
  • Incremental rendering
  • Ability to pause, abort, or reuse work
  • Priority assignment to different types of updates

React Best Practices

  • Hooks rely on call order, not variable names.
  • Top-level calls = consistent hook order = React can track state correctly.

Component Design:

  • Single responsibility: One component, one purpose
  • Composition over inheritance: Build complex UIs by combining simple components
  • Props interface: Clear, minimal prop APIs

State Management:

  • Local state first: Keep state as local as possible
  • Lift state when needed: Only when multiple components need it
  • Immutable updates: Never mutate state directly

Performance:

  • Measure before optimizing: Use React DevTools Profiler
  • Memoize appropriately: React.memo, useMemo, useCallback when beneficial
  • Code splitting: Lazy load routes and heavy components
  • Optimize lists: Use proper keys, virtualization for large lists

Code Organization:

  • Folder structure: Group by feature, not by file type
  • Custom hooks: Extract reusable stateful logic
  • TypeScript: Use for larger projects

Accessibility:

  • Semantic HTML: Use proper HTML elements
  • ARIA attributes: When semantic HTML isn't enough
  • Keyboard navigation: Ensure all interactions work with keyboard
  • Screen reader testing: Test with actual screen readers

Before React

  • In vanilla JS:
  • You often need to manually manipulate the DOM (document.createElement, appendChild, innerHTML, etc.).
  • Event handlers scattered everywhere in your code, making it harder to maintain.
  • It’s easy to perform unnecessary DOM operations that slow down the app (e.g., re-rendering whole sections when only one small change was needed).
  • JSX is a syntax extension that allows writing HTML-like code in JavaScript.
  • It's transpiled to React.createElement() calls.

JSX Transpilation:

// JSX
const element = <h1>Hello, {name}!</h1>;

// Transpiles to:
const element = React.createElement("h1", null, "Hello, ", name, "!");

JSX Rules:

  • Must return single parent element (use Fragment <>if needed)
  • Use className instead of class
  • Use htmlFor instead of for
  • Self-closing tags must end with />
  • Event handlers use camelCase (onClick, onChange)
  • Inline styles are objects: style={{ backgroundColor: 'red' }}
  • Any valid JavaScript expression can be embedded using {}

Conditional Rendering Patterns

// Ternary operator
{
    isLoggedIn ? <Dashboard /> : <Login />;
}

// Logical AND
{
    isLoggedIn && <Dashboard />;
}

// Function call, but it's recommended to create a separate component
function renderContent(isLoggedIn) {
    return isLoggedIn ? <p>Welcome back!</p> : <p>Please log in.</p>;
}

function App() {
    return <div>{renderContent(true)}</div>;
}

Props (Properties):

  • External data passed from parent to child components
  • Read-only - components should never modify their props
  • Can be any data type: strings, numbers, objects, functions, JSX
// Props destructuring
function UserCard({ name, email, onEdit }) {
    return (
        <div>
            <h3>{name}</h3>
            <p>{email}</p>
            <button onClick={onEdit}>Edit</button>
        </div>
    );
}

// Children prop for composition
function Card({ title, children }) {
    return (
        <div className="card">
            <h2>{title}</h2>
            <div className="card-body">{children}</div>
        </div>
    );
}

State

  • State is internal data that belongs to a component and can trigger re-renders when changed.
  • State is immutable - always create new objects/arrays
  • State updates are asynchronous
  • Multiple state updates may be batched
  • Use functional updates when new state depends on previous state
// only increase count by 1
function handleClick() {
    setCount(count + 1);
    setCount(count + 1);
}
// functional updates
function handleClick() {
    setCount((prev) => prev + 1);
    setCount((prev) => prev + 1);
}

useState

  • useState is a hook that adds state to functional components. It returns an array with the current state value and a setter function. When the setter is called, it triggers a re-render with the new state value.

Updating Objects/Arrays:

// Object
setUser({ ...user, name: "New Name" });

// Array
setItems([...items, newItem]);
setItems(items.filter((item) => item.id !== id));

Lazy Initial State

const [data, setData] = useState(() => expensiveCalculation());

useEffect Hook

  • useEffect handles side effects in functional components - operations like API calls, subscriptions, DOM manipulation, and timers.
  • Side effects: operations that affect things outside the component.
  • useEffect runs after the render is committed to the DOM.
  • Dependencies Array: No array runs after every render, [] runs once, [x, y] runs when dependencies change
  • Cleanup Function: Return a function from useEffect to clean up subscriptions, timers, or event listeners.
useEffect(() => {
  const timer = setInterval(() => console.log("tick"), 1000);

  // Cleanup runs before next effect or unmount
  return () => clearInterval(timer);
}, []); // Empty dependency array = componentDidMount + componentWillUnmount

useContext Hook

  • useContext provides a way to share data through the component tree without prop drilling. (Authentication, themes, language, API endpoints, feature flags)

Context Creation Process:

  • Create context with createContext(). Wrap components with Provider. Consume context with useContext()

Context Limitations:

  • Performance: All consumers re-render when context value changes

useReducer Hook

  • useReducer is an alternative to useState for managing complex state logic using the reducer pattern.

When to Use useReducer:

  • Next state depends on previous state
  • Want to centralize state update logic
  • State transitions need to be predictable

Reducer Pattern:

  • Pure function: (state, action) => newState
  • Actions: Objects describing what happened
  • Immutability: Always return new state object

useReducer + useContext: Powerful combination for global state management (Redux pattern).

useMemo & useCallback Hooks vs React.memo

  • Performance optimization hooks that prevent unnecessary recalculations and re-renders through memoization.
  • useMemo: Memoizes the result of expensive calculations. useCallback: Memoizes functions to prevent child re-renders
  • React.memo: HOC that wraps a functional component. Prevent re-render of child component if props unchanged.
// useMemo - memoizes VALUE
const expensiveValue = useMemo(() => {
  return items.filter((item) => item.active).length;
}, [items]);

// useCallback - memoizes FUNCTION
const handleClick = useCallback((id) => {
  setSelected(id);
}, []);
  • The React data flow is unidirectional, from parent to child.
  • We wrap child component with React.memo, and use useCallback and useMemo inside parent component. They are most beneficial when working together.

useRef Hook

  • useRef provides a way to access DOM elements and persist values across renders without causing re-renders.
  • DOM Access: Direct reference to DOM elements
  • Mutable Values: Store values that don't trigger re-renders
// DOM access
// or setInterval/setTimeout IDs for cleanup
const inputRef = useRef(null);
const focusInput = () => inputRef.current.focus();

// Mutable value (doesn't trigger re-render)
const countRef = useRef(0);
countRef.current += 1; // No re-render

useRef vs useState:

  • useRef: Mutable, no re-renders, .current property
  • useState: Immutable, triggers re-renders, direct value
  • Custom hooks are JavaScript functions that encapsulate and reuse stateful logic between components.

Rules of Custom Hooks

  • Must start with "use" (React convention)
  • Can call other hooks
  • Follow all Rules of Hooks
  • Return whatever makes sense (values, functions, objects)

Common Custom Hook Patterns

  • Data fetching: useApi, useFetch
  • Local storage: useLocalStorage
  • Form handling: useForm
  • Window size: useWindowSize
  • Previous value: usePrevious

Testing Custom Hooks: Use @testing-library/react-hooks to test hook logic in isolation.

// Custom hook for API calls
function useApi(url) {
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(null);

    useEffect(() => {
        fetch(url)
            .then((response) => response.json())
            .then(setData)
            .catch(setError)
            .finally(() => setLoading(false));
    }, [url]);

    return { data, loading, error };
}

// Usage
function UserProfile({ userId }) {
    const { data: user, loading, error } = useApi(`/api/users/${userId}`);

    if (loading) return <div>Loading...</div>;
    if (error) return <div>Error!</div>;

    return <div>{user.name}</div>;
}
  • Components are the building blocks of React applications - independent, reusable pieces that return JSX.

Functional vs Class Components:

  • Functional Components: Modern standard, use hooks for state/lifecycle
  • Class Components: Legacy, use this.state and lifecycle methods

Component Lifecycle

  • Component lifecycle refers to the 3 different phases a component goes through: mounting, updating, and unmounting.
  • Mounting: Component is created and inserted into DOM
  • Updating: Component re-renders due to prop/state changes
  • Unmounting: Component is removed from DOM
  • In functional components, useEffect handles all lifecycle concerns

Class Component Lifecycle Methods

  • componentDidMount: After first render (API calls, subscriptions)
  • componentDidUpdate: After every update (side effects based on changes)
  • componentWillUnmount: Before component removal (cleanup)

Event Handling

  • React uses SyntheticEvents - a wrapper around native events that provides consistent behavior across browsers (preventDefault, stopPropagation)
function Button() {
    const handleClick = (event) => {
        event.preventDefault(); // SyntheticEvent method
        console.log("Button clicked");
    };

    return <button onClick={handleClick}>Click me</button>;
}

Passing Arguments

<button onClick={() => handleClick(id)}>Click</button>
<button onClick={handleClick.bind(null, id)}>Click</button>

  • Controlled components: Form input value is controlled by React state.
  • Uncontrolled components: Form input value is controlled by DOM (use refs).

Single Source of Truth: React state becomes the source of truth for form data.

import { useState } from "react";

function ControlledInput() {
    const [value, setValue] = useState("");

    return (
        <input
            type="text"
            value={value} // value comes from state
            onChange={(e) => setValue(e.target.value)} // state updates on change
        />
    );
}
  • Use uncontrolled components for simple forms or when integrating with non-React libraries
import { useRef } from "react";

function UncontrolledInput() {
    const inputRef = useRef();

    const handleSubmit = () => {
        alert(inputRef.current.value); // read value directly from DOM
    };

    return (
        <>
            <input type="text" ref={inputRef} />
            <button onClick={handleSubmit}>Submit</button>
        </>
    );
}

Lifting State Up

  • Lifting state up means moving state to the closest common ancestor of components that need to share it. Prevents data inconsistency. State changes are centralized

When to Lift State:

  • Multiple components need the same data
  • Components need to communicate with siblings
  • Parent needs to coordinate child components

Clear Data Flow Pattern:

  • State lives in parent component
  • Data flows down via props
  • Changes flow up via callback functions

State Management Libraries

  • External libraries for managing complex application state across many components.

Redux

  • Predictable state container with single store
  • Store: Single source of truth for app state
  • Actions: Plain objects describing what happened
  • Reducers: Pure functions that specify state changes
  • Middleware: Handle async operations (Redux Thunk/Saga)

Zustand

  • Lightweight alternative with simpler API
const useStore = create((set) => ({
    count: 0,
    increment: () => set((state) => ({ count: state.count + 1 })),
}));

Context

Context + useReducer: Built-in React solution for medium complexity

  • React Router enables client-side routing in React applications, creating Single Page Applications (SPAs).

Core Components:

  • BrowserRouter: Provides routing context
  • Routes: Container for route definitions
  • Route: Defines path-to-component mapping
  • Link: Navigation without page refresh
  • Navigate: Programmatic navigation
import { BrowserRouter, Routes, Route, Link } from "react-router-dom";

function App() {
    return (
        <BrowserRouter>
            <nav>
                <Link to="/">Home</Link>
                <Link to="/about">About</Link>
            </nav>

            <Routes>
                <Route path="/" element={<Home />} />
                <Route path="/about" element={<About />} />
                <Route path="/users/:id" element={<User />} />
            </Routes>
        </BrowserRouter>
    );
}
  • Route Parameters: Dynamic segments in URLs (/users/:id)
  • Query Parameters: URL search parameters (?search=react)
  • Nested Routes: Routes within routes for complex layouts

Navigation Methods:

  • Link: Declarative navigation
  • useNavigate: Imperative navigation
  • Navigate: Component-based redirect
// useNavigate(): Programmatic navigation after events (form submit, login)
import { useNavigate, Navigate } from "react-router-dom";

function Login() {
    const navigate = useNavigate();

    const handleLogin = () => {
        // simulate login logic
        const success = true;
        if (success) {
            navigate("/dashboard"); // redirect after login
        }
    };

    return <button onClick={handleLogin}>Login</button>;
}

// Navigate: Conditional redirects inside JSX (protected routes, auto-redirects)
function ProtectedRoute({ isAuthenticated, children }) {
    if (!isAuthenticated) {
        return <Navigate to="/login" replace />; // redirect if not logged in
    }
    return children;
}
  • Error boundaries are React components that catch JavaScript errors anywhere in their child component tree and display fallback UI instead of crashing the app.

What Error Boundaries Catch

  • Errors in render methods
  • Errors in lifecycle methods
  • Errors in constructors of child components

What They DON'T Catch:

  • Event handlers (use try/catch)
  • Async code(setTimeout, promises)
  • Server-side rendering errors
  • Errors in the error boundary itself
  • No built-in hook, but libraries like react-error-boundary provide solutions
  • Error boundaries must be class components
class ErrorBoundary extends React.Component {
    static getDerivedStateFromError + componentDidCatch
}

function BuggyButton() {
  const [count, setCount] = React.useState(0);

  if (count === 3) {
    throw new Error("Crashed at count 3!");
  }

  return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}

function App() {
  return (
    <ErrorBoundary>
      <BuggyButton />
    </ErrorBoundary>
  );
}

Suspense & Lazy Loadings

  • Suspense allows components to wait for something before rendering, commonly used with code splitting and data fetching.
  • Code Splitting: Split large bundles into smaller, manageable pieces.
  • Lazy Loading: Defer loading of resources until they're actually required.
  • Code Splitting with React.lazy
const LazyComponent = React.lazy(() => import("./LazyComponent"));

function App() {
    return (
        <div>
            <Suspense fallback={<div>Loading component...</div>}>
                <LazyComponent />
            </Suspense>
        </div>
    );
}
  • Route-based Code Splitting:
const Home = React.lazy(() => import("./routes/Home"));
const About = React.lazy(() => import("./routes/About"));

function App() {
    return (
        <Router>
            <Suspense fallback={<div>Loading page...</div>}>
                <Routes>
                    <Route path="/" element={<Home />} />
                    <Route path="/about" element={<About />} />
                </Routes>
            </Suspense>
        </Router>
    );
}

Higher-Order Components (HOC)

  • Higher-Order Components are functions that take a component and return a new component with additional functionality
  • HOCs can create wrapper hell with deeply nested components, make debugging harder
  • HOC for error boundaries

Common Use Cases:

  • Authentication: Protect routes/components
  • Data fetching: Inject data into components
  • Styling: Add CSS classes or themes
  • Analytics: Track component usage

Render Props Pattern

  • Render props is a pattern where a component takes a function as a prop that returns JSX, allowing for flexible component composition.

Compound Components

  • Compound components work together as a cohesive unit, often with implicit state sharing between parent and children.
  • Examples: <select> and <option>, React Router's <Routes>and <Route>

React Developer Tools

  • React Developer Tools are browser extensions for debugging and profiling React applications

Components Tab:

  • Component tree: Hierarchical view of React components
  • Props inspection: See all props passed to components
  • State inspection: View and modify component state
  • Hooks details: See all hooks and their values

Profiler Tab:

  • Performance analysis: Identify slow components
  • Render timings: See how long components take to render
  • Commit timeline: Visualize when components update
  • Flamegraph: Hierarchical view of component render times

Key Profiling Metrics:

  • Render duration: Time spent rendering
  • Number of renders: How often components re-render
  • Props changes: What caused re-renders

Testing React Components

  • Testing ensures components work correctly and helps prevent regressions during refactoring.
  • Regressions: Something that used to work correctly is now broken after making changes.

Testing Philosophy:

  • Test behavior, not implementation: Focus on what users see and do
  • Test like a user: Use queries that match how users find elements
  • Avoid testing internal state: Test outputs and interactions

React Testing Library (Recommended):

import { render, screen, fireEvent } from "@testing-library/react";

test("increments counter when button is clicked", () => {
    render(<Counter />);

    const button = screen.getByRole("button", { name: /increment/i });
    const counter = screen.getByText("Count: 0");

    fireEvent.click(button);

    expect(screen.getByText("Count: 1")).toBeInTheDocument();
});

Testing Strategies:

  • Unit tests: Individual components in isolation
  • Integration tests: Multiple components working together
  • E2E tests: Complete user workflows

React 18 Features

  • React 18 introduced concurrent rendering and new features for better user experience and performance.

Automatic Batching: Multiple state updates are batched automatically

// React 18: Both updates batched automatically
setTimeout(() => {
    setCount(1);
    setFlag(true);
    // Only one re-render
}, 1000);

useDeferredValue: Defer expensive updates

function App() {
    const [query, setQuery] = useState("");
    const deferredQuery = useDeferredValue(query);

    return (
        <div>
            <input value={query} onChange={(e) => setQuery(e.target.value)} />
            <ExpensiveList query={deferredQuery} />
        </div>
    );
}

Concurrent Rendering: React can interrupt, pause, and resume rendering work

Setup and Configuration

npm create vite@latest my-app -- --template react-ts
  • tsconfig.json Configuration
{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "dom.iterable", "es6"],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx"
  },
  "include": ["src"]
}

Basic TypeScript Concepts for React

  • Basic Types
// Primitive
const name: string = "Lin";
const age: number = 9;
const isActive: boolean = true;

// Arrays
const numbers: number[] = [1, 2, 3];
const digits: Array<number> = [4, 5, 6];
const letters: Array<string> = ["a", "b", "c"];

// an array of arrays of strings and numbers
const nestedStrings: string[][] = [["N", "E"], ["S"], ["T", "E", "D"]];
const nestedNumbers: Array<number[]> = [[1, 2], [3]];

// Objects
interface User {
  name: string;
  age: number;
  email?: string;
}

const user: User = {
  name: "Lin",
  age: 9,
};

const users: User[] = [
  { name: "W", age: 10, email: "W@gmail.com" },
  { name: "E", age: 12 },
];

// one line
const employees: { name: string, age: number }[] = [
  { name: "Charles", age: 30 },
  { name: "Diana", age: 25 },
];
  • Union Types and Literals
type Status = "loading" | "success" | "error";
type Theme = "light" | "dark";

interface ButtonProps {
  variant: "primary" | "secondary" | "danger";
  size: "sm" | "md" | "lg";
}
  • use Interface when defining object shapes, declaration merging, building extensible APIs extends, Working with classes implement, React Component Props
  • use Type when creating union types, aliasing primitive types,conditional and mapped types

Interface

  • Supports declaration merging (multiple declarations combine):
interface User {
  id: number;
  name: string;
}

interface User {
  email: string;
}

// Result: User has id, name, and email
const user: User = {
  id: 1,
  name: "John",
  email: "john@example.com", // This is required due to merging
};
  • Uses extends keyword:
interface BaseUser {
  id: number;
  name: string;
}

interface AdminUser extends BaseUser {
  permissions: string[];
  role: "admin";
}

// Multiple inheritance
interface SuperAdmin extends BaseUser, AdminUser {
  superPower: boolean;
}

Type

  • Perfect for union types
type StringOrNumber = string | number;
type Status = "loading" | "success" | "error";
type Theme = "light" | "dark" | "auto";

// Complex unions
type ApiResponse<T> =
  | { success: true, data: T }
  | { success: false, error: string };
  • Can alias any type, primitives
type UserId = number;
type UserName = string;
type IsActive = boolean;

// Usage
const userId: UserId = 123;
const userName: UserName = "John";
  • Full support for computed properties, dynamically generating property names
type DynamicKeys = {
  [K in 'a' | 'b' | 'c']: string;
};
// Result: { a: string; b: string; c: string; }

// With template literals
type EventHandlers = {
  [K in `on${Capitalize<'click' | 'hover' | 'focus'>}`]: () => void;
};
// Result: { onClick: () => void; onHover: () => void; onFocus: () => void; }

Generics allow you to create reusable components, functions, and types that work with multiple types while maintaining type safety. Like a "placeholder".

Basic Function

// Need separate functions for each type
function getFirstString(items: string[]): string {
  return items[0];
}

function getFirstUser(items: User[]): User {
  return items[0];
}

// One function works for all types
function getFirst<T>(items: T[]): T {
  return items[0];
}

// Usage
const firstStr = getFirstString(["hello", "world"]);
const firstStr = getFirst(["hello", "world"]);

React Component

// One component works for all types
interface CardProps<T> {
  item: T;
  onClick: (item: T) => void;
  title: string;
}

// <T,> The comma tells TypeScript this is a generic, not JSX
const Card = <T,>({ item, onClick, title }: CardProps<T>) => (
  <div onClick={() => onClick(item)}>
    <h3>{title}</h3>
  </div>
);

// Usage
<Card item={user} onClick={handleUserClick} title={user.name} />
<Card item={product} onClick={handleProductClick} title={product.name} />

State Hook

// One hook works for all types
function useDataState<T>() {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(false);
  return { data, setData, loading, setLoading };
}

// Usage
const { data: user, setData: setUser } = useDataState<User>();
const { data: product, setData: setProduct } = useDataState<Product>();

Simple API Hook

// Type-safe API hook with async/await
function useApi<T>(url: string) {
  const [data, setData] = useState<T | null>(null);

  useEffect(() => {
    const fetchData = async () => {
      const response = await fetch(url);
      const result: T = await response.json();
      setData(result);
    };

    fetchData();
  }, [url]);

  return data; // Type is T | null
}

// Usage - full type safety!
const users = useApi<User[]>('/users'); // users is User[] | null

SPA SSR CSR

JWT

A stateless token that contains user ID, roles, expiration encoded in JSON and signed. Can be verified without server-side storage because the signature ensures authenticity.

  • User logs in → server validates credentials.
  • Server issues a JWT to the client.
  • Client stores JWT (localStorage, sessionStorage, or cookie).
  • Client sends JWT with every request (commonly in Authorization: Bearer <token>).
  • Server verifies signature and grants access.

Use cases: RESTful APIs, Microservices, Mobile apps

Session

A server-side storage of user data, keyed by a session ID. The server keeps a mapping of sessionID → user info.

Stateful, easily revoke or expire sessions, more secure

  • User logs in → server creates a session with user info.
  • Server sends a session ID to the client (usually in a cookie).
  • Client sends session ID with every request.
  • Server looks up session ID → retrieves user data → authenticates request.

Cookie

A small piece of data stored in the browser, automatically sent with HTTP requests to the domain that set it.

Can store session IDs, JWTs, or other data.

Storing JWT in Cookies

Can be HttpOnly → JavaScript can’t access it → protects against XSS.

Storing JWT localStorage (or sessionStorage)

Full client-side control → you decide when to send the token.

Not automatically sent with requests → you need to add it to Authorization headers manually.

XSS – Cross-Site Scripting

An attacker injects malicious scripts (usually JavaScript) into a web page. The script can steal cookies, tokens, or manipulate the DOM

  • Use HttpOnly cookies (JavaScript cannot read them).
  • Sanitize/escape user input before rendering.
  • CSP

CSRF – Cross-Site Request Forgery

An attacker tricks a logged-in user’s browser into sending requests to a website the user is authenticated on, without consent. Can perform unwanted actions like changing passwords or making transactions.

  • Use cookies with SameSite attribute.

SQL Injection (SQLi)

Attacker manipulates database queries via input.

  • Parameterized queries: A safe way to run SQL queries by separating the SQL code from user input. User input is treated as data not code.
  • ORM: A tool that maps database tables to programming objects. Lets developers interact with the database using code instead of raw SQL.

Man-in-the-Middle (MITM)

Intercepting communication between client & server. Attacker on public Wi-Fi intercepts login credentials sent over HTTP.

  • Always use HTTPS/TLS

Clickjacking

Tricks users into clicking hidden buttons or links, performing unintended actions.

  • Content-Security-Policy: frame-ancestors 'none'

Content-Security-Policy (CSP)

CSP is a HTTP response header, server tells the browser which sources of content (scripts, images, styles, frames, etc.) are allowed to load and execute.

Rendering Large Lists

  • Virtualization: Only render items currently visible in the viewport plus a small buffer
  • React Native: Use FlatList instead of ScrollView - it automatically virtualizes and provides built-in optimizations like getItemLayout for consistent item heights. There's FlashList performs well without needing getItemLayout, it can recycle views which consumes less memory.
  • React Web: Use libraries like react-window or react-virtualized
  • Server-Side Pagination: Backend returns only a slice of data (limit + offset or cursor). REST APIs, GET /users?page=2&limit=20
  • React Query: Provides a clean API. I usually combine useQuery with page numbers for classic pagination, or useInfiniteQuery for infinite scroll, pair it with a UI component react-window or FlatList

Janky Animations & Gestures

Sluggish UI and animation.

  • React Native: Use react-native-reanimated v2+ for native-driven animations that run on the UI thread, not JS thread which is for state changes and bussiness logic.
  • React Web: CSS transitions and animations for simple cases, requestAnimationFrame for complex JavaScript animations, a browser API requests that the browser calls a specified function to update an animation before the next repaint.
  • use transform and opacity properties (they don't trigger reflow/repaint), implement proper gesture handling with libraries like react-native-gesture-handler
  • Reflow (or Layout): recalculate the size and position of elements on the page, width, height, padding, or margin.
  • Repaint: A repaint is when the browser re-draws the pixels of an element on the screen, background-color or border-color.

Screen Fragmentation

Inconsistency of layout across different devices

  • Flexbox: Use flexible layouts that adapt to container size, simplifying alignment
  • Responsive design: Media queries in React web, Dimensions API in React Native
@media (max-width: 768px) {
  .container {
    flex-direction: column;
  }
}
  • Relative units: Use percentages, vh/vw units instead of fixed pixel values
  • Safe areas: Handle notches and status bars with react-native-safe-area-context

Accessibility (a11y)

Excludes users with disabilities

  • Semantic markup: Use proper HTML elements (button, input, nav) instead of generic divs
  • ARIA attributes: aria-label, aria-describedby, role attributes for complex components. For example, <button aria-label="Close"></button>
  • React Native: Use accessibilityLabel, accessibilityRole, accessibilityHint
  • Focus management: Ensure keyboard navigation works, manage focus after route changes
  • Testing: Use screen readers (VoiceOver, TalkBack), automated tools like eslint-plugin-jsx-a11y

Navigation & Routing

Confusing UX

  • React Web: React Router v6 with proper route nesting, lazy loading, and error boundaries.
  • React Native: React Navigation v6 with stack, tab, and drawer navigators

Internationalization (i18n) & Localization (l10n)

UI Consistency Across Platforms

iOS vs Android vs Web differences

  • Platform-specific styling: Use Platform.OS in React Native, CSS media queries for responsive web design
  • Design systems: Implement component libraries that adapt to platform conventions (Material-UI for Android-like web, native components for mobile)
  • Conditional rendering: Show platform-appropriate components (iOS action sheets vs Android bottom sheets)

Re-renders / State Management

Slow UI, wasted renders

  • React is unidirectional, from parent to child, so if parent re-render then it's child also re-render.
  • React.memo: Wrap child components to prevent re-renders when props haven't changed
  • useMemo: Memoize expensive calculations value that don't need to run on every render
  • useCallback: Memoize function references to prevent child re-renders caused by new function instances
  • State management libraries: Redux Toolkit, Zustand, or Jotai for predictable state updates and selective subscriptions
  • React DevTools Profiler: Identify performance bottlenecks and unnecessary re-renders

Large Assets & Images

Loading high-resolution images, consume significant bandwidth and memory, especially problemetic on mobile network and device.

  • Lazy loading: Load images only when they enter the viewport using Intersection Observer API or libraries like react-lazyload
  • Responsive images: Serve different image sizes based on device screen size and pixel density
  • Image optimization: Use modern formats (WebP, AVIF), compress images, use appropriate quality settings.
  • CDN implementation: Serve assets from geographically distributed servers with caching
  • Progressive loading: Show low-quality placeholders while high-quality images load
  • React Native specific: Use react-native-fast-image for better caching and performance

Memory Leaks

Crashes, slowdowns

  • JavaScript is garbage collected, but certain patterns prevent garbage collection.
  • Cleanup in useEffect: Always return cleanup functions for subscriptions, timers, and listeners
  • AbortController: Use for cancelling fetch requests when components unmount
  • Weak references: Use WeakMap/WeakSet for temporary object relationships
  • Subscription management: Unsubscribe from observables, remove event listeners
  • Memory profiling: Use browser DevTools Memory tab or React Native Flipper to identify leaks

App Size & Bundle Bloat

Slower installs, longer loads

  • Code splitting: Split application into chunks that load on-demand using React.lazy() and dynamic imports
  • Tree-shaking: Configure bundlers (Webpack, Metro) to eliminate dead code
  • Bundle analysis: Use tools like webpack-bundle-analyzer or react-native-bundle-visualizer
  • Modular imports: Import only needed functions (import { debounce } from 'lodash/debounce' instead of import _ from 'lodash')
  • Dependency audit: Regularly review and remove unused dependencies, choose lighter alternatives
  • Asset optimization: Compress images, use SVGs for icons, minimize fonts

Heavy JavaScript Computation

JavaScript is single-threaded, so heavy computations block UI updates, user interactions

  • Web Workers (Web): Browser API, move heavy computations to background threads that don't block UI
  • Background threads (React Native): Use libraries like react-native-worker-threads or native modules
  • Time-slicing: Break large computations into smaller chunks using requestIdleCallback or setTimeout
  • Memoization: Cache computation results to avoid repeating expensive operations
  • Algorithmic optimization: Use more efficient algorithms and data structures
  • Progressive processing: Show progress indicators and process data incrementally

Offline Support

App unusable without network

  • Data persistence: Store critical data locally using AsyncStorage (React Native), IndexedDB (web), or SQLite
  • Background sync: Queue actions while offline and sync when connection returns
  • Offline-first architecture: Design app to work primarily from local data, with network as enhancement
  • Network state detection: Use libraries like @react-native-netinfo or navigator.onLine to detect connectivity
    • Service Workers (Web): browser API acts as a proxy between a web browser and the network. Cache network requests and serve cached responses when offline
  • Progressive Web App (PWA): Implement service workers for comprehensive offline functionality

Network Reliability

Random failures, bad UX. Network requests fail frequently due to poor connectivity, server issues, or timeouts.

  • Exponential backoff: Retry failed requests with increasing delays to avoid overwhelming servers, the failure must temporary, not 404.
  • Circuit breaker pattern: Stop making requests temporarily after repeated failures
  • Graceful degradation: Provide reduced functionality when network is unreliable
  • Error boundaries: Catch and handle network errors at component level, show fallback data instead of crashing the app.
  • Request deduplication: Prevent multiple identical requests
  • Timeout handling: Set appropriate timeouts and provide user feedback

Data Synchronization

Local and server data conflicts, stale data

  • Optimistic updates: Update UI immediately, then sync with server and handle conflicts
  • Conflict resolution strategies: Last-write-wins with timestamp for non-critical update, merge strategies, or user-guided resolution, server validation to ensure data integrity for critical updates
  • Version vectors/timestamps: Track data versions to detect conflicts
  • Event sourcing: Store changes as a sequence of events rather than final state. The key benefit is that you have a complete, immutable history of every change, good for auditing and debugging.

Caching Strategy

Re-fetching wastes bandwidth

  • Stale-while-revalidate (SWR): Show cached data immediately while fetching fresh data in background
  • React Query/TanStack Query: Comprehensive caching with automatic background updates, When a component needs data useQuery, it fetches the data from the server. It then stores this data in its internal cache, identified by a unique key. The UI is updated with the fetched data.
  • HTTP caching headers: Leverage browser caching with proper Cache-Control, ETag headers (a unique identifier generated by server to determine if a cached resource is still fresh)

Insecure Storage

Insecurely stored data can be accessed through XSS attacks, local storage inspection, or device compromise

Common insecure storage locations:

  • Web: localStorage, sessionStorage for sensitive tokens
  • Mobile: Plain text files, regular SharedPreferences/UserDefaults
  • Both: Unencrypted databases, log files, temporary files

Solution approach:

  • Web: HttpOnly cookies for auth tokens, encrypt sensitive localStorage data
  • React Native: Keychain (iOS) / EncryptedSharedPreferences (Android)
  • Database encryption: Encrypt sensitive data at rest
  • Memory management: Clear sensitive data from memory after use
  • Secure transmission: Always use HTTPS for data transmission

Authentication & Authorization

Who are you & What can you do

  • Strong authentication: Multi-factor authentication, secure password policies, account lockout
  • Secure session management: JWT with proper expiration, refresh tokens, session invalidation
  • Role-based access control (RBAC): Define roles and permissions clearly
  • Route protection: Guard sensitive routes and API endpoints. One the frontend, router guard checks if a user is authenticated before rendering the requested page. One the backend, middleware runs before the main route handler and checks for a valid authentication token.

API Security

Insecure APIs can expose sensitive data, allow unauthorized actions, be abused for DDoS attacks, or become entry points for injection attacks that compromise the entire system.

  • Input validation: Validate and sanitize all inputs
  • Parameterized queries: Prevent SQL injection, treat user input as data not code
  • Rate limiting: Prevent abuse and DDoS
  • Authentication & authorization: Secure endpoint access
  • Security headers: CORS, CSP, security headers
  • Logging & monitoring: Track suspicious activity

CORS, or Cross-Origin Resource Sharing, is a security mechanism built into web browsers that allows a web page to request resources from a different domain than the one that served the web page.

Third-Party SDK Issues & Version Conflicts

Bugs, bloat, maintenance risk. Build errors, runtime crashes

  • Careful evaluation: Assess library quality, maintenance status, community support
  • Regular updates: Keep dependencies current but test thoroughly
  • Modular imports: Import only needed functionality
  • Dependency auditing: Regular security and quality audits
  • Fallback strategies: Have alternatives for critical dependencies
  • Lock files: Use package-lock.json or yarn.lock to ensure consistent installs
  • Dependency resolution: Configure resolution strategies for conflicts
  • Peer dependencies: Properly manage peer dependency requirements
  • Version auditing: Regular checks for vulnerable or conflicting versions

Native Module Integration (Mobile)

Hard to integrate custom features

  • Evaluate existing libraries: Check if maintained libraries already exist
  • JSI is an API that allows JavaScript and native code to hold references to each other and invoke methods directly, without the serialize and deserialize JSON communication system.
  • Fabric/TurboModules: Use new architecture for better performance
  • Platform-specific implementation: Separate iOS (Swift/Objective-C) and Android (Java/Kotlin)
  • Testing strategy: Test on both platforms and different OS versions

CI/CD Setup

Manual, error-prone releases

  • Automated testing: Run tests on every commit and pull request
  • Build automation: Consistent builds across all environments
  • Deployment automation: Automated releases to staging and production
  • Environment management: Separate pipelines for different environments
  • Development (Dev) -> Testing (Test / QA) -> Staging (Staging / Pre-Prod) -> Production (Prod)
  • Monitoring integration: Automated rollback on deployment issues

Environment Configuration

Wrong API keys, config leaks. Managing different configurations for development, staging, and production environments, including API endpoints, feature flags, and sensitive data like API keys and secrets.

  • Environment variables: Use .env files for different environments
  • Build-time configuration: Different builds for different environments
  • Runtime configuration: Dynamic configuration loading
  • Secret management: Secure storage for sensitive data, AWS Secrets Manager
  • Feature flags: Toggle features per environment, that allows you to turn features on and off in your application without deploying new code. They work by wrapping a new feature in a conditional statement that can be controlled remotely. A/B Testing and Rollbacks

Monitoring & Crash Reporting

  • Sentry has a Session Replay feature, a reproduction of the user's actions and the state of the application. Alert you to problems as they happen via email, Slack.
  • Error tracking: Capture and categorize JavaScript errors and crashes
  • Performance monitoring: Track app performance, loading times, and user interactions
  • User session recording: Understand user behavior leading to issues
  • Real-time alerts: Immediate notification of critical issues
  • Contextual debugging: Capture device info, user actions, and environment data

Backward Compatibility

Breaks for older devices

  • Feature detection: Check for feature availability before using
  • Polyfills: Provide fallback implementations for missing features
  • Progressive enhancement: Build base functionality first, enhance for modern devices
  • Graceful degradation: Ensure core features work even when advanced features fail
  • Testing strategy: Test on multiple OS versions and device configurations

Your approach to testing frontend code

What tools do you use for debugging?

You approach working with designers and backend developers?

How do you stay updated with frontend technologies?

hello