JavaScript & Web Tech
Why do we use javascript for web development? (and not any other language)
JavaScript is a popular programming language used for web development due to several key reasons:
- Client-Side Interactivity: JavaScript is primarily used to add interactivity and dynamic behaviour to websites on the client-side, meaning it runs directly in the user's web browser.
- Browser Compatibility: JavaScript is supported by all major web browsers. Browsers have built-in JavaScript engines that execute JavaScript code directly in the browser, making it a consistent and cross-browser solution. Therefore we do not need to maintain codebase in different languages for different browsers
- Versatility: JavaScript can be used for many use cases of web development, like
- doing form validation
- creating interactive elements
- creating animations
- handling events (e.g., clicks, mouse movements)
- manipulating the Document Object Model (DOM)
- handling AJAX requests and interacting with server-side APIs
- updating the page content without requiring a full page reload, providing a more seamless user experience.
- Large Community and Libraries: JavaScript has an extensive and active developer community. This means there are countless libraries, frameworks, and tools available that can significantly speed up the development process and help solve common web development challenges. Examples of popular JavaScript libraries and frameworks include React, Angular, and Vue.js.
- Support Native Web Technologies: Modern web technologies like HTML5, CSS3, and Web APIs are designed to work seamlessly with JavaScript. Many of these technologies are integrated into web browsers and accessible via JavaScript APIs.
- Security and Browser Sandboxing: JavaScript runs within a browser's sandboxed environment, ensuring security by preventing direct access to system resources. This is crucial for maintaining user safety and data privacy.
How does the chrome v8 JS engine work?
The V8 engine is an open-source JavaScript engine developed by the Google Chrome team.
The combination of parsing, profiling, optimization, and JIT compilation in the Chrome V8 engine contributes to the fast and efficient execution of JavaScript code in web applications.
- Parsing:
- The V8 engine's first step is to parse the JavaScript code into an Abstract Syntax Tree (AST), which represents the structure of the code in a tree-like format.
- Profiling:
- After parsing, the JavaScript code is first executed in an "interpreted" mode.
- As the JavaScript code is interpreted, the V8 engine uses profilers to collect runtime information about the code's behaviour. This includes tracking which parts of the code are executed frequently (hot paths) and which data types are commonly used.
- Optimisation & Just-In-Time (JIT) Compilation:
- Based on the gathered information, the V8 engine employs JIT compilation to optimise specific code paths.
- JIT compilation generates machine code that is tailored for the actual runtime behaviour of the code, taking advantage of the specific data types encountered during interpretation.
- Execution:
- Once a code path has been compiled, the resulting machine code is stored in an "executable memory" area.
- Subsequent executions of the same code path can directly use the compiled machine code, which significantly speeds up execution compared to interpretation.
- Garbage Collection:
- JavaScript is a garbage-collected language, meaning that memory management is handled automatically.
- V8's garbage collector periodically identifies and deallocates memory that is no longer needed, preventing memory leaks.
- Asynchronous Execution:
- V8 handles asynchronous operations like timers and network requests using the event loop, which allows non-blocking execution and responsiveness.
In summary, the V8 engine first parses the JS code and converts it into AST(a tree-like representation of the structure of the code) then it executes the code in interpretation mode and profiles the code (like which part of the code is frequently used and commonly used data types). Based on the runtime behaviour and profiling data, it optimises the code using JIT Compilation to selectively compile frequently executed code paths into highly optimised machine code and store it in a “executable memory”. Subsequent executions of the same code path can directly use the compiled machine code, which significantly speeds up execution compared to interpretation.
This combination of interpretation and JIT compilation helps achieve a balance between startup speed and runtime performance for JavaScript execution.

Does v8 javascript engine compile or interpret the code?
The V8 JavaScript engine employs a combination of both compilation and interpretation to execute JavaScript code. This approach is known as "Just-In-Time" (JIT) compilation, which aims to balance the benefits of both compilation and interpretation for optimising the performance of JavaScript execution.
Compiler vs Interpreter (Compilation vs execution)
- Translation vs. Interpretation: The primary difference is in how they process the source code. A compiler translates the entire source code (of high-level programming language) into machine code (low-level language) or bytecode before execution, while an interpreter reads and executes the source code line-by-line at runtime.
- Output: A compiler generates an executable file or bytecode as its output, while an interpreter directly executes the code without producing a separate output file.
- Error Handling: A compiler checks for errors throughout the entire code and reports them all at once, while an interpreter stops execution immediately after encountering the first error.
- Execution Performance: Due to the translation step, compiled code generally runs faster than interpreted code, as the latter incurs overhead in translating and executing line-by-line.
- Portability: Interpreted code can be more portable since it relies on the interpreter present on the target platform, while compiled code may need to be recompiled for different platforms.
Everything in JavaScript happens inside an Execution Context
When JavaScript code is executed, Execution Context is created and it is called Global Execution Context (GEC).
- JavaScript program is executed in TWO PHASES inside Execution Context
- MEMORY ALLOCATION PHASE - JS engine goes throughout the program and allocates memory of Variables and Functions declared and stores undefined and function code respectively as its value.
- CODE EXECUTION PHASE - JS engine now goes through the code line by line and executes the code.
- A Function is invoked when it is called and it acts as another MINI PROGRAM and creates its own Execution Context.
- Return keyword returns the Control back to the Parent Execution-Context, from where the Function is called and the Execution Context of that function is DELETED.
- CALL STACK maintains the ORDER of execution of Execution Contexts. It CREATES Execution Context whenever a Program starts or a Function is invoked and it pops out the Execution Context when a Function or Program ENDS.
- Undefined is like a placeholder till a variable is not assigned a value.
Execution Context
JavaScript interpreter creates a new context whenever it’s about to execute a function or script. Every script/code starts with an execution context called a global execution context (GEC). And every time we call a function, a new execution context is created and is put on top of the call stack. The same pattern follows when we call the nested function which can call another nested function.
Execution context has two components
- Memory component / variable environment: variable and function values are stored in a key value format.
- Code component / thread of execution: It is a place where whole JavaScript code is executed
this
this is a property of the execution context. only function calls establish a new this context (because it creates a new execution context)
Hoisting
JavaScript Hoisting refers to the process whereby the interpreter appears to move the declaration of functions, variables or classes to the top of their scope, prior to execution of the code.
https://developer.mozilla.org/en-US/docs/Glossary/Hoisting
variables defined with var and function declaration are hoisted, var takes undefined value and function contains its code.
let and const are also hoisted (in a different way: they exist in Temporal Dead Zone (TDZ) for the time being)
classes are hoisted — but they're in the Temporal Dead Zone (TDZ) until their declaration line runs (identical to let/const)
The Temporal Dead Zone exists until a variable is declared and assigned a value.
window.<variable> OR this.<variable> will not give the value of a variable defined using let or const.
Kind | What gets hoisted? | Value before declaration | Example |
var | Binding + initialized to undefined | undefined | console.log(x); // undefined var x = 5; |
function declaration | Binding + the function itself | the function code | foo(); // function code function foo() {} |
let, const, class | Binding only — TDZ until init | throws ReferenceError | new Foo(); // throws ReferenceError class Foo {} |
precedence order: variable assignment > function declaration > variable definition
e.g
var a = 10;
function a() {}
typeof a will be number
var a;
function a() {}
typeof a will be function because variable is not assigned only defined
function a() {}
var a;
typeof a will be function because variable is not assigned only defined
Blocked Scope
let & const is block scoped, means it is not accessible outside of the block, but var can be accessible because var is function scoped.
Hoisting vs Memory Component of Execution Context
Hoisting is a mechanism in JavaScript where variable and function declarations are moved to the top of their containing scope during the compile phase. This means that regardless of where variables and functions are declared in the code, they are moved to the top of their scope.
The memory component of an execution context refers to the memory space allocated for variables and function references during the execution of a JavaScript program.
Event Loop
Event loop is used to handle asynchronous operations in JS.
The event loop is a constantly running process that monitors both the callback queue and the call stack.If the call stack is not empty, the event loop waits until it is empty and places the next function from the callback queue to the call stack. If the callback queue is empty, nothing will happen:
https://www.javascripttutorial.net/javascript-event-loop/
- Callback functions of Web APIs are first stored in the Web API environment and then transferred to some queue from where the event loop picks them up and places it in the call stack, whenever the call stack is empty.
- All Callback functions (except promise callback and mutation observer) are transferred to callback queue or task queue or macrotask queue.
- Promises callback and mutation observer are transferred to the microtask queue.
- Microtask queue tasks are given priority over Macrotask queue tasks, event loop will pick tasks from callback queue, only when all tasks of microtask queue are done.
- Too many microtask queue tasks generated can cause Starvation (not giving time to callback queue tasks to execute).
- If the value of the expression following the await operator is not a Promise, it's converted to a resolved Promise i.e. await will also trigger an item into microtask queue
Order:
- Statement
- Microtask Queue - Promise callbacks/mutation observer (microtask queue)
- Web worker (port/window.eventHander, etc..)
- Macrotask Queue - all other callbacks e.g. setTimeout (callback/macrotask queue)
JavaScript is synchronous single-threaded language
Synchronous means one at a time i.e. one line of code is being executed at a time in order the code appears.
Single threaded means that one command is being executed at a time.
So in JavaScript one thing is happening at a time.
Sync kar lo/ sync me rakhna (we generally use in our day-to-day life): means keep everything in a single thread, means data in all devices are the same.
JavaScript is single-threaded, meaning it processes one task at a time. However, it can handle asynchronous operations efficiently using the event loop.
Synchronous
One task at a time (single threaded)
Asynchronous
multiple task at a time (multi threaded)
parallel programming
Asynchronous Function
Functions running in parallel with other functions are k/n as asynchronous function
e.g. fetch, setTimeout, setInterval, etc.
How to perform a specific task on completion of asynchronous function
- Promise
- callback function - it grows the code horizontally and gives control to the asynchronous function
Inversion of control
When we pass a callback (instead of using a promise) to an asynchronous function to process (after the async task is done), we give control to that async. function. This is k/n as inversion of control.
Callback Hell
Callback hell or "pyramid of doom," is a situation where there is a deep and complex nesting of callback functions. This generally happens while handling asynchronous code, where operations are performed one after the other, and each operation requires a callback to handle its result.
It grows the code horizontally
Async flow
Async flow (asynchronous control flow) refers to the design patterns, mechanisms, and architectural structures used in programming to manage operations that take time to complete without freezing or blocking the execution of the main program thread.
In a traditional synchronous flow, code executes strictly line-by-line. If line 2 requests data from a slow database, lines 3, 4, and 5 must sit completely frozen until that network round-trip finishes.
In an asynchronous flow, the program initiates a time-consuming task, sets it aside in the background, and immediately moves on to execute subsequent lines of code. When the background task finally completes, the program hooks back into it and handles the result.
Asynchronous control flow can be handled in 4 ways
- Callbacks
- then/catch in Promises
- async/await in Promises
- Reactive Streams & Observables: For highly complex async workflows where data arrives continuously over time (like WebSockets, mouse coordinates, or user typing events), arrays or single promises aren't enough, Reactive Streams (via libraries like RxJS) can be used
Reactive Streams
A Reactive Stream is an architectural standard for handling continuous, real-time data feeds asynchronously without blocking memory. Its primary superpower is managing Backpressure—preventing a fast data producer from crashing a slow data consumer.
Without Reactive Streams: If a server pushes 10,000 logs/second to a database that can only process 1,000/second, the unmatched data builds up in memory, causing an Out of Memory (OOM) crash.
With Reactive Streams: The system switches from a Push model (forcing data onto the consumer) to a Pull model (the consumer explicitly asks for what it can handle). The consumer can tell the producer: "Send me exactly 5 items, then wait until I ask for more."
The Stream Lifecycle (The Handshake)
- Subscribe: The Subscriber connects to the Publisher.
- Handshake: The Publisher hands over a Subscription object.
- Request: The Subscriber requests specific delivery limits (e.g., request(2)).
- Deliver: The Publisher fires onNext() to deliver those 2 items, then halts completely until the next explicit request token arrives.
JavaScript (Frontend): Implemented via RxJS (library) using Observables to handle complex, messy UI streams like real-time search typing debounces, drag-and-drop mechanics, and WebSocket updates.
Promise
Promise is an object that represents the eventual completion of asynchronous operation. It represents a result that may be available now, or in the future or never.
Promises are used to handle asynchronous operations in JavaScript.
Promise can be consumed/handled in 2 ways:
- then, catch
- async/await
Promise.all vs allSettled, race, any
- Promise.all: It takes an array of promises and returns a resolved promise when all the promises are resolved or returns a rejected promise as soon as any of the promises is rejected. Waits for all promises to be fulfilled or any one to be rejected.
- Promise.allSettled: It takes an array of promises and returns a resolved promise (containing array of result of each promise) when all the promises are settled (either rejected or resolved). Waits for all promises to settle (either fulfilled or rejected) and returns an array of results with information about each promise.
- Promise.race: It takes an array of promises and returns a resolved or rejected promise as soon as the first promise is resolved or rejected. It's a race, and the first one to finish (fulfil or reject) determines the outcome.
- Promise.any: It takes an array of promises and returns a resolved promise as soon as any of the promises is fulfilled/resolved otherwise returns a rejected promise when none of the promise is resolved. It's like trying different options, and the first successful one wins. If all options fail, you get a report of why they failed.
if the given array of promises does include a value instead of promise, then it directly returns the value in the result array
- all → all must win
- allSettled → wait for everyone, no matter the outcome
- race → first across the line, win or lose
- any → first winner only, ignores losers (until all lose)
Async/Await
- Async function is used to handle asynchronous tasks in JavaScript.
- Asynchronous tasks are generally handled though promises and promises can be consumed in 2 ways, async/await or then/catch.
- Await keyword can only be used inside an async function.
- Async function always returns a promise, if we directly return a promise from the async function then it gets returned as it is, otherwise if we return any value, then resolved promise with that value will be returned.
Difference b/w .then/catch & async/await
In async/await JS engine appears/fakes to wait at the line where await is used, till the given promise is resolved and does not proceed further in the code.
While in .then/catch, the JS engine does not wait for the promise to resolve and proceed further in the code, and whenever the promise gets resolved it calls the attached callback function.
And None of them block the thread or call stack.
Closure
Closures occurs when a function retains access to variables from its outer (enclosing) function even after that outer function has completed execution.
A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). i.e. closure = function + its lexical environment
Whenever a function is returned from a function, even if it is vanished in the execution context, It's not just that function alone it returns but the entire closure (function along with its lexical scope).
In closure reference of the variables in the scope is there, but not its value
ref: https://www.youtube.com/watch?v=eBTBG4nda2A&list=PLlasXeu85E9cQ32gLCvAvr9vNaUccPVNP&index=13
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures
Uses of Closure: Memoization, Currying, Once Function, Data hiding and encapsulation, debounce/throttle
Cons: it can take lots of memory, because those variables are not garbage collected.
Garbage Collector
It frees variables from memory which are no longer in use, or have never been used.
JavaScript is a garbage-collected language, meaning that memory management is handled automatically.
(GC) is a mechanism that automatically manages memory by identifying and reclaiming memory that is no longer needed or reachable by the program. It helps prevent memory leaks and ensures efficient memory usage in programs.
Mark and Sweep Algorithm
"Mark and Sweep" is a garbage collection algorithm used by programming languages like JavaScript to manage memory and automatically reclaim memory that is no longer in use.
The "Mark and Sweep" algorithm ensures that memory occupied by objects that are no longer reachable or referenced by the program is properly released, preventing memory leaks and improving the overall performance of the application.
Here's how the Mark and Sweep algorithm works:
- Mark Phase: The first step of the algorithm involves marking all reachable objects in memory. Objects that are still being used by the program are marked as "live," while the not accessible ones are left unmarked ("dead").
- Sweep Phase: In the sweep phase, the garbage collector goes through the entire memory space and deallocates (reclaims) memory occupied by unmarked (dead) objects. This involves releasing memory that is no longer needed and making it available for future allocations.
Lexical Environment
The Local memory (Created during Execution Context of that function) + Lexical environment of its parent.
e.g when the execution reach a use of a variable/function, it will search for its existence in the local memory of that function, if it's not found it will go to the lexical environment of its parent (there is a reference of lexical environment of its parent in local memory of a function), and so on. This chain is known as the Scope Chain.
Scope
Scope is till where we can access a function/variable.
Block
Multiple statements formed in a group enclosed in curly brackets forms a block
Block is needed because, sometimes Javascript expects a single statement to run, but we need to run commands with multiple statements which is only possible by block. e.g if else, for, etc
Error Types
There are three types of error:
- referenceError - given where variable/function does not have memory allocation
- typeError - given when we change type that is not supposed to be changed
- syntaxError - when proper syntax(way of writing a statement) is not used
Anonymous Functions
It is a function that does not have any name associated with it. An anonymous function is not accessible after its initial creation, it can only be accessed by the variable it is stored in as a function, as a value.
It is generally used at places where function is used as values e.g. arrow function, and callback function
https://www.geeksforgeeks.org/javascript-anonymous-functions/
High Order Function
A high order function is a function that accepts one or more functions as arguments or returns a function.
Function Statement / Function Declaration
regular function
<function>.name gives name of the function
<function>.length gives count of parameters of the function
Function Expression
A function stored in a variable as its value
Function expression can’t be called before its declaration because the value of the variable will be undefined till then.
First Class Function / First Class Citizen
The ability of function to be used as values is k/n as First Class Function.
IIFE
immediately Invoked Function Expression - self executing function
(function() {
// logic
})()
Generator Function
Generator functions are used to generate values on-demand (on the fly).
Generator functions are a special type of function in JS that can be paused and resumed. They allow us to generate a sequence of values lazily.
Generators are used in iterable functions of in-built data structures of JS.
Pure Function
Deterministic: A Pure Function is a function that always returns the same result for a given set of arguments. It does not depend on any state or data change during a program’s execution, rather it only depends on its input arguments.
No Side Effects: A pure function doesn't cause any observable effects outside of its scope. It means it doesn't modify variables outside of its own scope, it doesn't interact with the DOM, it doesn't make API calls, and it doesn't change any external state.
E.g.
function add(a, b) {
return a + b;
}
here add is a pure function
let total = 0;
function addToTotal(value) {
total += value; return total;
}
here addToTotal function modifies the total variable outside of its scope. Therefore, it is not a pure function.
Constructor Function
In JavaScript, a constructor function is used to create objects.
// constructor function
function Person () {
this.name = 'John',
this.age = 23
}
// create an object
const person = new Person();
Difference b/w regular and arrow function
ref: https://medium.com/swlh/javascript-arrow-functions-vs-regular-functions-5ec4a9076796
- Syntax
- this binding
In regular functions, this keyword represents the object that calls the function, which could be the window, the document, a button or whatever.
In arrow functions the this keyword represents, this of the object where it is defined. - Argument binding
Unlike regular functions, arrow functions do not have an arguments binding. However, they have access to the arguments object of the closest non-arrow parent function. - Using new keyword
Regular functions are constructible (constructor function) and callable.
Arrow functions are only ‘callable’ and not constructible
hence new keyword can’t be used with arrow function
JSON and Object literals
Object Literals: It is a JS Object. it is a comma-separated list of key-value pairs wrapped in curly braces. It can be taken as a 'container' for data.
JSON: JavaScript Object Notation is a textual/string format of a JS Object.
JSON cannot be an object. JSON is a string format. The data is only JSON when it is in a string format. When it is converted to a JavaScript variable, it becomes a JavaScript object.
const myJSON = '{"name":"John", "age":30, "car":null}';
const myObj = JSON.parse(myJSON);
.call() & .apply()
The call() method calls the function with a given this value (object) and arguments provided individually.
When we don't specify this, it'll refer to the globalThis i.e. window in the browser's context.
in apply() we pass arguments in array
.bind()
the bind() method creates copy of the function with a given this value(object) and arguments provided individually
bind is same as call() but instead of calling the function, it's just create clone of the function, which we can invoke/call later
https://www.youtube.com/watch?v=75W8UPQ5l7k
Polyfill (Shims)
Polyfills are used to add missing features to a browser that it does not natively support. It’s like browser fallback.
Shims are often used to provide a consistent API or bridge differences between environments, which can include polyfilling missing features. e.g. jQuery
In practice, the terms "polyfill" and "shim" are sometimes used interchangeably, and their specific meanings can vary depending on the context in which they are used.
ProtoType
Whenever we create a function/object/array, the JS engine attaches a prototype property in it.
Prototype property is basically an object (also known as Prototype object), which contains some functions like toString, valueOf, map, reduce, etc.
We can also attach methods and properties in a prototype object, which enables all the other functions/object/array to inherit these methods and properties.
We can access the prototype of an object using: Object.getPrototypeOf(<object name>) or <object name>.__proto__
Prototypes are the mechanism by which JavaScript objects inherit features from one another. Basically, when you try to access a property of an object: if the property can't be found in the object itself, the prototype is searched for the property. If the property still can't be found, then the prototype's prototype is searched, and so on until either the property is found.
Compose & Pipe function
The compose() function takes a list of functions and returns a new function. This new function, when called with an argument, will apply the functions from right to left.
The pipe() function is similar to compose(), but it applies the functions from left to right.
Reduce Function
It is used to get a single value out of array, value can be anything, string, number, object, array, etc
If you don't supply an initial value then the FIRST element of the array is used as initial value (and that element is skipped in the loop)
Proxy
In JavaScript, a Proxy is a built-in object that allows us to customise the fundamental operations (such as property access, assignment, function invocation, etc.) of an object.
It provides a way to define custom behaviour for these operations, effectively allowing us to create a "wrapper" around an object and control how it interacts with the outside world.
const proxy = new Proxy(target, handler);
Object.defineProperty() vs Proxy
Object.defineProperty
- Object.defineProperty is a method that allows you to add a new property or modify an existing property on an object and define certain behaviours for that property.
- It does not return a new object and modify the given object.
- Object.defineProperty(obj, prop, descriptor);
Proxy
- It returns a new object with the given behaviour applied
- const proxyObj = new Proxy(obj, handler);
Observer
Observer APIs are used to detect changes in the applications.
- MutationObserver: Mutation Observer observes the DOM tree.
- IntersectionObserver: Intersection observer observes a DOM element’s visibility and positions.
- ResizeObserver: ResizeObserver observes the changes in the dimensions of a DOM element.
- PerformanceObserver: PerformanceObserver is used to observe performance measurement events and be notified of new performance entries as they are recorded in the browser's performance timeline.
Module
JavaScript modules allow us to break up our code into separate files. This makes it easier to maintain the code-base.
It relies on the import and export statements.
e.g.
person.js
const name = "Jesse”, age = 40;
export { name, age };
main.html
<script type="module">
import { name, age } from "./person.js";
</script>
Falsy
A falsy (sometimes written falsey) value is a value that is considered false when encountered in a Boolean context.
E.g. null, undefined, 0, -0, “”, false, NaN, 0n, document.all()
Catch in try/catch
A Catch can catch errors for all the then/catch (promises) above it
e.g. 1.
getUser(1)
.then(user => getProfileImage(user.username))
.then(image => optimizeImage(image))
.then(result =>console.log("Done!"))
.catch(err => console.error(err));
Here the last catch can catch errors from all the 3 then’s above it
e.g. 2.
getUser(1)
.then(user => getProfileImage(user.username))
.catch(err => {
console.warn("Image failed, using default avatar!");
return "/images/default-avatar.png"; // 🟢 Heals the chain by returning a fallback value
})
.then(image => optimizeImage(image)) // 🟡 This will STILL run!
.then(result => console.log("Done!"))
.catch(err => console.error("Final catch:", err));
Here the 1st catch will only catch errors from the then’s above it (here only 1) and the main getUser function
And the 2nd catch will catch errors from the 2 then’s b/w the two catches
Finally
The finally statement in try catch finally defines a code block that always runs regardless of the result.
finally() does not receive any argument.
Throttle & Debounce
Debounce & Throttle is used to limit the rate of the given function call
- Throttle: the given function will be called at most once per specified period.
- It is a technique in which there is always a specific delay(not more not less) b/w two function calls.
- e.g. shooting game, pistol gun can only be fired after some time is passed (reload duration)
- Debounce: the given function will be called after a specified period after the caller stops calling the decorated function.
- It is technique in which the given function is called only after a certain delay of time has been passed since the last trigger/firing of the event
- e.g. search bar in any e-commerce website
JS Data Structure & Time Complexity
Data Structure | Insertion | Deletion | Access | Search | Iteration | Size |
|
|
|
|
|
|
|
MAP: map = new Map() | map.set(key, val) | map.delete(key) | map.get(key) | map.has(key) | map.keys(), map.values(), map.entries() | map.size |
| Average: O(1) | Average: O(1) | Average: O(1) | Average: O(1) | Average: O(n) | Average: O(1) |
| Worst: O(log n) | Worst: O(log n) | Worst: O(log n) | Worst: O(log n) | Worst: O(n) | Worst: O(1) |
|
|
|
|
|
|
|
OBJECT: obj = { } | obj[key] = val | delete obj[key] | obj[key] | obj.hasOwnProperty(key) | Object.keys(obj), Object.values(obj), Object.entries(obj) | Object.keys(obj).length, Object.values(obj).length, Object.entries(obj).length |
| Average: O(1) | Average: O(1) | Average: O(1) | Average: O(1) | Average: O(n) | Average: O(n) |
| Worst: O(n) | Worst: O(n) | Worst: O(n) | Worst: O(n) | Worst: O(n) | Worst: O(n) |
|
|
|
|
|
|
|
SET: set = new Set() | set.add(val) | set.delete(val) | set.has(val) | set.has(val) | set.values() | set.size |
| Average: O(1) | Average: O(1) | Average: O(1) | Average: O(1) | Average: O(n) | Average: O(1) |
| Worst: O(n) | Worst: O(n) | Worst: O(n) | Worst: O(n) | Worst: O(n) | Worst: O(1) |
|
|
|
|
|
|
|
ARRAY: arr = [ ] | arr.push(val), arr.unshift(val) | arr.pop(), arr.shift() | arr[idx] | arr.indexOf(val), arr.includes(val) | arr.forEach() | arr.length |
At the end | push: O(1) | pop: O(1) | Average: O(1) | Average: O(n) | Average: O(n) | Average: O(1) |
At the Beginning | unshift: O(n) | shift: O(n) | Worst: O(1) | Worst: O(n) | Worst: O(n) | Worst: O(1) |
Spread vs Rest Operator
The main difference between rest and spread is that the rest operator puts the rest of some specific user-supplied values into a JavaScript array.
But the spread syntax expands iterables into individual elements.
Spread operator deep copies the primitive properties(1st depth) of the object but for the nested objects it does only shallow copying
Object Spread operator actually is internally the same as Object.assign(). Following 2 lines of code are totally the same.
let aClone = { ...a };
let aClone = Object.assign({}, a);
null vs undefined
null is a actual value assigned to a variable, which represents no value/null
and when a variable is declared but not initialised by any value, it is by default assigned undefined to it
console.log(null == undefined) // true because both of then does not represents a value
console.log(null === undefined) // false because typeof null is object, but type of undefined is undefined
undefined vs undeclared
An undeclared variable is one that hasn't been declared at all, while an undefined variable is one that has been declared but hasn't been assigned a value (not initialised).
e.g.
let y;
console.log(y); // undefined, because y is declared but has not been assigned any value (not initialised)
console.log(x); // ReferenceError x is not defined
z = 10; // in strict mode // ReferenceError z is not defined
var vs let vs const
var is function scoped, let & const are block scoped.
when we use const to declare a variable, it means that the variable cannot be reassigned to a different value. However, it does not make the value itself immutable.
e.g.
const myObject = { key: 'value' };
myObject.key = 'new value'; // This is allowed
myObject = { anotherKey: 'another value' }; // This will throw an error because we are trying to reassign myObject
In this case, the myObject variable itself cannot be reassigned, but we can still modify the properties of the object it points to.
In the case of strings, numbers, and booleans, we cannot reassign the variable because they are primitive values, and const prevents the variable from being reassigned.
Objects, on the other hand, are reference types in JavaScript. When we create an object, we are actually creating a reference to a location in memory. Using const with an object prevents reassignment of the variable to a different memory location, but it does not prevent us from modifying the properties of the object at that memory location.
Object.freeze() vs Object.seal()
Object.freeze() does not let to add/modify/remove any property/key to object
but Object.seal() can modify any existing property but doesn't let us add/delete property.
If we attempt to modify a property on a frozen object in strict mode, JavaScript will fail silently or throw an error (in non-strict mode, it will fail silently).
Argument vs Parameter
The parameters are the aliases for the values that will be passed to the function. The arguments are the actual values.
function foo( a, b, c ) { }; // a, b, and c are the parameters
foo( 1, 2, 3 ); // 1, 2, and 3 are the arguments
Math.max/min
console.log(Math.min()) // Infinity
console.log(Math.max()) // -Infinity
NaN = Not a Number
console.log(NaN == NaN) // false
console.log(NaN === NaN) // false
console.log(Math.max(NaN, 1)) // NaN
console.log(Math.min(NaN, 1)) // NaN
console.log(Math.min(NaN, Infinity)) // NaN
console.log(Math.max("any string", 1)) // NaN
The Math.max() and Math.min() functions return NaN if any parameter isn't a number and can't be converted into one (of course NaN cannot be converted into a number).
Infinity
1/0 = Infinity
-1/0 = -Infinity
0 / Infinity = 0
Infinity x 0 = NaN
Infinity x 1 = Infinity
Infinity + Infinity = Infinity
Infinity - Infinity = NaN
Infinity x Infinity = Infinity
Infinity / Infinity = NaN
Operator Precedence
Number > String > Boolean
When using > to compare two operands, if either operand is a number, Javascript will first convert the string/boolean to its equivalent number and then numerically compare.
i.e. ’10’ > 9
Only when both operands are string, they are compared lexicographically. i.e. character by character until they are not equal or there aren't any characters left. The first character of '10' is less than the first character of '9' hence '10' is < '9' i.e. ’10’ < ’9’
Also, these operators work from left to right
== Operator
- Comparing Primitives (String, Number, Boolean):
- If both of the operands are primitives and of the same type, == performs a simple value check.
- e.g.
- 5 == 5; // true
- 'hello' == 'hello'; // true
- true == false; // false
- Comparing Objects:
- if both of the operands are objects, then JS compares them by their reference not value.
- i.e. if both objects are pointing to same memory location, if yes then it will return true otherwise false
- e.g.
- const obj1 = { key: 'value' };
const obj2 = { key: 'value' };
console.log(obj1 == obj2); // false, because they are different objects in memory - const arr = [1];
console.log(arr == arr); // true, because they are same objects in memory - console.log({} == {}); // false, because they are different objects in memory
- Comparing Different Types: Number > String > Boolean
- Number and String:
- If one operand is a number and the other is a string, JavaScript tries to convert the string to a number and then compares them.
- e.g.
- 5 == '5'; // true, because the string '5' is coerced to the number 5
- 5 == 'yo bro'; // false, because the string 'yo bro' is coerced to the NaN
- Boolean and Non-Boolean:
- Number and Boolean
- When comparing a boolean and a number value, the boolean is converted to a number (true becomes 1, false becomes 0).
- e.g.
- true == 1; // true
- false == 0; // true
- false == 1; // false
- true == 2; // false
- String and Boolean
- When comparing a boolean and a string value, the boolean is converted to its string value (true becomes “true”, false becomes “false”)
- e.g.
- “true” == true // true, because true if coerced to “true”
- “false” == false // true
- “Nyc pik” == true // false
- Null and Undefined:
- null and undefined are equal when using ==
- e.g.
- null == undefined; // true
- Object and Primitive:
- If one operand is an object and the other is a primitive value, the object is converted to a primitive value using the object's valueOf() and toString() methods.
- e.g.
- 'hello' == new String('hello'); // true, because the object, new String('hello') is coerced to the primitive value 'hello'
- 1 == [1]; // true, because the object [1], is converted to string “1” using [1].toString() and 1 == “1” as “1” is coerced to 1 so 1 == 1, which is true
- false == [0] // true
- [0] == 0 // true, “0” == 0 -> 0 == 0 -> true
- [0] == “” // false, “0” == “” -> false
- Different Objects:
- When comparing two different object types, the references are compared, not the contents of the objects.
- e.g.
- [] == {}; // false, because they are different objects in memory
e.g.
console.log([1] == 1) // 1 == 1 = true
console.log([1] == '1') // 1 == 1 = true
console.log(['1'] == '1') // 1 == 1 = true
console.log(['1'] == 1) // 1 == 1 = true
console.log([1] == ['1']) // false as both are arrays (not the same reference)
console.log(new Boolean(true) == 1) // 1 == 1 = true
console.log(new Boolean(true) == new Boolean(true)) // false as both are actually objects
console.log(Boolean(true) == '1') // true == '1' = 1 == 1 = true
console.log(Boolean(false) == [0]) // true == [0] = 0 == 0 = true
console.log(new Boolean(true) == '1') // object true == '1' = 1 == 1 = true
console.log(new Boolean(false) == [0]) // object fasle == [0] = // false as both are actually objects
console.log(null == undefined) // true
+ Operator
+<anyString which is not a number> = NaN, e.g. ‘biro’
+<anyString which is a number> = that number, e.g. ‘1’
+<anyNumber> = that number
+{ } = NaN
+[ ] = 0
// In mathematical operations, + works on both numbers and strings (used in string concatenation).
// Hence, if any of the operands is not a number(string), using + converts all operand/s to string and concatenates.
String({ }) = “[object Object]”
String([ ]) = “”
String(123) = “123”
to check if a key present in object
- if ('key' in myObj), BUT the in operator matches all object keys, including those in the object's prototype chain.
- so best way is to use
myObj.hasOwnProperty('key')
Sparse Array
In JavaScript, a sparse array is an array in which not all elements have been assigned a value. This means that there are "holes" or empty slots in the array, where no value is stored. These holes are represented by undefined.
Array.flat(depth)
It is used to flatten a array till given depth
Array.prototype.flat() method can be used with sparse arrays. When we apply flat() to a sparse array, it will remove empty slots (undefined elements) and return a new array with only the defined elements.
Array.Sort()
Array.sort expects a compare function that defines the sort order.
If omitted, the array elements are converted to strings, then sorted lexicographically
i.e [999, 1111, 111, 2, 0] will become [0, 111, 1111 ,2, 999]
Function Currying
Currying is a technique in functional programming, in which a function with multiple arguments is converted into several functions in sequence, each taking one or more arguments.
It can be done in 2 ways:
- Closure methods
- Bind
Memoization
Memoization is a programming technique which is used to increase a function’s performance by caching its previously computed results.
Because JavaScript objects behave like associative arrays, they are ideal candidates to act as caches. Each time a memoized function is called, its parameters are used to index the cache. If the data is present, then it can be returned, without executing the entire function. However, if the data is not cached, then the function is executed, and the result is added to the cache.
Passing by value, Passing by reference & their relation with react memoization
- Passing by value: All primitives (number, string, boolean, etc) in JS are passed by value.
Means when we pass a primitive data type variable as an argument to a function. Any change in that argument inside that function does not affect the original variable.
e.g.
const count = 1;
function increaseCount(count) {
count++;
return count;
}
const increaseCountResp = increaseCount(count);
console.log("count", count); // 1
console.log("increaseCountResp", increaseCountResp); // 2
- Passing by reference: objects (object, array, function, etc) in JS are passed by reference. Means when we pass an object data type variable as an argument to a function. Any change in that argument inside that function also affects the original variable.
e.g.
const obj = { a: "a" };
function addGreet(obj) {
obj.greet = "Hello World";
return obj;
}
const greetResp = addGreet(obj);
console.log("obj", obj); // { a: 'a', greet: 'Hello World' }
console.log("greetResp", greetResp); // { a: 'a', greet: 'Hello World' }
- In react, React.memo is used to memoize a component. It prevents re-rendering of the component if its props have not been updated. And in react, props are passed by value. So if the props which are passed to a component does not change, then the memoized component can be used directly which can prevent its re-render.
AbortController
The AbortController is a built-in JavaScript API that allows us to cancel ongoing asynchronous operations like fetch requests, timers, event listeners, etc. It prevents memory leaks and race conditions by stopping unnecessary network traffic if an action is no longer needed.
const controller = new AbortController();
// Pass the signal to the fetch request
fetch('/api/data', { signal: controller.signal })
.catch(err => {
if (err.name === 'AbortError') console.log('Request cancelled');
});
controller.abort(); // Cancel the request
ES Modules (ESM) vs CommonJS (CJS)
ES Modules (ESM) and CommonJS (CJS) are two different systems for organising and importing/exporting code in JavaScript.
- Syntax: ES Modules use import and export statements to define dependencies and expose functionality between different modules, while CommonJS uses require() to import modules and module.exports or exports to export functionality.
- Async Import: ES Modules support asynchronous loading of modules using the import() function, allowing you to conditionally load modules while CJS only supports synchronous Import.
- Named and Default Exports: ESM supports both named exports (exporting specific entities from a module) and default exports (exporting a single entity as the module's default), while CJS supports only named exports.
ECMAScript
ECMAScript is the standardised specification of the rules, syntax, and features of writing code in a scripting language.
JavaScript is a practical implementation of that specification, used for creating dynamic and interactive web applications and supported by various environments and platforms.
Features of ES6
Headless UI
Headless UI refers to a design pattern for frontend components where the component provides all the functionality, state management, and accessibility logic, but zero default styling and we can write our own custom css.
Traditional component libraries (like Material UI or Bootstrap) enforce a specific look and feel out of design of the current system. A headless component library provides only the raw structural engine, leaving the visual presentation completely up to us.
[ Traditional Library ] ─── Forces ───> Logic + Accessibility + Visual Themes/Styles
[ Headless UI Library ] ─── Forces ───> Logic + Accessibility + (Your Custom CSS)
E.g. Radix UI, Headless UI (from tailwindCSS), React Aria (from Adobe)
Interpolation
In JavaScript, interpolation is the process of inserting strings or values into an existing string.
e.g.
t("MNgo Quiz is {{evaluation}}", { evaluation: 'fantastic' })
Idempotent
An operation is known as idempotent if applying it multiple times has the same effect as applying it once.
e.g.
PUT HTTP request is idempotent because it will update the same value multiple time for the same request
POST HTTP request is not idempotent as it will keep creating a new entry for the same request if called multiple times
Topological Order/Sort
A topological sort is a graph traversal algorithm in which a dependent node v is visited only after all its dependencies are visited i.e. In topological order a node u coming before v (u, v) means v is dependent on u.
A topological ordering is possible if and only if the graph has no directed cycles, that is, if it is a directed acyclic graph (DAG).
It is used in spreadsheets for dependency resolution of cells related to each other by some formula. In a spreadsheet, dependencies are parent nodes (cells referenced in formulas), and dependents are child nodes (cells containing formulas), so order will be parent then children.
e.g.
A1(0, 0): 10
B1(0, 1): =A1*2
Topological order: A1, B1
i.e. A1 must be processed before B1 because we can calculate value of B1 only when we know A1’s value
type sheetGraph { [dependencyCell: number]: dependentCells[ ] }
e.g.
{
“0_0”: [“0_1”]
}
i.e. when 0_0 value changes then 0_1’s value must also change
How npm works
npm (Node Package Manager) is a package manager for JavaScript and Node.js which is used to manage and distribute packages (libraries, frameworks, etc.). It allows us to easily install, update, and manage dependencies in our project.
- if we do npm install somewhere deep inside a project, it goes up and up till it finds package.json file or node_modules folder (root of the project), for installing dependencies of the project
- When we install a package, npm adds an entry for that package along with its versions in the package.json file under the "dependencies" section.
- Dependency Resolution: npm creates a package-lock.json file that locks package’s dependency versions and dependency tree (for sub packages), to ensure consistent and reproducible builds and they are compatible with each other.
- npm supports scoped packages, which group related packages together under a specific scope (e.g. @organisation/package).
NPM streamlines the process of managing dependencies and packages in JavaScript and Node.js projects
- installation
- version management
- dependency resolution
- script execution
Semantic Versioning (SemVer)
Semantic Versioning (SemVer) is a universal, standardized numbering system used in software engineering to communicate the exact nature of changes in a new code release
<major version>.<minor version>.<patch version>
- MAJOR: Incremented when you make breaking API changes. If a developer updates to this version, their existing application code might break, and they will likely need to rewrite parts of it.
- MINOR: Incremented when you add new features in a backwards-compatible manner. It introduces new capabilities, but it will not break any code written for older minor versions.
- PATCH: Incremented when you deploy backwards-compatible bug fixes. No new features are added; it simply repairs internal code mistakes.
value | desc |
version | Must match version exactly |
~version | Approximately equivalent, i.e. only accept new patch versions |
^version | Compatible with version, i.e. accept new minor and patch versions |
>version, >=version, <version, <=version | Version should match the condition |
1.2.x | 1.2.0, 1.2.1, etc., but not 1.3.0 |
* | Matches any version |
latest | Obtains latest release |
JS Tooling
JavaScript Tooling refers to the ecosystem of software applications, libraries, and utilities that developers use to write, build, test, optimize, and deploy JavaScript applications.
Modern JavaScript does not run raw in production anymore; it passes through a sophisticated pipeline designed to maximize developer productivity while ensuring the final code is as fast and compatible as possible.

What is Bundler
A bundler is a tool used in web development to combine multiple separate files, often written in different languages or technologies, into a single file or a smaller number of files.
Here are the key aspects and benefits of using a bundler:
- File Dependency Resolution: Bundlers analyse the codebase to determine the dependencies between different files, including JavaScript modules, CSS files, images, and more.
- Code Transformation: Bundlers can apply transformations to the code, such as transpiling newer JavaScript syntax into a version compatible with older browsers.
- Optimising and Minifying: Bundlers can optimise code by removing whitespace, renaming variables, and performing other operations to reduce file size. This is often referred to as minification.
- Module System Compatibility: Bundlers allow you to use modern JavaScript module systems (like ES6 Modules) and bundle them for browsers that may not natively support these features.
- Code Splitting: Bundlers support code splitting, which allows parts of the application to be loaded on demand, improving initial load times.
- Asset Management: Bundlers can handle assets like images, fonts, and other files, ensuring they are included and processed correctly in the final output.
e.g. Webpack, Rollup, Parcel, Browserify
Webpack
Webpack is a popular open-source module bundler for modern web applications. It's widely used in the JavaScript ecosystem to manage and bundle various assets like JavaScript files, CSS files, images, fonts, and more. Webpack helps optimise the loading and execution of web applications by intelligently combining and serving these assets.
- Webpack offers various optimization techniques, including minification, code splitting, and caching, to improve the performance of web applications.
- Webpack supports tree shaking, a technique that eliminates dead code (unused exports) from the final bundle. This helps reduce file size.
- Webpack is highly configurable through a webpack.config.js file. This file specifies how Webpack should process different types of files and how the final bundle should be generated.
- Webpack uses loaders to preprocess files. Loaders transform files from one format to another.
- Webpack provides a development server that enables Hot Module Replacement (HMR). This means that when you make changes to your code, the browser is updated in real-time without requiring a full page refresh.
Parcel
Parcel is a zero-configuration module bundler. It's designed to be extremely easy to set up and use, making it a good choice for quick prototyping or smaller projects.
Key Features of Parcel:
- Zero Configuration
- Built-in Support for Common Technologies: React, vue, Svelte, without extra configuration
- Code Splitting
- Hot Module Replacement (HMR)
Babel
Babel transpiles modern JavaScript code (ES6+ syntax) into a version of JavaScript that is compatible with older browsers and environments. This allows developers to write code using the latest language features while ensuring it can run on a wider range of platforms.
Babel is commonly used in React projects to transpile JSX syntax into regular JavaScript.
Babel can be configured using a configuration file (usually named .babelrc) or through configuration options in package.json.
Here's how Babel works:
- Parsing: Babel starts by parsing the input JavaScript code using a parser like Babel Parser. This step breaks down the code into an Abstract Syntax Tree (AST), which is a hierarchical representation of the code's structure.
- Transformation: Once the code is parsed, Babel can apply various plugins to the AST. These plugins can do things like transforming newer syntax into equivalent older syntax or adding polyfills for missing features.
- Generation: After the AST has been transformed, Babel then generates code from the modified AST. This code is typically in an older version of JavaScript that is widely supported across different environments.
- Output: The transformed code is then outputted, and it can be saved to a file or used directly in a browser or server environment.
Vite
Vite (pronounced veet from the French word for "quick") is a lightning-fast build tool and development server for modern web applications.
[ Webpack ] ── Bundles EVERYTHING first ──> Crawls entire dependency tree ──> Dev Server Ready (Slow)
[ Vite ] ── Starts Server INSTANTLY ──> Transforms only the requested files on-demand via Native ESM ──> Dev Server Ready (Fast)
Feature | Webpack | Vite |
Dev Engine | JavaScript (Babel/Terser) | esbuild (Written in Go; 100x faster) |
Dev Architecture | Bundle-based server | Native ESM (On-demand translation) |
Production Build | Custom Webpack compilation | Rollup (Highly optimized tree-shaking) |
Configuration | Complex and verbose | Minimal, zero-config default |
Source Maps
Source maps are a way to map a bundled (combined/minified) code back to an unbuilt state. When we build for production, along with minifying and combining our JavaScript files, we generate a source map too which holds information about the original files.
.env variable
An "environment variable," often referred to simply as an "env variable," is a variable outside of a program or application that stores configuration settings, system information, or any data that needs to be available to multiple processes or components within an operating system or application environment. These variables are used to customise the behaviour of software and to manage various aspects of the environment in which programs run.
Prettier
Prettier is an open-source code formatter that is used to format the code to follow a consistent style and layout. It supports various programming languages, including JS, TS, HTML, CSS, JSON, and more. Prettier helps teams maintain a standardised code style without manual formatting efforts or debates over code formatting conventions.
Linter
A linter is a code analysis tool used to check source code for potential errors, bugs, style violations, and adherence to coding standards or style guidelines. It helps identify issues early in the development process, before the code is executed or deployed, reducing the likelihood of bugs and making the codebase more maintainable.
e.g. ESLint (for JavaScript and TypeScript), Pylint (for Python), TSLint (for TypeScript), Stylelint (for CSS and Sass)
Husky
Husky is a popular JavaScript tool used to manage Git hooks. It allows developers to automatically run scripts - like linting, formatting, or testing at specific points in the Git workflow, such as before a commit or a push.
Unit Testing
Unit testing is a software development practice in which individual units or components (like functions, methods, or modules) of a program are tested in isolation from the rest of the application. The goal is to ensure that each unit of code works as expected.
Here are the key aspects of unit testing:
- Isolation: Unit tests focus on testing a specific piece of functionality in isolation. This means that external dependencies or interactions are typically replaced with mock objects or stubs.
- Fast Execution: Unit tests should be quick to execute. This allows developers to get fast feedback on the correctness of their code.
- Deterministic: A unit test should always produce the same result when run under the same conditions. This means that tests should not rely on external factors like the network or system time.
- Repeatable: Unit tests should be repeatable in different environments. They should produce the same results whether run on a developer's machine or in a CI/CD pipeline.
- Independent: Unit tests should be independent of each other. The success or failure of one test should not impact the results of another test.
- Good Test Coverage: The goal of unit testing is to achieve a high level of test coverage, ensuring that most if not all code paths are tested.
- Refactoring Safety Net: Unit tests act as a safety net when refactoring code. They help ensure that existing functionality remains intact after making changes.
End to End Testing
The primary goal of E2E testing is to ensure that all components or units work together correctly in a real-world scenario. It helps catch integration issues and ensures that the application functions as expected from the user's perspective. e.g. Selenium, Cypress, Puppeteer, and TestCafe
- Scope: E2E testing aims to test the entire flow of an application from the user's perspective. It simulates real user interactions with the system.
- Granularity: E2E tests are coarse-grained and cover a broader range of functionalities. They often involve multiple units and can span across different components or modules.
- Dependencies: E2E tests do not typically use mocks or stubs. They interact with the actual application and its dependencies as a real user would.
- Speed: E2E tests are slower compared to unit tests because they involve interactions with the entire application, including user interfaces, databases, and external systems.
Test Driven Development (TDD)
Test Driven Development (TDD) is a software development practice that focuses on creating unit test cases before writing the actual code.
Git
Revert a commit: git revert <commit id> or git reset –soft <commit id>
Git Rebase: naam pe dhyaan do -> re-base (change base of the feature branch to the latest commit of the main branch)
Ref: https://www.youtube.com/watch?v=0chZFIZLR_0
git merge vs git rebase:
- git merge: git merge combines the changes from one branch into another branch, creating a new "merge commit" that has two parent commits. git merge <source-branch>
- git rebase: git rebase moves or combines a sequence of commits to a new base commit. It essentially rewrites the commit history. git rebase <target-branch>
Debugging
Steps involved in Debugging
- Reproducing the Error: Before you can debug an issue, you need to be able to reproduce it consistently. This involves understanding the conditions or inputs that lead to the problem.
- Identify the Actual Error: Identifying an error in a wrong may result in the wastage of time. It is very obvious that the production errors reported by users are hard to interpret, and sometimes the information we receive is misleading. Thus, it is mandatory to identify the actual error.
- Find the Error Location: Once the error is correctly discovered, you will be required to thoroughly review the code repeatedly to locate the position of the error. In general, this step focuses on finding the error rather than perceiving it.
- Use Console Logs
- Set Breakpoints
- Step Through the Code
- Isolate the Problem: Narrow down the scope of the problem. Identify which part of the code is causing the issue. This may involve commenting out sections of code to see when the behaviour changes.
- Analyse the Error: The next step comprises error analysis, a bottom-up approach that starts from the location of the error followed by analysing the code. This step makes it easier to comprehend the errors. Mainly error analysis has two significant goals, i.e., evaluation of errors all over again to find existing bugs and postulating the uncertainty of incoming collateral damage in a fix.
- Test Potential Solutions: Implement potential solutions one at a time and test to see if they resolve the issue. Be cautious and make small, reversible changes.
- Document Your Findings: Keep track of what you've tried, what worked, and what didn't. This documentation can be valuable for future debugging or for team members.
- Retest and Verify: After making changes, ensure that the issue is resolved and that it didn't introduce any new problems.
SOLID Principles in OOP
The SOLID principles are a set of five design principles that are intended to guide software development in order to create maintainable, flexible, and scalable code.
- Single Responsibility Principle (SRP): Given class should have only 1 responsibility.
- Open-Closed Principle (OCP): Software entities (classes, modules, functions) should be open for extension but closed for modification, meaning new functionality can be added to the system without altering existing code.
- Liskov Substitution Principle (LSP): Objects of a subclass should be able to replace Objects of superclass without affecting the correctness of the program.
- Interface Segregation Principle (ISP): Breaking large interfaces into smaller, specific interfaces Instead of having monolithic interfaces.
- Dependency Inversion Principle (DIP): High-level modules should not depend on low-level modules, but both should depend on abstractions (interfaces or abstract classes).
How would you ensure that your team is adhering to established coding standards to keep consistency in the codebase?
To ensure that our team follows the established coding standards, we can follow these strategies:
- Coding Guidelines Document: We can create a document for defining a set of coding standards and rules to adhere while writing code.
- Keeping the custom defined code formatter file for multiple IDEs in the repo: We can keep the code formatter file in the repo itself (like settings.json in .vscode folder for vscode), which automatically formats the code on saving the file.
- Using linter on pre-commit: We can use linter, which checks and highlights the variation from the defined coding standards before pushing it to git repo.
- Setting Up linter in CI/CD pipeline: We can add linter in the CI/CD pipeline of the repo. So on each push, linter will analyse the code and highlight the coding format issues
- Defining Code Review Guidelines & following them while doing Code Reviews: Similar to the Coding Guidelines Document, we can also create a Code Review Guidelines Document and follow them while doing code reviews. We can comment at the line, where we found any breach of defined coding standards, while doing the code review or our peer’s PR.
Web, Browser & Optimisation
What does <!DOCTYPE html> mean?
<!DOCTYPE html> is a declaration at the beginning of an HTML document that defines the document type and version of HTML being used. By including <!DOCTYPE html> we signal the browser to use the latest standard mode, which helps ensure consistent rendering and behaviour across different browsers.
HTML Semantic
Instead of using divs everywhere, using correct html element to convey the meaning of the element like header, nav, main, section, article, aside, footer, figure, figcaption, details, summary, etc

HTML manifest attribute
The manifest attribute in HTML is used to specify the location of a web application's cache manifest file. The cache manifest file is a text file that lists the resources (like HTML files, CSS, JavaScript, images, etc) that should be cached by the browser when the user visits the site.
e.g.
<html manifest="example.appcache"></html>
HTML Quirks Mode
Quirks mode is a way for browsers to maintain backward compatibility with older web pages that were designed before the widespread adoption of standardised HTML and CSS specifications.
Omitting the <!DOCTYPE html> declaration or using an outdated/incorrect <!DOCTYPE> can trigger quirks mode.
Aria Attributes
ARIA (Accessible Rich Internet Applications) attributes are a set of special HTML attributes designed to make web content and applications more accessible to people with disabilities, particularly those who use screen readers.
ARIA attributes generally fall into three buckets: Roles, States, and Properties.
- ARIA Roles (role="...")
- Roles tell a screen reader what an element is, when standard HTML doesn't make it clear. It defines the element's purpose, like: role="search", role="tooltip", role="alert", role="tabpanel".
- E.g: <div role="button" tabindex="0">Click Me</div>
- ARIA States
- States define the current condition/state of an element. These are highly dynamic and usually change based on user interaction via JavaScript.
- aria-expanded: Tells the user if a dropdown or accordion is open (true) or closed (false).
- aria-checked: Indicates whether a custom checkbox or radio button is checked.
- aria-disabled: Tells the user that an element is visible but currently unusable.
- ARIA Properties
- Properties define the nature/relationships of an element. These are usually static and give extra context.
- aria-label: Provides a human-readable text label for elements that don't have text content (like an icon-only button).
- aria-labelledby: Links an element to another element that acts as its label (using an ID).
- aria-describedby: Links an element to a longer description (like a tooltip or error message).
- aria-live: Tells screen readers to announce updates to an element automatically (crucial for live chat boxes, alerts, or notification banners).
- aria-modal="true": tells screen readers that the underlying windows/content underneath the dialog are temporarily inert, meaning the user's focus should be completely trapped inside the modal overlay until it is closed.
- aria-valuemin & aria-valuemax: For a custom role="slider", aria-valuemin and aria-valuemax define the absolute minimum and maximum values allowed for the range, which gives critical context to assistive technologies. (Note: aria-valuenow is also essential to track the current value).
Iframe options
- src
- width, height
- loading - lazy,
- sandbox - allow-same-origin allow-scripts
- allow - geolocation; microphone; camera
- scrolling
Event Delegation
Event delegation is the process of not adding event listeners to all the child elements but only on parent and then handling events from there, using Event Bubbling.
Benefit of Event Delegation
- Improved Performance: This reduces the total number of event listeners, which can lead to better performance, especially on large pages or in applications with many interactive elements.
- Dynamic Elements: Event delegation works well with dynamically generated or added elements. Since the event listener is attached to a parent element that already exists in the DOM.
- Reduced Memory Consumption: Attaching event listeners to individual elements can lead to memory leaks in certain situations, especially if elements are frequently created and destroyed. Event delegation can help in this.
- Simplifies Event Management: Event delegation can lead to cleaner and more organised code, as you can handle events for similar elements in a single place.
Event Bubbling: (bubble burst in outwards direction)
When the element and its parent container both have an onClick event handler defined, then on clicking on the child element, the event handler function of the element and its parent both(in sequence from child to parent) gets called/invoked, this phenomenon is k/n as event bubbling.
i.e. in case of event bubbling, the event movement begins from the target to the outermost element in the file.
event.stopPropagation() is used in child’s event handlers to stop/prevent this phenomenon.
Event Capturing
Event capturing is the same as event bubbling, only here an event moves from the outermost element to the target.
Attaching Event Listener to Dynamically created DOM Element
e.g. suppose we created a select element dynamically and attached change event listener to it, then it will not work
const selectEle = document.createElement("select");
selectEle.dataset.selectFieldId = “mine”
// selectEle.addEventListener("change", handleProductChange); // does not works
element.addEventListener("event", handleEvent); does not work for dynamically created elements
we need to attach change event listener to the nearest static parent element of the select element
suppose #content is the static div in our html file, where we are rendering the DOM elements dynamically using javaScript, then we can attach eventListener to #content and use event delegation to handle change in our select element
const contentEle = document.getElementById("content");
contentEle.addEventListener('change', handleChange);
function handleChange(e) {
const selectFieldId = e?.target?.dataset?.selectFieldId;
if (selectFieldId === “mine”) {
// do the work
}
}
Feature Detection vs User-Agent Sniffing
Feature detection and user agent sniffing are two different approaches used in web development to achieve cross-browser compatibility. They serve as techniques to determine the capabilities of a user's browser.
Feature detection involves checking if a specific feature or capability is supported by the browser before attempting to use it.
if (typeof localStorage !== 'undefined') {
// localStorage is supported, use it
} else {
// Provide an alternative or fallback behaviour
}
User agent sniffing (or browser sniffing) involves examining the User-Agent header sent by the browser in the HTTP request to identify the type and version of the browser being used.
if (navigator.userAgent.indexOf('MSIE') !== -1) {
// Internet Explorer specific code
} else {
// Code for other browsers
}
Web APIs
Browser has superpowers that are lent to JS engine to execute some tasks, these are Web APIs such as:
- console (console.log())
- location (location.href = “”)
- DOM API (document.getElementById(“”))
- setTimeout
- fetch
- local storage (localStorage.getItem("”))
Local storage/Session storage/IndexedDB
All of them exist for the same origin and stores data on the client side.
sessionStorage | localStorage | indexedDB |
Maximum limit is 5 MB | Maximum limit is 5 MB | Maximum limit depends on browser |
Data in the sessionStorage exists till the browser is open. If we close the browser then our data will also erase automatically from the sessionStorage. | localStorage has no expiration time, Data in the localStorage persist till the user manually delete it | same as localStorage |
sessionStorage.setItem(“hi”, “hello”) | localStorage.setItem(“hi”, “hello”) | window.indexedDB.open("MyTestDatabase", 3); |
Cookies (Maximum limit is 4 KB)
Cookies are small files which are stored on a user’s computer for a domain. They are used to hold a modest amount of data specific to a particular client and website and can be accessed either by the web server or by the user’s browser.
Only the same website that saves information to a cookie can access it.
Shadow DOM
Shadow DOM is used for building reusable and encapsulated web components, helping to prevent style and script conflicts between different parts of a web page. The Shadow DOM provides a way to encapsulate the structure, style, and behaviour of a component within a scoped and isolated container.
Benefits of using the Shadow DOM:
- Encapsulation: Components with Shadow DOM are self-contained and encapsulated.
- Reusability: Shadow DOM enables developers to create reusable components that can be easily dropped into different parts of a web page or shared across projects.
- Isolation: Shadow DOM provides isolation for both styling and scripting, making it possible to create complex components without worrying about interference from external styles or scripts.
- Maintenance: Changes made within a shadow tree are less likely to affect other parts of the application, improving code maintenance and reducing unintended side effects.
Web Components
Web Components allow us to create reusable, encapsulated, and framework-agnostic HTML tags. Unlike components built in React, Vue, or Angular, Web Components run natively in the browser without requiring a heavy third-party library bundle or a compilation build step.
PWA (Progressive Web App)
Progressive Web Apps (PWAs) are a type of web application that delivers a native app-like experience to users while being built with web technologies (HTML, CSS, and JavaScript). They offer a range of benefits that combine the best of web and native app experiences.
- App like experience
- Offline functionality
- Faster loading (because of cached resources)
- Push notification
- Background task
- Responsive
Service Worker
A Service Worker is a JavaScript script that runs in the background of a web browser, independent of the web page, and enables advanced features like task execution in background, caching/offline support, push notifications, and background synchronisation.
It is a key component of Progressive Web Apps (PWAs) and can significantly enhance the performance, reliability, and user experience of web applications, especially in offline or low-network conditions.
The service worker life cycle
- Registration
- Installation
- Activation
Difference b/w Service Worker and Web Worker
Service Worker is used for tasks related to network request, caching, offline functionality, while Web Worker is used to parallelize tasks like heavy computation or data processing independently from the main thread.
Difference b/w http cache and service worker cache
HTTP cache is browser-level caching controlled by HTTP headers, while Service Worker cache is a programmable JavaScript cache offering more control, offline capabilities, and persistence for web applications.
WebGL
WebGL (Web Graphics Library) is a low-level JavaScript API that allows browsers to render high-performance, hardware-accelerated 2D and 3D graphics directly inside an HTML <canvas> element without requiring any third-party plugins.
Traditional web elements (HTML/CSS) are rendered by the computer's CPU. WebGL bypasses the CPU and executes code directly on the user's GPU (Graphics Processing Unit), which is specifically designed to calculate millions of pixel coordinates simultaneously.
Different types of cache
Browser Cache: browser-level cache done using http headers, stores static files locally, reducing the need to re-download them on subsequent visits.
Content Delivery Network (CDN) Cache: Distributes static content globally to reduce latency and improve load times.
Database Cache: Stores frequently accessed database query results in memory for faster retrieval.
Object Cache: Caches results of expensive function calls or calculations for reuse.
Memory Cache: Stores data in the server's memory for quick access and improved performance.
Process of Browser Caching
- The browser loads the resource for the first time, the server returns 200, and the browser downloads the resource file from the server, and caches the resource file and response header for comparison and to use in the next load.
- The next time when the client requests to load the resource, the time difference between the current time and the last return of 200 responses is first compared. If it does not exceed the max-age set by cache-control, it will hit the cache and load resources locally. If the browser does not support HTTP 1.1, it uses the expires header to determine whether it has expired.
- If the resource has expired, it means that the cached resource may no longer be valid, and a request with If-None-Match and If-Modified-Since section is sent to the server.
- After the server receives the request, it first validates whether the requested resource has been modified based on the Etag value. If the Etag value is the same, there is no modification; 304 is returned; if E-tag value is inconsistent which means there is a change, the new resource will be directly returned with a new Etag and status code 200.
- If the request received by the server does not have an Etag value, it will compare If-Modified-Since with the last modification time of the requested resource. If it has not changed, server will return 304; if they are inconsistent, server will return the response header with new last-modified, resource and return status code 200.
Communication Across Browser Tabs
ref: https://dev.to/weifengnusceg/browser-concepts-the-one-and-only-guide-you-need-3bni
- The postMessage() method allows you to send messages between different windows or tabs that share the same origin (same domain).
- One window can send a message to another window/tab by specifying the target window's reference and the data to be sent.
targetWindow.postMessage(“your message”, targetOrigin)
window.addEventListener("message", (event) => {
if (event.origin !== "http://localhost:8080") return
// Do something
}, false);
- LocalStorage and SessionStorage:
- Both localStorage and sessionStorage are storage mechanisms that allow you to store data on the client side.
- They can be used to share data between tabs from the same origin.
- However, they don't provide a built-in way to listen for changes across tabs, so you might need to implement a mechanism to detect changes manually.
window.localStorage.setItem("loggedIn", "true");
window.addEventListener('storage', (event) => {
if (event.storageArea != localStorage) return;
if (event.key === 'loggedIn') {
// Do something with event.newValue
}
});
- The Broadcast Channel API allows you to create named channels through which you can send messages to other tabs/windows that share the same origin.
- Tabs/windows listening to the same channel will receive messages.
const channel = new BroadcastChannel('app-data');
channel.postMessage(data);
channel.addEventListener ('message', (event) => {
console.log(event.data);
});
DOM
The Document Object Model (DOM) is a hierarchical tree-like representation of the structure and content of a web page.
It allows us to interact with and manipulate the HTML and XML content of a web page dynamically using JS.
How a HTML Page Renders
- Start to parse the HTML to build DOM
- Fetch the external resources (css, images are fetched in parallel while HTML parsing is happening, for JS depends on async, defer)
- Parse the CSS and build the CSSOM
- Execute the JavaScript
- Merge DOM and CSSOM to construct the Render Tree
- Calculate the layout and paint
- Layout (Reflow): Computes the exact geometry, size, and position of every visible element on the viewport.
- Paint (Repaint): Fills in pixels on the screen (colors, backgrounds, text styles, shadows).
When the browser encounters an external resource (like an image or video) while parsing HTML, it typically starts fetching that resource in parallel with continuing to parse and render the HTML. This is k/n as asynchronous loading.
- JS: Block Parsing unless defer or async
- CSS: Block Rendering
- Other resources: No blocking
In a web page, when multiple synchronous scripts are encountered, they are typically executed in the order in which they appear in the HTML
ref: https://starkie.dev/blog/how-a-browser-renders-a-web-page
Reflow vs Repaint
Reflow (Heavy)
- What it is: The browser calculates the geometry, size, and position of elements.
- Cost: Extremely Expensive. Modifying one element can cause a domino effect, forcing the browser to recalculate its parents, siblings, and children.
- What Triggers Reflow:
- Structural DOM changes (adding/removing elements, display: none).
- Changing dimensions (width, padding, margin, border-width, top, left).
- Window resizing or font family/size changes.
- Querying live layout metrics via JS (e.g., offsetWidth, getBoundingClientRect()).
Repaint (Lighter)
- What it is: The browser redraws pixels on the screen when an element's appearance changes without altering its layout or dimensions.
- Cost: Cheaper than reflow, but still costs CPU/GPU cycles.
- Triggers: Visual-only changes: color, background-color, visibility: hidden, border-style, border-color, box-shadow, outline.
The Golden Rule: A Reflow always forces a Repaint (because moving an element means it has to be redrawn in the new spot). A Repaint can happen completely on its own.
Bad Practice (Triggers Reflow/Repaint) | Good Practice (Optimized Approach) |
Changing individual styles one-by-one via JS (el.style.width = '10px'; el.style.margin = '5px';) | Change the className or use el.style.cssText to trigger exactly one combined reflow. |
Making live DOM updates inside a loops | Make changes offline using a DocumentFragment, then append it to the live DOM once. |
Using top / left for layout-based animations | Use CSS transform: translate() and opacity. These bypass both reflow and repaint, offloading execution directly to the GPU via the Compositing layer. |
Constantly querying layout metrics in loops (causing layout thrashing) | Cache the layout values in a local JavaScript variable before running the loop. |
CSS animation performance: why transform is preferred
Using transform promotes the element to its own isolated GPU layer. To animate it, the GPU just shifts or fades that pre-rendered layer on top of the page without disturbing the rest of the layout, guaranteeing a smooth 60+ FPS animation.
- The Core Reason: CPU vs. GPU
- Layout Properties (top, left, width): Run entirely on the CPU. The browser must recalculate the geometry (Reflow) and redraw the pixels (Repaint) on every single frame, causing stuttering (jank).
- Transform Properties (translate, scale, rotate): Run entirely on the GPU. The browser skips (reflow) layout and (repaint)paint entirely, handling the animation as a simple image manipulation.
- The Rendering Lifecycle Shortcut
- Slowest: top / left → Reflow → Repaint → Composite
- Slow: color / box-shadow → Repaint → Composite
- Fastest: transform / opacity → Composite Only
Async vs Defer

Critical Rendering Path (CRP)
The Critical Rendering Path is the sequence of steps the browser goes through, to convert the HTML, CSS, and JavaScript into pixels on the screen. Optimising the critical render path improves render performance.
The document object model (DOM) is created as the HTML is parsed. The HTML may request JavaScript, which may, in turn, alter the DOM. The HTML includes or makes requests for styles, which in turn builds the CSS object model (CSSOM). The browser engine combines the two to create the Render Tree. Layout determines the size and location of everything on the page. Once layout is determined, pixels are painted to the screen.
Optimising for CRP
Improve page load speed by prioritising which resources get loaded, controlling the order in which they are loaded, and reducing the file sizes of those resources. Performance tips include:
- Minimising the number of critical resources by deferring non-critical ones' download, marking them as async, or eliminating them altogether
- Optimising the size of critical resources of each request
- Prioritising the critical resources: optimising the order in which critical resources are loaded by prioritising the downloading of critical assets, thereby shortening the critical path length.
Critical Section of a page
The critical section of a web page refers to the part of the page that is immediately visible to the user upon the initial page load, without requiring additional scrolling or interaction.
Way to optimise critical section
- Displaying perception: loader, error, etc
- Optimise CRP
- Identify & Prioritise critical resources
- Minimise number of critical resources
- Decrease/optimise size of resources
- Optimise/compress resources
- Decrease network calls
- Load non critical/important resources later
- SSR
Code Obfuscation
Code obfuscation is a technique used to make source code more difficult to understand or reverse engineer. It involves transforming the code in a way that it remains functional but becomes harder for humans to read and comprehend. This is often done to protect intellectual property, prevent unauthorised modifications, or enhance security.
Some common obfuscation techniques: variable & function renaming, string encryption, code jumbling, dummy code insertion (inserting meaningless or redundant code), code splitting, minification, etc.
Tree Shaking
ref: https://benestudio.co/building-a-tree-shaking-friendly-javascript-package/
Tree shaking is a dead-code (unused code) elimination process. It relies on the static structure of ES modules (import and export). So CommonJS modules (require) can’t be shaken off directly because of their dynamic nature. So the bundlers we are using, like Webpack and Rollup, automatically tree shake our code for us.
To achieve this, we need to mark your library as side-effect-free in our library’s “package.json”. Add:
"sideEffects": false
To build a tree shaking friendly package we need to
- Use ES modules. Make sure the final output of your package is using ES modules, not CommonJS or AMD.
- Config side effects properly in package.json file to help bundlers. i.e. "sideEffects": false
window.onload vs DOMContentLoad
- window.onload
- It get triggered when the complete web page is loaded
- It is used when we need to execute JS code that relies on the complete availability of the web page
- DOMContentLoad
- It gets triggered when the browser is done loading & parsing the html document into DOM.
- It is often used when we want to execute JavaScript code as soon as the DOM hierarchy is ready
Hydration
Hydration in JavaScript, is the process of attaching event handlers to the DOM elements.
Hydration or Rehydration is a technique in which client-side JavaScript converts a static HTML web page (delivered either through static hosting or server-side rendering), into a dynamic web page by attaching event handlers to the HTML elements, sets up interactivity and make the page fully functional.
This helps improve the initial loading performance and user experience of web applications.
Selective Hydration
Selective/Partial hydration, is a concept in web development that refers to the practice of dynamically loading and rendering specific parts of a web page based on user interaction or other conditions.
The goal of selective hydration is to optimise the initial page load by sending only the essential HTML, CSS, and JavaScript needed to render the initial view. Then, as the user interacts with the page, additional content and functionality are loaded and rendered dynamically.
Selective hydration can be achieved using techniques like:
- Conditional Rendering
- Client-side Routing
- Lazy loading component
- Using Suspense in react
- Data-fetching on demand
Progressive Rendering
Progressive rendering is a method designed to enhance user experience and perceived speed by showing content as quickly as possible, even before every asset has finished downloading. Rather than delaying display until the full page is ready, it incrementally prioritizes and renders content.
Associated strategies for progressive rendering include:
- Critical Rendering Path Prioritization: Load and display the most vital above-the-fold content, such as primary text and key images, first so users see meaningful information immediately.
- Lazy Loading: Postpone the loading of scripts or below-the-fold images until they are actually required, ensuring essential content loads first.
- Content Streaming: Browser rendering can begin as soon as the first chunks of data arrive by sending content in streams rather than waiting for full processing.
- Asynchronous Resource Loading: By loading resources like scripts asynchronously, developers prevent slow assets from blocking the rendering of other elements.
- Use of Placeholder Elements: Display low-resolution images or placeholders initially, swapping them for full-quality content once available to show users that progress is being made.
- Streamlined Delivery: Shorten download times through asset optimization, including JavaScript/CSS minification and image compression
Web Vitals
Web Vitals are a set of metrics used to measure and improve speed, performance and interactivity of a web page.
- First Contentful Paint (FCP):
- First Contentful Paint (FCP) measures the time taken to render the first piece of content of a web page
- It indicates how quickly users perceive that the page is loading and becoming usable.
- To Reduce FCP:
- We need to decrease resources size (images, css, js, etc) or minimise render blocking resources (lazy loading for images, async loading for non-critical script)
- Prioritise Critical Resources: Use the <link rel="preload"> and <link rel="prefetch"> tags to instruct the browser to fetch critical resources early and similarly for critical JS.
- Minimise Network Requests & Server Response Time: fast CDN & caching can be used
- A good FCP score is typically under 1 second.
- Largest Contentful Paint (LCP):
- Largest Contentful Paint, measures the time taken to render the largest visible element (such as an image or text block) of a web page.
- LCP is an important metric for understanding when the main content of a page becomes visible to users.
- A good LCP score is typically under 2.5 seconds.
- Total Blocking Time (TBT):
- Total Blocking Time (TBT) measures the amount of time during which the main thread of a web page is blocked and unable to respond to user input.
- TBT is concerned with how quickly a page can start responding to user input (first interaction)
- To reduce TBT, we can optimise JS execution and minimise the impact of long tasks using web-worker
- A good TBT score is typically under 300 milliseconds.
- Time to Interactive (TTI):
- Time to Interactive (TTI) measures the time it takes for a web page to become fully interactive for users.
- TTI is concerned with when the page becomes fully interactive.
- To improve TTI, we need to optimise critical resources (load critical js, responsible for interactivity, first).
- A good TTI score is typically under 5 seconds.
- Cumulative Layout Shift (CLS):
- CLS, measures the visual stability of a page by tracking unexpected layout shifts in the loading process.
- CLS helps ensure that page content doesn't unexpectedly shift while users are interacting with it.
- To reduce CLS:
- Give width, height attribute to image/video elements
- Ensure new content/element does not shift any existing content/element
- Allocate space for content which will be loaded asynchronously.
- A good CLS score is typically under 0.1.
- Speed Index (SI):
- Time taken to load entire web-page
- It measures how quickly the content of a web page is visually displayed during the entire loading process.
- It takes into account the progression of content loading over time and provides a score based on the visual completeness of the page at different points during loading.
- A good Speed Index score is typically under 3.4 seconds.
Core Web Vitals
3 core web vitals are:
- Largest Contentful Paint (LCP) — Loading
- What it is: The time it takes for the largest visible element (e.g., a hero banner image or main heading text) to fully render on the screen.
- Target: ≤ 2.5 seconds
- Quick Fix: Compress images into modern formats (WebP/AVIF), set up an aggressive CDN to serve assets closer to users, and eliminate render-blocking scripts.
- Interaction to Next Paint (INP) — Interactivity
- What it is: The latency of all user interactions (clicks, key presses, taps) across the entire page lifecycle, measuring how long it takes for the browser to paint the next visual frame after an action.
- Target: ≤ 200 milliseconds
- Quick Fix: Break up heavy, monolithic JavaScript functions ("long tasks") using code splitting, and optimize framework rendering loops.
- Cumulative Layout Shift (CLS) — Visual Stability
- What it is: A score quantifying how much layout elements unexpectedly shift position while loading, which can cause users to accidentally click the wrong button.
- Target: ≤ 0.1 (structural score, not time)
- Quick Fix: Always declare explicit width and height dimensions on image tags and video containers, and reserve static placeholder heights for late-loading dynamic advertisements.
How CLS is calculated
Cumulative Layout Shift (CLS) is a structural score calculated by multiplying how much screen area unexpectedly moved (Impact Fraction) by how far it traveled (Distance Fraction).
CLS Score = Impact Fraction × Distance Fraction
- Impact Fraction: The total percentage of the screen affected by the shift. It combines the area where the element was originally and the area where it moved to. (e.g., if an element occupying 40% of the screen shifts down by 15%, it affects 55% of the screen, or 0.55).
- Distance Fraction: The greatest distance the unstable element traveled, divided by the screen's largest dimension (width or height). (e.g., if a button moves down by 150px on a 1000px tall smartphone screen, it moved 15%, or 0.15).
Final Score for this shift = 0.55 × 0.15 = 0.0825
Waterfall
Waterfall refers to a graphical representation of the various resources (like HTML, CSS, JavaScript, images, etc.) that a web page loads, and the order in which they are loaded. It's a tool used for visualising the loading process of a web page.
The waterfall chart displays a timeline on the x-axis and the different resources on the y-axis. Each resource is represented as a horizontal bar, with the position on the y-axis indicating when it starts to load and the length of the bar representing how long it takes to load. The resources are loaded in the order they are requested by the browser.
When debugging a memory leak in a SPA, what are the most common JavaScript-level causes you look for
- Forgotten Global Event Listeners: Attaching listeners to global targets (e.g., window.addEventListener('resize', ...) or a global event bus) inside a component without removing them on unmount. The global object indefinitely retains the callback, which closures over the component instance.
- Uncleared Timers (setInterval / setTimeout): Active intervals or unexecuted timeouts running in the background. Their callbacks keep all referenced scope variables and component states alive.
- Detached DOM Nodes: Removing an element from the page's visible DOM tree while a JavaScript variable (like a cache, map, or array) still retains a direct reference to that node.
- Unbounded Closures in Long-Lived Services: Global state stores, singletons, or event handlers capturing memory-heavy local variables in inner function scopes that are never cleared.
When you implement route-based code splitting, what metrics would you track to ensure you didn't just shift the cost into a slower navigation (e.g., after a user clicks)?
[ User Clicks Link ] ───> [ Download Chunk ] ───> [ Parse & Execute JS ] ───> [ Screen Paints New Route ]
└──────────────────────── Route Transition Latency ────────────────────────┘
▼
Monitored via INP & LoAF
- Interaction to Next Paint (INP): Measures the time between a user clicking a navigation link and the browser actually painting the next frame. If compiling the new JS chunk chokes the main thread, your INP will spike above the healthy 200ms threshold.
- Route Transition Latency (Custom Metric): Tracks the raw duration of a navigation event. You can log this manually using the User Timing API in your router's navigation hooks:
- router.onTransitionStart(() => performance.mark('nav-start'));
- router.onTransitionEnd(() => {
- performance.mark('nav-end');
- performance.measure('RouteTransition', 'nav-start', 'nav-end');
- });
- Long Animation Frames (LoAF): It identifies exactly which split script file (e.g., chunk.Dashboard.js) caused the main thread to stutter during a route change.
- Network Fetch Latency & Cache Hit Rate: Monitors whether navigation lag is caused by a slow network download or JS execution. If your CDN cache hit rate for chunk files is low, users are constantly waiting on raw data transfers mid-session.
Does font affect rendering and layout phases? If yes, how to minimise the effect?
- Rendering Phase: The time it takes to download a font can impact the rendering phase. If a font is large or takes a long time to download, it may delay the rendering of the associated text content.
- Layout Phase: The browser needs to measure the size of text elements for layout calculations. The size of the text is influenced by the font metrics, including the font size, line height, and character spacing.
- For minimising its impact
- Use optimised/compressed font file type like WOFF2
- Lazy loading fonts: For non-critical fonts, load them asynchronously after the critical section is loaded
- Use fallback fonts
- Preloading: Preload fonts using the <link rel="preload"> tag to initiate the font download earlier in the page loading process.
System Design
Things to consider in Frontend System Design interviews
Tutorial: https://www.youtube.com/watch?v=44mOnnt5pic&list=WL&index=1&t=2s
RADIO - Requirements, Architecture, Data Models, Implementation, Optimisation
- Requirements - gather all requirements
- Functional requirements
- Module Wise
- Authentication & User Management
- Product Listing
- Pricing & Subscription
- Payment Gateway
- Cart
- Account Management
- Feature Wise
- Search
- Filter
- Details
- Product Review
- Add/remove items to cart
- Video Streaming - Dash.js, Shaka Player
- Non-Functional Requirements
- Devices - Mobile/Desktop/Tablets
- Responsive/Adaptive
- Accessibility - support for disabled people, international people in local language
- Assets Optimisation
- Performance - Web Vitals, CSR/SSR
- Security
- Caching
- Offline Support
- Logging & monitoring
- Testing
- Scope - Prioritisation - what least to build, Minimum Viable Product (MVP)
- Tech Choices
- Library/Framework
- State Management
- Caching & Storage
- Offline Support / PWA
- Design System / theming / Design Tokens
- Components: Material UI, Prime React, Headless UI
- Folder Structure
- Packages
- Build Tools - webpack, rollup, turbopack
- Component Architecture
- Routes / Component Diagram
- Component Hierarchy / Component API
- Dependency Tree / Data Sharing
- Data Models, API, Component API/State/Props
- Data Models / State Management
- Backend API
- Component API
- state/props
- event handling
- customisation - theming
- Reusability
- Data source
- Optimisation & Performance
- Accessibility
- Availability
- Security
Common HLD Components
- Architectural Patterns
- Monolith Frontend
- Micro Frontend
- The idea behind Micro Frontends is to think about a website or web app as a composition of features which are owned by independent teams. Each team has a distinct area of business or mission it cares about and specialises in.
- iframe: window.postMessage(), window.addEventListener(‘message’, function(){ })
- shadowDOM - web components (html+css+js encapsulated which can be used anywhere, any js library)
- npm package (can be used in particularly that js library)
- Module Federation: It is an architectural pattern and a powerful feature introduced in Webpack 5 that allows a JavaScript application to dynamically load code from another separate application at runtime. Before Module Federation, sharing code between independent frontend teams required bundling shared code into npm packages, which meant forcing a full application rebuild and redeployment every time a shared component was updated. With Module Federation, applications can share code instantly without redeploying the host container.
- Communication Protocols
- HTTP Request - client makes the request and server sends the response
- HTTP Long Polling - client keep asking to server until all demands are met
- Web-Socket - server sends response whenever any update/new data is available
- Server Sent Events(SSE) - client need to make request only once, server keep sending data
- Availability
- Offline support - service worker (PWA - Progressive Web App)
- Responsive - Device support
- internationalisation
- Accessibility
- Add keyboard accessibility - use tabIndex html attribute, programmatically using JS
- Html5 semantics: using correct html element to convey the meaning of the element, header, nav, article, section, figure, figcaption, main, footer, etc
- aria html attributes
- alt html attribute for image
- Adding form field labels
- Consistency: should have same behaviour / look on all browsers
- js polyfills
- design system (material ui, atlassian design system, etc) / theming
- Credibility & Trust
- SEO (Search Engine Optimisation)
- On Page
- title, description, meta, content
- Semantic HTML
- Off Page
- backlinks
- ads
- Logging & Monitoring
- Error Logging: sentry, datadog, posthog
- User Monitoring/Tracking: RUM(Real User Monitoring): user tracking (types of users, how much time they spend), Posthog
- Performance Monitoring: Application Performance Monitoring (APM): datadog
- Application Monitoring: capacity/traffic monitoring (Google Analytics, Posthog)
- Storage & Database
- Caching - HTTP Caching, In Memory Caching, API Caching
- State Management - Redux, React Context, Jotai
- Local Storage, Session Storage, IndexedDB, Cookies
- Performance & Optimisation
- Network Performance
- Caching resources
- Compress resources (gzip, brotli)
- Debouncing/Throttling
- Widget/Component Result Cache (Memoization)
- Assets Performance
- Assets resources caching
- webP Images (smaller in size, can be compressed without losing quality, support animation like gifs)
- compress/resize media
- image smaller in size, use src-set: https://html.com/attributes/img-srcset/#ixzz7cyKsZPJ5
- The src-set attribute is an HTML attribute used in the <img> element to provide multiple sources (and optionally their sizes) for an image. This helps browsers to choose the most appropriate version of an image based on the device's screen size and resolution. It's particularly useful for responsive web design to optimise for various devices and network conditions.
- <img
src="image-800.jpg"
srcset="
image-400.jpg 400w,
image-800.jpg 800w,
image-1200.jpg 1200w,
image-1600.jpg 1600w
"
sizes="
(max-width: 400px) 400px,
(max-width: 800px) 800px,
(max-width: 1200px) 1200px, 1600px
"
alt="Description of the image"
/>
- Lazy Loading assets
- JS Performance
- Write Optimal Code (best time complexity)
- Minify code
- remove unused/repeated code (tree-shaking)
- Loading JS Asynchronously (defer)
- Event Delegation
- Use Web Worker for high weight tasks
- Bundle Splitting
- Memoization
- Rendering Performance
- Prevent un-necessary re-renders: React.memo, useCallback, useMemo
- Delivery Option: Pagination, Infinite Scroll, Virtualisation
- Debounce/Throttle: Optimizing high-frequency events like search inputs (debouncing) or window resizing/scrolling (throttling) to reduce CPU load.
- Load Non-critical stuff later (analytics script, non-critical styles) - by using defer for non-critical scripts or loading scripts asynchronously using JS, by using media=”print” for non-critical css or loading css asynchronously using JS
<link rel="stylesheet" href="non-critical.css" media="print">
document.addEventListener('DOMContentLoaded', function() {
var link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'non-critical.css';
document.head.appendChild(link);
}); - Perception - Loader, Skeleton, Placeholder
- SSR
- Prefer CSS animation
- Web Vitals - FCP, LCP, TBT, TTI, CLS, INP
- Keep a connection state in the app, so that you will not make network request knowing that it will fail
- Security
- .env variables
- Authentication & Authorisation
- Content Security Policy (CSP)
- CORS
- CSRF
- XSS (Cross-Site Scripting)
- Testing
- Unit Testing (Individual Testing), e.g Jest, React Testing Library, Chai
- Integration Testing
- End-to-end Testing (E2E Testing), e.g. Selenium, Cypress, Playwright

https://www.youtube.com/watch?v=9JDlZxR8gVw
How can we make a web app scalable
- Optimise Frontend Performance:
- Network Performance
- JS Performance
- Assets Performance
- Rendering Performance
- Caching:
- Browser Caching
- CDN Caching
- Service Worker Caching
- Load Balancing:
- Distribute incoming traffic across multiple servers using load balancers.
- Load balancing helps evenly distribute user requests and prevents any single server from becoming a bottleneck.
- Horizontal Scaling:
- Add more servers or resources to your infrastructure to handle increased load.
- Use containerization and orchestration tools like Docker and Kubernetes for efficient scaling.
- Content Delivery Network (CDN):
- Employ a CDN to cache and serve static content from servers located strategically around the world.
- CDNs reduce latency and offload traffic from your main servers.
- Asynchronous Processing:
- Move time-consuming tasks to background jobs or queues.
- Use message queues or job schedulers to handle asynchronous tasks like kafka, SQS, etc
- Optimise Code and Assets:
- Minify and compress CSS, JavaScript, and HTML files.
- Use efficient coding practices to reduce the overall size of your codebase.
- Employ techniques like lazy loading for non-essential resources.
- Responsive Design:
- Ensure the website is responsive and works well on various devices and screen sizes.
- Responsive design improves user experience and accommodates a diverse user base.
- Monitoring and Analytics:
- Implement monitoring tools to identify performance bottlenecks and issues, like datadog APM, Posthog
- Security Best Practices:
- Implement security measures to protect against common vulnerabilities like CSP, CSRF, CORS
- Micro Service Architecture:
- Consider a microservices architecture to break down the application into smaller, independent services.
- Each service can be developed, deployed, and scaled independently.
- Content and Database Caching:
- Cache frequently requested content and database queries to reduce the load on your servers.
- Use caching mechanisms to serve pre-generated content when possible.
What are the security measures which should be considered during development?
- Using .env variable for secret keys and credentials
- Input validation
- CSP Policy
- CORS
- CSRF: csrf token, same-site
- Proper error handling
- Authentication - auth token
- Access Control
- Storing encrypted data in browser storage
- Encrypted password
- Encrypted url
- Code obfuscation - minification, uglification
- Logging & Monitoring
How can we ensure components support multi-themeing (dark mode, client-specific branding) and are extensible
- Design Token Layer: Never hardcode style values in the components. Instead, map identical semantic design tokens to completely different raw values using HTML data attributes.
- Compound Components: Instead of configuring components using a fragile array of boolean properties (hasIcon={true} title="..."), use Compound Components. Break a massive layout down into modular sub-components that share state implicitly via React Context.
What is WCAG Compliance
WCAG (Web Content Accessibility Guidelines) is the gold-standard global benchmark for digital accessibility. It outlines how to make websites, apps, and digital documents usable for everyone, including individuals with visual, auditory, motor, or cognitive disabilities. It is divided into three conformance levels: A (Minimum), AA (The standard legal requirement for most mid-to-large businesses), and AAA (The highest, most stringent standard).
All WCAG rules are built on four core principles, known by the acronym P.O.U.R.:
- P – Perceivable: Users must be able to see or hear the information. (e.g., Providing alt text for images so blind users know what they display).
- O – Operable: Users must be able to navigate the interface. (e.g., Making the entire UI functional using only a keyboard without a mouse).
- U – Understandable: The content and UI must be clear. (e.g., Error messages must tell the user exactly what went wrong and how to fix it).
- R – Robust: The website must work reliably across a wide range of browsers, devices, and assistive technologies.
How to make element keyboard accessible
To make a User Interface (UI) keyboard accessible, you must ensure a user can navigate and operate every feature using only a keyboard (Tab, Shift + Tab, Enter, Spacebar, and Arrow Keys).
- The Core Rules of Keyboard Accessibility
- Maintain a Logical Tab Order
- Focus must move predictably: top-to-bottom, left-to-right.
- Use native semantic HTML (<button>, <a>, <input>) because they have built-in keyboard support.
- Never use positive indexes (tabindex="1"). It forces an artificial order that breaks layouts. Use tabindex="0" to make a custom element focusable naturally, or tabindex="-1" to make it focusable only via JavaScript.
- Never Hide the Focus Indicator
- Avoid global resets like outline: none; which leave keyboard users blind.
- Use the CSS modern pseudo-class :focus-visible to style a high-contrast focus ring that appears only when a user is navigating via keyboard.
- button:focus-visible {
- outline: 3px solid #3b82f6;
- outline-offset: 2px;
- }
- The Rule of Two (Key Events)
- Any custom element that triggers an action on a mouse click must also trigger that exact action using Enter and the Spacebar via a keydown listener.
- Advanced Component Layouts
- Modal Focus Traps
- When an overlay dialog box opens, you must implement a Focus Trap
- Pressing Tab must cycle focus only within the active elements inside the modal.
- The cursor must never escape "underneath" into the background page.
- Pressing Escape must instantly close the modal and return focus to the button that originally opened it.
ref: check react/srcKeyboardAccessibility of coreJs Repo
What are the A11y issues? How do you handle it?
A11y is short form of accessibility
Some common accessibility issues are:
- Lack of keyboard navigation/shortcuts
- Missing alt attribute for image
- Colour contrast issues
- Missing label of form elements
- Unstructured content
- Not following semantic html
To handle it we can do:
- Use semantic html
- Keyboard accessibility
- alt attribute for images
- Use accessibility testing tools like pagespeed.web.dev, lighthouse
Diff b/w Virtualisation and Windowing
Windowing calculates which items are inside the visible viewport and as we scroll, items that leave the viewport are completely destroyed (unmounted from the DOM), and items entering the viewport are created from scratch (mounted into the DOM).
But in virtualization, it creates a fixed number of DOM rows (just enough to fill the viewport plus a tiny buffer) and never destroys them. As we scroll, instead of deleting an off-screen row, a row that leaves the top of the screen is physically repositioned to the bottom, and its contents are instantly swapped out with the next dataset item.
Design Patterns
ref: https://www.youtube.com/watch?v=tv-_1er1mWI
- Creational Design Patterns: how objects are created
- Singleton: It guarantees a single instance of an object. Imagine you have a magical treasure chest. The Singleton pattern ensures that you always use the same chest throughout your journey, no matter how many times you need it.
- Factory: Defines an interface for creating an object but lets subclasses alter the type of objects that will be created. Think of a car factory. The Factory pattern is like an assembly line that produces cars. Instead of building a car from scratch each time, you have a factory that churns out cars with consistent features.
- Builder: Separates the construction of a complex object from its representation. Let's imagine you're building a fancy house. The Builder Pattern is like having a skilled team of builders who follow a blueprint to construct your dream home, step by step. Each builder has a specific role, and they work together to create a house that meets your requirements.
- Structural Design Patterns: how objects relate to each other
- Decorator: Attaches additional responsibilities to an object dynamically. Consider a plain cake. The Decorator pattern is like adding layers of frosting, fruits, or chocolate to make the cake fancy. You enhance an object's functionality by wrapping it with additional features.
- Module: Organises code into separate modules, similar to having different compartments in a toolbox for organising tools.
- Adapter: Allows objects with incompatible interfaces to work together. Imagine you have a collection of old, vintage vinyl records that you want to play. However, your fancy new sound system only supports the latest digital music formats. This is where the adapter comes in. The adapter is like a magical device that you plug into your sound system. It knows how to translate the signals from your old vinyl records into a format that the new sound system can understand.
- Facade: Provides a simplified interface to a complex system of classes. Imagine you have a complex gadget, like a high-tech coffee maker with many buttons, switches, and settings. Making a cup of coffee might involve a series of steps and configurations. Now, not everyone is a barista or interested in all the technical details of the coffee maker. Some just want a quick and easy way to get their coffee. The facade is like a simplified control panel on the coffee maker that provides easy buttons like "Start," "Brew," or "Cappuccino." It hides the complexity of the inner workings of the coffee maker.
- Behavioural Design Patterns: how objects communicate with each other
- Observer: Defines a one-to-many dependency between objects, so that when one object changes state, all its dependents are notified and updated automatically. Picture a weather station. The Observer pattern is like having many weather apps on your phone. Whenever the weather changes, all the apps get updated simultaneously. It's a way for multiple parts of your program to stay in sync.
- Mediator: Reduces direct connections between objects by introducing a central mediator object.
- Command: Turns a request into a stand-alone object, encapsulating all the information about the request. Encapsulates a request as an object, allowing you to parameterize clients with queues, requests, and operations, like giving commands to a robot that executes them later.
- MVC (Model-View-Controller): Separates an application into three interconnected components: Model (data), View (user interface), and Controller (handles user input). Consider a restaurant. The MVC pattern separates the kitchen (Model), where the food is prepared, the dining area (View), where customers see and enjoy the food, and the waiter (Controller), who takes orders and communicates between the kitchen and dining area.
- MVVM (Model-View-ViewModel): MVVM is like building a house where you have a blueprint (Model), the actual house (View), and an interior decorator (ViewModel) making sure everything looks good inside based on the plan
- Flux: An architecture for managing the flow of data in a web application, often used with React.
Difference between Controller (of MVC) and ViewModel (of MVVM)
While both the Controller and ViewModel play roles in connecting the Model and View, the Controller often handles the overall application flow and user input, while the ViewModel is more focused on organising and presenting data in a way that is suitable for the user interface.
Agile Methodology

- Customer-Centric Approach: Agile places a strong emphasis on understanding and meeting the needs of the customer. Continuous customer feedback is actively sought and incorporated into the development process.
- Iterative Development: Instead of trying to deliver the entire product at once, Agile breaks the project into smaller increments called iterations. Each iteration typically lasts 2-4 weeks and results in a potentially shippable product increment.
- Cross-Functional Teams: Agile teams are typically small, cross-functional groups that include developers, testers, designers, and other necessary roles.
- Empowered Teams: Agile teams are self-organising and have the authority to make decisions about how to accomplish their work. They are empowered to choose the best approach to meet their goals.
- Adaptability: Agile teams are responsive to changing requirements and priorities. They can adapt quickly to new information or feedback from stakeholders.
- Continuous Delivery and Integration: Agile encourages continuous integration of code into a shared repository and frequent delivery of working software.
- Sprint Planning: At the beginning of each iteration (commonly referred to as a "sprint"), the team collaborates to define the scope of work for that sprint. They select user stories or tasks from the backlog to work on.
- Daily Stand-Ups: The team holds daily stand-up meetings to discuss progress, challenges, and plans. This helps ensure everyone is aware of the project's status and any potential roadblocks.
- Retrospectives: At the end of each sprint, the team conducts a retrospective meeting to reflect on what went well, what could be improved, and how to implement those improvements in the next sprint.
- Backlog Prioritisation: The product backlog is a prioritised list of features, user stories, or tasks that need to be addressed. The highest priority items are worked on first.
- Working Software as the Primary Measure of Progress: The ultimate goal of each sprint is to produce working, potentially shippable software. This ensures that progress is tangible and provides value to the customer.
Google Docs
googleDocs folder of machineCoding repo
https://www.youtube.com/watch?v=9JKBlkwg0yM&list=PLg-m8NS3FSbf37gYvFj8maUk7XQeZ4iFU
https://www.youtube.com/watch?v=GYVLp0ekHdM&list=PLg-m8NS3FSbf37gYvFj8maUk7XQeZ4iFU&index=2
https://www.youtube.com/watch?v=uOKrTc3Q0D0&list=PLg-m8NS3FSbf37gYvFj8maUk7XQeZ4iFU&index=4
Google Sheets
googleSheets folder of machineCoding repo
https://www.youtube.com/watch?v=fmIiDLbLc_s&list=PLg-m8NS3FSbf37gYvFj8maUk7XQeZ4iFU&index=3
Google Calendar
https://www.youtube.com/watch?v=leo1FZ6vu1I
Networking & Security
What happens when you type a URL in the web browser and hit enter?
- Parse URL
- Look for IP address of the domain in DNS
- Establish TCP connection with the server
- Make HTTP request
- Severs send the HTTP response
- Browser receives the response, parse the response and display the content
- Parsing the URL: Browser parses the URL to extract various components such as the protocol (HTTP, HTTPS), domain name (e.g., www.example.com), path, query parameters, and other fragments.
- The browser looks for the IP address of the domain name (locate the server hosting that website) in the DNS (Domain Name System)
- DNS is a list of URLs and their corresponding IP address (like a telephone book). We can access the website directly by typing the IP address but imagine remembering a group of numbers to visit any site.
- The DNS checks at the following places for the IP address.
- Check Browser Cache: The browser maintains a cache of the DNS records for some fixed amount of time.
- Check OS Cache: If the browser doesn't contain the cache then it requests to the Operating System as the OS also maintains a cache of the DNS records.
- Router Cache: If your computer doesn't have the cache, then it searches in the router cache of the DNS records.
- ISP (Internet Service Provider) Cache: If the IP address is not found till this then it is searched at the cache that ISP maintains of the DNS records. If not found here also, then ISP’s DNS recursive search is done. In "DNS recursive search", a DNS server initiates a DNS query that communicates with several other DNS servers to find the IP address.
- The Browser initiates a TCP connection with the server.
- When the browser receives the IP address, it will build a connection between the browser and the server using the internet protocol. The most common protocol used is TCP protocol. The connection is established using a three-way handshake.
- The browser sends an HTTP request to the server.
- Once the TCP connection is established with the server, actual request i.e GET | http://www.google.com is sent.
- The server handles the incoming request and sends an HTTP response.
- The server processes the request and prepares the response in client requested format i.e. HTML, JSON, XML, etc & adds other details like response status, etc. Once the server is ready with the response, it will be sent back to the client over the established TCP connection.
- Browser’s Receiving and Parsing the Response and then Rendering and Displaying Content:
- Once the client receives the response from the server, the browser checks the response status code i.e 2xx(success response), 3xx(redirecting request),4xx(client error) 5xx(server error), etc.
- The browser will try to translate the response based on the received response type. In our case it is HTML, so it will display the html page. If the response is cacheable is will store the response in the browser cache.
Crawls, indexing and its ways
Crawlers, also known as spiders or bots, are automated programs used by search engines to discover and index web pages on the internet. The process of crawling involves systematically visiting web pages, following links, and extracting information for further processing.
Crawlers follow these steps
- Discovery: Crawlers start by visiting a list of known URLs, often provided by a sitemap or seed list
- Fetching: After getting a URL, the crawler sends a request to the server hosting the page to retrieve its content.
- Parsing: Once the content is retrieved, the crawler analyses the HTML, CSS, and JavaScript to extract relevant information like text content, links, metadata, and more.
- Indexing: The extracted information is then processed and stored in a searchable index. This index is like a database that allows search engines to quickly retrieve relevant results when a user performs a search.
How can we do 200 network request (download 200 images) in a web page
We can batch them in chunks (suppose the size of the chunk/no of items in a chunk is 10) and process chunks one-by-one, and in a chunk all items will run concurrently / parallelly. So at any moment, at most the size of the chunk network requests are going on.
- If we have domain sharding (for http 1.1, new http has no request limitation) implemented then we can decide any size for a chunk.
- No domain sharding: then we can have maximum of 6 items in a chunk, so the total no of chunks will be 200/6 = 34
WebSocket Protocol
WebSocket is a network protocol that enables continuous, two-way (bi-directional), real-time communication between a client (like a web browser) and a server over a single, long-lived connection.
WebSocket operates on its own dedicated protocol, called the WebSocket Protocol (WS).
Just like HTTP, the WebSocket protocol has an unencrypted and an encrypted version, running on the exact same ports:
- ws:// (WebSocket): Runs unencrypted over standard HTTP Port 80
- wss:// (WebSocket Secure): Runs encrypted over Transport Layer Security (TLS/SSL) on HTTPS Port 443
Webhook
A webhook is a mechanism that allows one server to send real-time data to another server as soon as a specific event occurs. Webhooks are commonly used in web development and integration scenarios to enable communication between different applications or services. Webhooks operate asynchronously, allowing near-real-time communication between systems. The sender does not wait for a response from the receiver, making it a lightweight and efficient way to notify other systems about events.
Difference between Server Sent Events(SSE) and Webhook
- Webhook: Server -> Server: Notifies you about specific events and sends detailed information when those events occur.
- SSE: Server -> Client: Provides a continuous stream of updates from the server in real-time, keeping the application constantly updated without manual requests.
In summary, Webhooks are event-driven notifications triggered by specific events, whereas SSE is a continuous stream of updates sent from the server to the client without the need for the client to repeatedly request information.
Compare Different Communication Protocols
Feature / Metric | Short Polling | Long Polling | WebSockets (ws://) | Server-Sent Events (SSE) |
Communication Direction | One-way (Client $\rightarrow$Server) | One-way (Client $\rightarrow$Server) | Bi-directional (Full-Duplex) | Uni-directional(Server $\rightarrow$Client) |
Protocol Foundation | Standard HTTP | Standard HTTP | HTTP Upgrade $\rightarrow$ Custom TCP Sockets | Standard HTTP (over HTTP/2 or 3) |
Connection Lifetime | Ephemeral (Closes instantly) | Near-persistent (Closes on data delivery) | Persistent (Stays open infinitely) | Persistent (Stays open infinitely) |
Data Format Support | Any (JSON, XML, Binary) | Any (JSON, XML, Binary) | Any (Text or Raw Binary Data) | Text-only (UTF-8 / JSON streams) |
Header Overhead | Extremely High(Sent with every poll interval) | High (Sent with every data cycle) | Minimal (Sent only during initial handshake) | Low (Sent only during connection setup) |
Real-Time Latency | High (Bounded by poll interval delay) | Low (Delivered immediately upon data arrival) | Near-Zero (Sub-millisecond) | Near-Zero (Sub-millisecond) |
Automatic Reconnection | N/A (Handled by new interval loop) | N/A (Handled by subsequent request loop) | No (Must be manually coded in JS) | Yes (Built-in browser feature) |
Firewall / Proxy Friendly | Yes (Standard Web Traffic) | Yes (Standard Web Traffic) | Can be blocked by strict corporate networks | Yes (Runs over standard HTTP paths) |
Scalability Bottleneck | High CPU stress from handling frequent requests | Connection pool exhaustion on thread-based servers | Memory limits (Maintaining thousands of open TCP sockets) | Max 6 connections per domain if restricted to HTTP/1.1 |
Ideal Use Case | Checking status of rare background jobs | Basic notification fallbacks for legacy systems | Multi-user collaboration platforms and real-time games | Generative AI token streaming (ChatGPT) and live tickers |
What is pub/sub (publish/subscribe)
Imagine you have a bunch of friends who love getting updates about your life. Instead of calling each friend individually every time you have something new to share, you decide to put a notice on a community board. Now, whenever you have something interesting to tell, you just put a note on that board.
Your friends know to check the board regularly. When they see a new note, they read it and get the latest news about you. They don't have to wait for your call or be available at the same time you're sharing news.
In this analogy:
- You are the publisher.
- The community board is the message broker (the 'pub-sub' system).
- Your friends are the subscribers.
Pub/Sub is a messaging system used to facilitate communication between different services of a software.
It is an asynchronous and scalable messaging system that decouples services producing messages from services consuming those messages.
Instead of communicating directly, publishers send their messages to a central broker (pub-sub system). This broker is responsible for managing the subscriptions and distributing the messages to the appropriate subscribers.
What is CI/CD

Code Commit -> build -> test -> deploy
CI/CD automates the process of integrating and deploying code, leading to faster development cycles.
CI: Continuous Integration
- Integration of Code Changes: CI involves automatically integrating code changes from multiple contributors into a shared repository multiple times a day.
- Automated Testing: After code is integrated, automated tests are run to ensure that the new code doesn't break existing functionality.
- Version Control: It heavily relies on version control systems like Git to manage code changes.
CD: Continuous Deployment/Delivery
- Continuous Deployment: This is the practice of automatically deploying every code change that passes automated tests to a production environment. This means that every time a new piece of code is added, it's automatically deployed for users.
- Continuous Delivery: This is similar to continuous deployment, but with an additional step. In continuous delivery, the code is automatically pushed to a staging or pre-production environment. It's then up to the team to decide when and if to deploy it to production.
E.g. Jenkins, github actions, gitlab ci/cd
What is load balancer
A load balancer is a networking device or software application used in distributing incoming network traffic across multiple servers or computing resources in a way that optimises resource utilisation, ensures high availability, and enhances the performance of applications or websites.
- Client Sends a Request: A client (such as a web browser) sends a request to access a website or application. This request is directed towards the load balancer.
- Load Balancer Receives the Request: The load balancer is the first point of contact for the client. It intercepts the request before it reaches the backend servers.
- Load Balancer Evaluates Servers: The load balancer maintains a pool of backend servers (also known as nodes or instances) that are capable of handling the incoming requests. These servers could be physical machines, virtual machines, or containers.
- Load Balancer Chooses a Server: The load balancer uses a variety of algorithms to determine which server should handle the request. Common algorithms include:
- Round Robin: Requests are distributed equally among servers in a cyclic manner.
- Least Connections: The request is sent to the server with the fewest active connections.
- IP Hash: The client's IP address is used to determine which server should handle the request. This ensures that a particular client's requests are consistently sent to the same server.
- Load Balancer Forwards the Request to the selected server
- Server Processes the Request
- Server Sends Response to Load Balancer
- Load Balancer Sends Response to Client: The load balancer, acting as a reverse proxy, receives the response from the server. It then forwards the response to the client that initially made the request.
What is domain sharding
It is a legacy technique used during HTTP/1.1, current http versions HTTP/2, HTTP/3 does not have these limitations. There was a limit of the maximum number of concurrent requests (HTTP/1.1) a browser can make to a single origin.
- Chrome, Firefox, Edge: 6
- Safari: 10
So to overcome this, domain sharding come into picture
Domain sharding is a technique used to overcome the limitations on the number of concurrent connections a web browser can make to a single domain. It involves spreading resources (like images, scripts, stylesheets, etc) across multiple subdomains of the main domain.
What is CDN
CDN stands for Content Delivery Network. It is a distributed network of servers present at multiple locations which is responsible for serving web resources (like images, videos, scripts, stylesheets, etc.) to clients in a faster and more reliable manner. e.g. Akamai Technologies, cloudflare, Amazon CloudFront, Google Cloud CDN, etc.
Here's how a CDN works:
- Content Replication: The content of a website (e.g., images, videos, scripts) is replicated and stored on multiple servers across the CDN's network.
- Edge Servers: These replicated servers are known as edge servers. They are strategically placed at different geographic locations, closer to the end-users.
- User Requests: When a user requests content from a website, the CDN automatically redirects the request to the nearest edge server that has a copy of the content.
- Faster Delivery: Since the content is served from a server closer to the user, it reduces the distance the data has to travel, resulting in faster load times.
- Load Balancing and Failover: CDNs use load balancing algorithms to distribute the requests evenly among edge servers. If one server fails, the requests are automatically redirected to another available server.
- Scalable Video Streaming: CDNs equipped with video delivery capabilities can efficiently stream video content to users, adjusting quality based on their network conditions.
- Support for Secure Connections (HTTPS): CDNs can help manage SSL/TLS certificates and provide secure connections for websites, making it easier to implement and maintain HTTPS.
- Content Optimization: CDNs often offer features like image optimization, minification of scripts and stylesheets, and GZIP compression, which can further improve website performance.
- Content Caching and Persistence: CDNs cache static content like images, scripts, and stylesheets. This reduces the need for repeated requests to the origin server, further improving load times.
- Service Continuity: Even if the origin server experiences downtime, a CDN can continue to serve cached content, providing a fallback option for users.
- Security: CDNs often provide security features like DDoS protection, XSS, SQL injection, CSP, SSL encryption, and web application firewalls.
- Domain Sharding: CDNs often have the capability to distribute content across multiple subdomains. The CDN provider sets up the necessary subdomains (e.g. cdn1.example.com, cdn2.example.com) on their infra. When content is uploaded to the CDN, it can automatically distribute the files across its network of subdomains. This is sometimes done automatically by the CDN, but in some cases, developers may need to configure this behaviour.
Normalisation
Normalization is the process of organizing a database to reduce data redundancy (duplication) and prevent data anomalies (bugs when inserting, updating, or deleting records).
The goal is to divide large, messy tables into smaller ones and connect them using relationships, ensuring every piece of data is stored in exactly one place.
Middleware
Middleware is a software component that acts as a bridge between different parts of a software application. It enables communication, data processing, and interactions between different modules or components in a system.
What is next() in Node.js
In Node.js web frameworks like Express, next() is a control-flow function passed as the third argument to middleware handlers. It acts as a signaling mechanism that tells the server: "I am done processing my part of this request; hand it off to the next middleware function in line."
[ HTTP Request ] ───> [ Middleware 1 ] ─── next() ───> [ Middleware 2 ] ─── next() ───> [ Controller Route ] ───> [ HTTP Response ]
three-way handshake of TCP
TCP (Transmission Control Protocol), is a protocol used by devices/systems to communicate over a network.
The three-way handshake is a fundamental process in the establishment of a TCP (Transmission Control Protocol) connection between two devices, typically a client (initiating device) and a server (responding device). This process ensures that both devices are ready to send and receive data in a reliable manner.
- SYN (Synchronise) - Client to Server:
- The client sends a TCP segment with the SYN flag (SYN = 1, ACK = 0) set to the server.
- This segment indicates that the client wants to establish a connection and is ready to synchronise sequence numbers.
- SYN-ACK (Synchronise, Acknowledge) - Server to Client:
- The server receives the SYN segment from the client and acknowledges it by sending a TCP segment with both the SYN and ACK flags set (SYN = 1, ACK = 1).
- The server also generates its own initial sequence number (ISN) for this connection.
- This segment confirms the client's request to establish a connection and also synchronises the server's sequence numbers.
- ACK (Acknowledge) - Client to Server:
- Upon receiving the SYN-ACK segment from the server, the client acknowledges it by sending a TCP segment with the ACK flag set and its own initial sequence number (ISN) incremented by one.
- This segment acknowledges the server's readiness to establish the connection and confirms synchronisation of sequence numbers.
Browser Same Origin Policy
The Same-Origin Policy (SOP) is a foundational security mechanism built into all modern web browsers. Its primary job is to isolate code loaded from one website, preventing it from interacting with or stealing sensitive data from another website.
It essentially stops a malicious site you visit in one browser tab from reading your bank account or email data open in another tab.
CORS
CORS (Cross-Origin Resource Sharing) is a mechanism by which data or any other resource from different origins (different domain/subdomain/port/protocol) could be shared intentionally.
A preflight request is a specific type of HTTP request that is sent by a web browser to a server as part of the Cross-Origin Resource Sharing (CORS) mechanism.
- do preflight/options request, in order to check that the server will permit the actual request. In that preflight request, the browser sends headers that indicate the HTTP method and headers of the actual request.
- responding to preflight, the server sends back additional headers (would contain the Allowed methods, Allowed origin details about the target site), which is then together with the original header used in the actual request.
- After deciding whether the target site could return the requested information based on this response, the actual request is sent by the browser.
HTTP Methods
- The GET method is used to retrieve data from the server. It is a safe method, meaning it should not have any side effects on the server's state.
- GET requests can be cached, bookmarked, and shared, as they do not change the server's state.
- The POST method is used to submit data to be processed to a specified resource.
- It is not idempotent, meaning multiple identical POST requests may have different effects on the server (e.g., creating multiple records).
- The PUT method is used to update a resource on the server or create it if it doesn't exist.
- It is idempotent, meaning multiple identical PUT requests should have the same effect on the server as a single request.
- The DELETE method is used to delete a specified resource.
- It is idempotent, meaning multiple identical DELETE requests should have the same effect on the server as a single request.
- The HEAD method is similar to a GET request, but it only retrieves the headers of the response without the actual content.
- It is often used to check the status of a resource without requesting the entire content.
- The OPTIONS method is used to describe the communication options for the target resource.
- It can be used to determine which HTTP methods and headers are allowed by a server for a particular resource.
http 1/2 and httpS
Http 1.1: HTTP/1.1 processes requests and responses sequentially. This means that if a browser wants to load multiple resources (like HTML, CSS, JavaScript, images) from a server, it sends one request at a time and waits for each response before sending the next request.
Http 2: One of the biggest improvements in HTTP/2 is the ability to send multiple requests and receive multiple responses over a single TCP connection. This eliminates the problem of head-of-line blocking.
HTTP - A protocol used by clients (e.g. web browsers) to request resources from servers (e.g. web servers).
HTTPS - A way of encrypting HTTP. It basically wraps HTTP messages up in an encrypted format using SSL/TLS.
XMLHttpRequest (XHR)
XHR objects are used to interact with servers. Using XHR we can retrieve data from a URL without having to do a full page refresh. This enables a web page to update just part of a page without disrupting what the user is doing.
XMLHttpRequest is used heavily in AJAX programming.
AJAX (Asynchronous JavaScript and XML)
using AJAX web applications can do API calls and are able to make quick, incremental updates to the user interface without reloading the entire browser page. This makes the application faster and more responsive to user actions.
JWT (JSON Web Token)
JWT is a standard for securely transmitting information between parties as a JSON object. It is commonly used for authentication in web applications. In simple terms, a JWT is like a secret message in a special envelope that only certain people can open because they have the right key. It's a secure way to share information over the internet.
Here's how authentication works with JWT:
- User Authentication: When a user logs in to a web application, the server checks the provided credentials (e.g. username and password) against the stored user data (usually in a database).
- Token Creation: If the provided credentials are valid, the server generates a JWT. This JWT is composed of three parts: a header, a payload, and a signature. The header typically specifies the type of token and the signing algorithm, while the payload contains claims (pieces of information) about the user.
- Token Signing: The server takes the header, payload, and a secret key and signs the JWT. This creates the signature, which ensures that the token has not been tampered with.
- Token Issuance: The server sends the JWT back to the client as part of the response to the login request. The client now has a token that represents the user's authentication status.
- Token Storage: The client typically stores the JWT in the browser's local storage or a cookie. This allows the client to include the token in subsequent requests to the server.
- Token Verification: When the client makes a request to a protected resource (e.g an API endpoint), it includes the JWT in the request headers. The server receives the token.
- Token Decoding: The server first verifies the token's signature using the secret key. If the signature is valid, the server proceeds to decode the JWT to access the information in the payload.
- Claims Verification: The server checks the claims in the payload to ensure they meet the required criteria. For example, it may check the expiration time (exp claim) to ensure the token is still valid.
- Access Granted or Denied: If the token is valid and the claims pass verification, the server grants access to the requested resource. If not, it denies access and may send an error response.
- Token Refresh (Optional): If the JWT has an expiration time (exp claim), the client can request a new token before the current one expires. This can be done using a refresh token or by re-authenticating.
In the digital world, JWTs are used for authentication. After we log in to a website, the server sends a JWT. This JWT contains our user ID (payload) and is locked with a secret key (signature). In our subsequent requests to the server, this JWT is included in the request. The server can then read the user ID from the JWT and know who we are without asking for our password every time.
Cross Site Scripting (XSS)
Cross Site Scripting (XSS) is a vulnerability in a web application that allows a third party to execute a script in the user’s browser on behalf of the web application. It allows the attacker to compromise the interactions that users have with a vulnerable app. It usually happens because the website does not filter malicious code, it is mixed with normal code, and the browser has no way to distinguish which scripts are trustworthy, which leads to the execution of malicious code.
Three types of XSS attack:
- Reflected XSS: It arises when an application receives data in an HTTP request and includes that data within the immediate response in an unsafe way.
- DOM XSS: DOM-based XSS arises when an application contains some client-side JavaScript that processes data from an untrusted source in an unsafe way, usually by writing the data back to the DOM.
- Stored XSS: Stored XSS (also known as persistent or second-order XSS) arises when an application receives data from an untrusted source and includes that data within its later HTTP responses in an unsafe way.
Preventing XSS vulnerabilities is likely to involve a combination of the following measures:
- Filter input on arrival: At the point where user input is received, filter as strictly as possible based on what is expected or valid input.
- Escape HTML Entities
function escapeHTML(input) {
return input.replace(/</g, '<').replace(/>/g, '>')
.replace(/&/g, '&').replace(/"/g, '"').replace(/'/g, ''');
}
- Encode data on output: At the point where user-controllable data is output in HTTP responses, encode the output to prevent it from being interpreted as active content. Depending on the output context, this might require applying combinations of HTML, URL, JavaScript, and CSS encoding.
- Use appropriate response headers: To prevent XSS in HTTP responses that aren't intended to contain any HTML or JavaScript, you can use the Content-Type and X-Content-Type-Options headers to ensure that browsers interpret the responses in the way you intend.
- Content Security Policy: As a last line of defence, you can use Content Security Policy (CSP) to reduce the severity of any XSS vulnerabilities that still occur. To enable CSP, you need to configure your web server to return the Content-Security-Policy HTTP header. ref: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
Content Security Policy
CSP allows us to define origins from where resources are allowed to be loaded and executed in our web page.
Content Security Policy (CSP) is a security feature implemented by web browsers to mitigate the risks of cross-site scripting (XSS) attacks, data injection, and other code injection attacks that can compromise the security of a web app.
To enable CSP, we need to configure our web server to return the Content-Security-Policy HTTP header.
Alternatively, the <meta> element can be used to configure a policy, for example:
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src https://*; child-src 'none';" />
Directives
- default-src: Specifies the default source for resources if no other directive is matched.
- script-src: Controls the sources from which scripts can be loaded.
- style-src: Controls the sources from which styles can be loaded.
- img-src: Specifies allowed sources for images.
- font-src: Specifies allowed sources for fonts.
- connect-src: Defines the allowed sources for network requests like AJAX and WebSocket.
- frame-src: Specifies the origins from which frames can be loaded.
- media-src: Defines sources for media elements like <audio> and <video>.
- object-src: Specifies sources for objects such as Flash content.
Cross Site Request Forgery (CSRF)
It is a type of security vulnerability that occurs when a malicious website tricks a user's browser into making an unintended request to a different site on which the user is authenticated. This can lead to actions being taken on the user's behalf without their consent.
e.g.
- Let's say you are logged into your online banking account in one browser tab.
- While still logged in, you visit a malicious website in another tab.
- The malicious website contains a hidden form that submits a request to your bank's website, using your authenticated session without your knowledge.
- This request might perform actions like transferring funds or changing account settings, since it's being made in the context of your authenticated session.
To prevent CSRF attacks, web applications can implement measures like:
- CSRF Tokens: Generating a unique token for each user session and requiring this token to be submitted with each request. This token is checked on the server side to verify the legitimacy of the request.
- Same-Site Cookies: Setting cookies with the SameSite attribute to restrict when they are sent along with a request. This helps prevent cookies from being sent in cross-origin requests.
Man In The Middle Attack (MITM)
MITM refers to the fact that the attacker establishes a unique connection with both ends of the communication, and exchanges the data they receive, so that both ends of the communication think they are passing through a private connection and a direct conversation with each other, but in fact the entire conversation is completely controlled by the attacker. In a MITM attack, the attacker can intercept the two-way communication and insert new content.
Subresource Integrity (SRI)
Subresource Integrity (SRI) is a security feature that enables browsers to verify that resources they fetch (for example, from a CDN) are delivered without unexpected manipulation. It works by allowing you to provide a cryptographic hash that a fetched resource must match.
Tokenisation
Tokenisation is the process of replacing sensitive data or real-world assets with unique, non-sensitive digital identifiers (called "tokens"). It is widely used across three main fields: digital payments for security, blockchain technology for asset ownership, and artificial intelligence/machine learning for language processing.
Generate Random Unique ID in Web
const uniqueId = window.crypto.randomUUID();
console.log(uniqueId);
Smart Contracts
Smart contracts are self-executing contracts with the terms and conditions written directly into code. They run on blockchain platforms, ensuring that the terms of the contract are automatically enforced, executed, and recorded without the need for intermediaries like banks or legal systems (external 3rd party). Smart contracts are often used to create and manage tokens or cryptocurrencies.
Decentralised: Smart contracts operate on blockchain networks, which are decentralised and distributed across many nodes (computers). This decentralisation ensures that no single entity has control over the contract.
Transparency: All transactions and operations performed by a smart contract are recorded on the blockchain and are transparent and publicly viewable.
Digital Signature

Memento Architecture
The Memento Design Pattern is a behavioral design pattern that allows us to capture and save the current internal state of an object so that it can be restored to this exact state later— all without violating encapsulation (keeping the object's private data secure).
It is the foundational pattern used to build Undo/Redo mechanisms, transaction rollbacks, or game save-state systems.
Next.js
SSG - Static Site Generation
Entire application is fully built and compiled into flat, static HTML, CSS, and JS files at build time (during the CI/CD deployment pipeline). These static files are pushed directly to a globally distributed Content Delivery Network (CDN). When a visitor hits your URL, the CDN instantly serves the pre-built files from the edge node nearest to them.
SSR - Server-Side Rendering
In (SSR), a web page is rendered on the server (where the web app is deployed) and then sends the fully rendered HTML page to the client (browser), then client hydrates it (attach event listeners)
Advantages of Server-Side Rendering:
- SEO Benefits: Search engines have an easier time crawling and indexing content in HTML files. With SSR, search engines can quickly access the fully-rendered content, which can improve search engine rankings.
- Improved Initial Page Load Time: Since the server sends a fully-rendered page to the client, the user can see the content sooner. This can lead to better perceived performance, especially on slower networks.
- Better Social Sharing: When you share a link on social media platforms, the platform often fetches a preview of the page. With SSR, the platform can easily access the fully-rendered HTML, ensuring accurate previews.
- Accessibility and Performance on Low-Powered Devices: Devices with limited processing power, like older smartphones or IoT devices, may struggle with heavy client-side rendering. SSR can provide a better user experience on such devices.
- Avoidance of Flash of Unstyled Content (FOUC): With client-side rendering, there's a brief moment where the page might appear unstyled until the JavaScript loads and applies the styles. SSR helps avoid this FOUC.
- Easier Caching and CDN Integration: Fully-rendered HTML can be easily cached, which improves performance and reduces the load on servers. CDNs can also cache the HTML content.
CSR - Client-Side Rendering
In CSR, the server sends a basic HTML file along with a JavaScript bundle. The JavaScript code is responsible for generating the content and rendering it in the browser (client).
Advantages of Client-Side Rendering:
- Fast Initial Load: CSR can provide a fast initial load time, especially for smaller applications. Only essential HTML, CSS, and JavaScript are sent from the server.
- Smooth User Experience: Once the initial load is complete, interactions are fast and smooth. Changes in the UI can happen instantly without requiring full page reloads because of SPA.
- Rich Interactivity: CSR allows for highly interactive web applications with dynamic content updates based on user actions.
- Better for Web Applications: CSR is well-suited for web applications where a lot of the content and interactivity is generated dynamically based on user input or API calls.
In practice, many applications use a combination of both Server-Side Rendering and Client-Side Rendering (CSR) for the best of both worlds. This is sometimes referred to as "Hybrid Rendering" or "Universal Rendering".
SSR is often used for the initial page load, while client-side rendering takes over for subsequent interactions, providing a more interactive experience.
How will you select between CSR, SSR & SSG for your application? What parameters will you consider?
1. When to use CSR (Client-Side Rendering)
Use CSR when your application is highly interactive, locked behind a login screen, and does not require search engine visibility.
- Best Used For: Authenticated user dashboards (e.g., finance tracking, analytics portals).
- Internal company tools and inventory management systems.
- SaaS web applications (e.g., Notion, Trello) where content is dynamic but private.
- The Rule of Thumb: If a user must log in to see the content, use CSR. Google cannot crawl gated pages anyway, so server pre-rendering is a waste of resources.
2. When to use SSR (Server-Side Rendering)
Use SSR when your data changes constantly, needs to be updated in real-time on every click, but must be indexed perfectly by search engines.
- E-commerce product pages with shifting prices, stock counts, or flash sales.
- Social media feeds and live trend timelines (e.g., X/Twitter feed layouts).
- Live news websites or sports tickers where content becomes stale within minutes.
- The Rule of Thumb: If the data changes frequently and public users/search bots need to see the exact same fresh data instantly upon arrival, use SSR.
3. When to use SSG (Static Site Generation)
Use SSG when the content is public, rarely changes, and your primary goals are maximum speed and top-tier SEO.
- Company landing pages and marketing websites.
- Documentation platforms and product manuals.
- Personal portfolios, blogs, and case studies.
- The Rule of Thumb: If the content is identical for every single visitor and only changes when you manually update it, pre-build it using SSG for blistering CDN speeds.
Metric / Feature | CSR | SSR | SSG |
Initial Page Load Speed | Slow (Waiting for JS to download & run) | Fast (HTML arrives completed) | Blazing Fast (Served instantly via edge CDN) |
Data Freshness | Real-time (Fetched on demand) | Real-time (Computed on every request) | Delayed (Fixed at build time unless using ISR) |
Server Cost | Very Low (Static files served via storage) | High (Requires continuous active node server) | Very Low (Static asset hosting only) |
SEO | Poor (Bots struggle to execute heavy client JS) | Excellent | Excellent |
What is hydration in Next.js and when can it cause UI mismatches?
Hydration is the process where React takes the static HTML pre-rendered by Next.js on the server and injects JavaScript event listeners (onClick, onChange, etc) into it once it reaches the browser, making the page fully interactive.
What is a Hydration Mismatch?
When the HTML generated on the server does not perfectly match the initial HTML React calculates in the browser. When they don't align, React loses track of the DOM and throws an error.
- Using Browser-Only APIs During Render: Referencing window, document, or localStorage directly in your component markup (the server evaluates it as undefined, while the client evaluates the real value).
- Dynamic / Non-Deterministic Data: Using new Date() or Math.random(). The server generates one timestamp during the build/request, but the client generates a slightly later timestamp during hydration.
- Invalid HTML Structure: Nesting a <div> inside a <p> tag. The browser's native parser automatically forces a fix to the broken HTML structure before React can hydrate it, destroying the expected layout matching template.
Fix: Use a useEffect Guard: Keep browser-dependent stuffs inside useEffect so it only triggers after the initial client hydration is complete.
React Server Components
React Server Components (RSC) is an architectural paradigm/pattern that splits React components into two categories based on where they execute: Server Components and Client Components.
- Server Components (Default): These run exclusively on the server (during build time or on each request). They can fetch data directly from databases or file systems, and their source code is never sent to the browser, keeping the client bundle size incredibly small.
- Client Components: These are marked with the "use client" directive. They are sent to the browser to handle user interactivity, state management (useState), and lifecycle effects (useEffect).
Instead of sending heavy JavaScript libraries to the browser, RSC renders static parts on the backend and sends a lightweight UI description (the RSC payload) to the client, which seamlessly merges with interactive components.
Feature | Server-Side Rendering (SSR) | React Server Components (RSC) |
Primary Goal | Fast Initial Load: Instantly converts the initial React tree into raw HTML so the user doesn't see a blank screen while JS downloads. | Zero Bundle Size & Direct Backend Access: Decouples components so heavy backend logic stays off the client. |
Where the code lives | The code for every component is still bundled and sent to the browser so React can hook up interactivity (hydration). | The code for Server Components never leaves the server. Only the final UI layout structure is sent. |
Interactivity | Components can use state, hooks, and browser events (after hydration is complete). | Server components cannot use state or browser hooks. Interactivity must be passed to Client Components. |
Data Fetching | Happens at the top page level before rendering (e.g., getServerSideProps or router-level fetches). | Can happen dynamically at the individual component level using standard async/await. |
State Preservation | Refetching or navigating via full SSR will wipe out existing client state (like text typed into an input box). | Refetching data from the server updates the UI dynamically without losing the user's current client-side state. |
TL;DR:
- SSR/RSC/SSG/ISR
- Automatic Code Splitting
- Default Routing support (without using any external library)
- Image Optimisation: Images are automatically optimised for different screen sizes and devices, improving website performance and user experience. Next.js uses techniques like lazy loading, responsive images, and format detection to deliver the most efficient image experience.
- API Proxying
- Built in Internationalisation (i18n) Support (without using any external library)
- Many options of Data Fetching (getStaticProps, getServerSideProps)
- Built-in Typescript support
- Turbopack Bundler (based in Rust, introduced in Next 14)
- Server Actions (introduced in Next 14, allows us to run server-side code directly from React components, simplifying data mutations and user interactions without API routes in specific scenarios.)
Rendering Strategies
- React Server Components (RSC): Default in App Router. Renders on server; minimizes client-side JS bundle size.
- SSR (Server-Side Rendering): HTML generated on every request. Best for dynamic, personalized data.
- SSG (Static Site Generation): HTML generated at build time. Extremely fast, ideal for SEO and static content (blogs, marketing).
- ISR (Incremental Static Regeneration): Revalidates and updates static pages in the background after deployment without full rebuilds.
Routing (App Router)
- File-Based Routing: Directory structure defines URL paths (`app/dashboard/page.js` -> `/dashboard`).
- Special Files:
• `layout.js`: Shared UI across sub-routes; preserves state on navigation.
• `loading.js`: Automatic loading UI using React Suspense.
• `error.js`: Isolated error boundaries for graceful degradation.
- Dynamic Routes: Folders named with brackets ( `[id]` ) capture dynamic path parameters.
Data Fetching & Full-Stack
- Server Actions: Direct execution of asynchronous server-side code from client forms/events without manual API endpoints.
- Route Handlers: Custom backend endpoints (`GET`, `POST`, etc.) built inside `route.js` using standard Web APIs.
- Middleware: Code executed before a request is completed; ideal for auth, redirects, and geo-targeting.
Built-In Optimizations
- next/image: Automatic resizing, modern formats (WebP), lazy-loading, prevents Layout Shift (CLS).
- next/font: Self-hosts fonts automatically; eliminates external network requests and avoids font flicker (FOUT).
- Code Splitting: Splitting bundles per page so users only download necessary JS.
Tooling & DX
- SWC / Turbopack: Rust-based compiler replacing Babel/Webpack for fast build times.
- Fast Refresh: Instant live-editing while preserving component state.
- TypeScript: Out-of-the-box support with automatic type generation for routes.
Next.js Streaming allows a server to break down a webpage's HTML into chunks and send them to the browser piece-by-piece over a single HTTP connection as soon as they are ready.
Instead of waiting for slow database queries to load the entire page, Next.js streams the static layout (headers, sidebars) instantly, leaving loading placeholders where the slow data will eventually slide in.
It can be done in 2 ways
- Component Level Streaming: By wrapping a slow React Server Component in a <Suspense> boundary, we isolate its loading state from the rest of the layout.
- Page Level Streaming: We can use Next.js's file convention. By creating a loading.tsx file inside a route folder, Next.js automatically wraps the entire page.tsx in a fallback boundary.
For more details check nextjs-streaming of coreJs Repo
Server Actions
Server Actions are asynchronous server-side functions that we can invoke directly from our React components (both Client and Server components) without manually writing an API route. They provide a seamless bridge between our frontend UI and backend logic.
Instead of capturing a form submission, writing a manual fetch('/api/endpoint') call, and setting up a separate API route file to handle the request, you define a function with the "use server" directive. Next.js automatically handles the network communication behind the scenes.
Basic Example:
// A Server Component
export default function OrderForm() {
// The Server Action runs entirely on the server
async function createOrder(formData) {
"use server";
const itemId = formData.get("itemId");
// Securely run database operations directly here
await db.order.create({ data: { itemId } });
}
return (
<form action={createOrder}>
<input type="text" name="itemId" />
<button type="submit">Place Order</button>
</form>
);
}
getStaticPaths, getStaticProps, getServerSideProps, getInitialProps (Legacy Stuffs)
- getStaticPaths (Before Next 14)
- getStaticPaths is a method in Next.js that allows us to specify which paths should be pre-rendered at build time when using Static Site Generation (SSG).
- getStaticProps (SSG - Before Next 14):
- Usage: Used in a page component to fetch data at build time for Static Site Generation - SSG
- Execution: Runs at build time, not in the client-side JavaScript bundle.
- revalidate in getStaticProps: revalidate in getStaticProps, specifies how often Next.js should re-generate the static page.
- getServerSideProps (SSR - Before Next 14):
- Usage: Used in a page component to fetch data on each request, on the server-side (Server Side Rendering).
- Execution: Runs on the server every time a request is made to the page.
export async function getServerSideProps / geStaticProps() {
const response = await fetch('https://api.example.com/data'); // Fetch data from an API or database
const data = await response.json();
return {
props: { data, }
};
}
The getStaticProps & getServerSideProps function must return an object with a props key. The value of props will be passed as props to the page component.
- getInitialProps (obsolete - the ancient way - not used anymore):
- Usage: Used in both page components and regular React components.
- Execution:
- In page components, it can run on both the server and the client. On the server, it runs during the initial request. On the client, it runs when navigating to the page using the client-side router.
- In regular React components, it only runs on the client.
- Return Value: Should return an object with the data that will be merged into the component's props.
MyComponent.getInitialProps = async () => {
const response = await fetch('https://api.example.com/data'); // Fetch data from an API
const data = await response.json();
return { data };
};
Feature Strategy | Pages Router (Old) | App Router (Modern) |
Static Generation (SSG) | getStaticProps | fetch(url) (Default behavior) |
Server-Side Rendering (SSR) | getServerSideProps | fetch(url, { cache: 'no-store' }) |
Incremental Static (ISR) | getStaticProps + revalidate | fetch(url, { next: { revalidate: 60 } }) |
Dynamic Path Generation | getStaticPaths | generateStaticParams() |
React.js
React
React.js is an open-source JavaScript library used for building user interfaces or UI components for web applications. React is particularly popular for creating interactive and dynamic user interfaces because it allows developers to efficiently update and render components as data changes. React is often described as a declarative library for building user interfaces. In a declarative approach, you specify what you want to achieve, and the library (React in this case) takes care of updating the DOM to match the desired state.
Key features and concepts of React include:
- Component-Based Architecture: React applications are built using components, which are self-contained, reusable modules that encapsulate a specific piece of UI
- Reusable Components: Components in React are designed to be reusable, which promotes code modularity and maintainability.
- Component Lifecycle: React components go through various lifecycle stages (mounting, updating, un-mounting), and one can hook into these stages to perform actions at specific points in a component's life.
- Virtual DOM: React maintains a lightweight representation of the DOM, known as the virtual DOM. When data changes, React compares the new virtual DOM with the previous one to identify the minimal set of updates needed to reflect the changes in the actual DOM. This makes React very efficient in managing UI updates.
- JSX (JavaScript XML): React uses JSX, a syntax extension for JavaScript, which allows developers to write HTML-like code directly within their JavaScript files. JSX makes it easier to visualise and build component hierarchies.
- Unidirectional Data Flow: React follows a unidirectional data flow, which means that data in an application flows in a single direction. This makes it easier to track changes and debug code.
Challenges with React
- SEO
- Props drilling: passing props to deep down the component
- State Management
- Unnecessary Re-renders: can be prevented using React.memo
Virtual DOM
It is a key concept in React that helps to efficiently update the actual DOM, making React highly performant.
Here's a breakdown of how it works:
- Actual DOM: The DOM (Document Object Model) represents the structure of a web page. It's a tree-like structure where each element (e.g., div, p, span) is a node.
- Virtual DOM: The Virtual DOM is a lightweight copy of the actual DOM. It's a JavaScript object that mirrors the structure of the actual DOM.
Reconciliation
Reconciliation is the algorithm React uses to differentiate one virtual dom tree with another to differentiate which parts need to be changed.
- Something changes: When a component's state, props, context or parent component changes in React, a new virtual DOM tree is created to represent the updated state.
- Diffing: React performs a process called "diffing" to compare the new virtual DOM tree with the previous virtual DOM. It identifies the differences (or "diffs") between the two trees.
- Minimal Updates: It identifies the minimal set of DOM operations (insertions, deletions, or updates) to update the actual DOM based on the diffs. It aims to minimise the number of actual DOM operations needed.
- Batched Updates: React doesn't immediately update the actual DOM after each change. Instead, it batches these changes together.
- Updating the Actual DOM: Finally, React applies the identified updates to the actual DOM. This is the step where the changes are reflected in the browser.
Advantages of the Virtual DOM
- Efficiency: By comparing virtual DOM trees rather than making direct changes to the real DOM, React can make intelligent decisions about how to update the UI with minimal performance impact.
- Optimization: React optimises the update process to ensure that only the necessary parts of the DOM are modified, resulting in faster rendering times.
- Cross-platform Consistency: The Virtual DOM allows React to work consistently across different platforms and browsers, abstracting away any inconsistencies or quirks in how different browsers handle the DOM.
- Smoother User Experience: The efficient updating process of the Virtual DOM leads to a smoother and more responsive user experience, especially in complex applications with frequent updates.
- Facilitates Reconciliation: The Virtual DOM enables React to efficiently reconcile changes in the component's state or props, ensuring that the UI accurately reflects the application's data.
React Fiber Architecture
React Fiber is a job-scheduling architecture that manages how, when, and in what priority the differences found in the Virtual DOM are calculated and pushed to the real DOM.

- Reconciliation (The Whole System)
- What it is: The umbrella term for the entire process React uses to keep the user interface in sync with the underlying application state. It is the full backstage operation.
- Virtual DOM (The Blueprint)
- What it is: A lightweight, in-memory JavaScript representation of the user interface.
- Why it matters: It acts as a fast, cheap scratchpad. React can create and tear down these virtual objects instantly without touching the heavy, slow browser DOM.
- Diffing (The Logic)
- What it is: The mathematical algorithm that compares the previous Virtual DOM tree with the newly generated one.
- Why it matters: It handles the intelligence. It pinpoints the exact elements that changed so React knows the absolute minimum number of mutations required to update the screen.
- React Fiber (The Job Scheduler)
- What it is: The underlying scheduling engine and linked-list data structure that hosts and executes the reconciliation process.
- Why it matters: It handles the mechanics. It breaks the heavy diffing work down into tiny, bite-sized tasks. If a user interacts with the page (like typing or clicking) midway through a render, Fiber pauses the background calculation, handles the urgent user action, and then smoothly resumes updating the screen.
Analogy
Think of a kitchen renovation project managed by a company called Reconciliation Inc
- The Virtual DOM is the quick digital 3D model of the new kitchen layout.
- Diffing is the automatic software report showing the exact changes (e.g., "Only the cabinet doors need replacing, keep the existing fridge").
- React Fiber is the smart project manager on-site. If a pipe bursts (high priority), they freeze the cabinet installation, divert the crew to fix the pipe instantly, and then pick up right where they left off with the cabinets once the emergency is resolved.
Key Benefits of React Fiber
React Fiber’s job-scheduling architecture brings three major upgrades to how web applications run:
- No More Screen Freezing (Time Slicing): It breaks massive, heavy rendering tasks into tiny, bite-sized steps. Instead of locking up the browser thread until a huge page finishes loading, it updates the layout in small increments, giving the browser room to breathe.
- Smart Task Prioritization: It understands that not all actions are equal. It prioritizes urgent user inputs (like typing in a text field or clicking a button) over low-priority background work (like loading a hidden chart or rendering a list footer).
- Interruptible Rendering: It allows React to pause a heavy, non-urgent layout calculation mid-way through if a user suddenly clicks something. React handles the click instantly, throws away or pauses the old work, and then smoothly resumes its calculations without a single frame of lag.
- Enables Concurrency Features: Fiber serves as the foundational engine that makes modern React features possible, including <Suspense> for data loading, streaming server-side rendering, and performance hooks like useTransition.
React 18 Core Features
React 18 introduces structural changes to the core rendering engine. At its heart is the concept of Concurrent Rendering, which allows React to pause, resume, or abandon updates in the background to ensure the main UI thread stays responsive.
Key changes introduced in React 18:
- The New Root API: Swaps out the legacy ReactDOM.render method in favor of ReactDOM.createRoot. This serves as the explicit gateway required to unlock all new concurrency features.
- Automatic Batching: Automatically groups multiple state updates together into a single re-render, regardless of where they originate. In previous versions, batching was limited to native React event handlers; React 18 extends this to promises, timeouts, and native browser events.
e.g. Automatic batching inside an asynchronous fetch call:
// React 18 triggers exactly ONE re-render for both the state updates
fetch('/api/user').then(() => {
setIsLoading(false);
setUserData(data);
});
- Transitions: Introduces a way to classify state updates into two categories: Urgent Updates (typing, clicking) and Transition Updates (heavy background computations like filtering list components). This is implemented via the new useTransition and startTransition hooks.
e.g. Managing heavy filtering tasks with useTransition:
const [isPending, startTransition] = useTransition();
const [search, setSearch] = useState('');
const handleSearch = (e) => {
setSearch(e.target.value); // Urgent: Shows typed letters instantly
startTransition(() => {
setFilterQuery(e.target.value); // Non-urgent: Filters thousands of items in the background
});
};
- New Built-in Hooks: Includes specific hooks for solving layout and architectural constraints:
- useId: Generates unique, stable IDs on both the server and client sides to prevent hydration mismatches.
- useDeferredValue: Accepts a value and returns a deferred copy that lags behind the main thread, acting like a native performance debounce.
- useSyncExternalStore: Safely subscribes external data stores (like Redux or Zustand) to the new concurrent rendering mechanism without visual glitching.
- useInsertionEffect: Specifically built for CSS-in-JS libraries to inject style tags dynamically before layout effects run.
What is 'flushSync' in React 18, and what's a realistic scenario where you'd wrap a state update with it?
flushSync is a utility (from react-dom package) that forces React to complete a state update synchronously and instantly mutate the real browser DOM.
It acts as an escape hatch to bypass React 18's automatic batching. The exact millisecond the callback code inside flushSync finishes running, React forces a visual layout update before moving to the next line of JavaScript.
The Problem Without flushSync
Standard React state updates are asynchronous. If you add a new message to a list state (setMessages) and then immediately calculate the container's height on the next line to scroll down, the browser DOM hasn't actually updated yet. Your code will read the old height, causing the scroll anchor to miss the new message.
The Solution With flushSync
By wrapping the state update in flushSync, you force the new message to render to the screen instantly. On the very next line of code, your scroll calculation reads the correct new height, snapping the container perfectly to the bottom.
Use it when: You must read or modify DOM properties (like .getBoundingClientRect(), .focus(), or .scrollTop) immediately following a state modification.
useSyncExternalStore
useSyncExternalStore is a built-in React Hook introduced in React 18 that allows components to safely subscribe to external data stores (any state management container outside of React's built-in useState or useReducer systems).
The hook takes three arguments and returns the current snapshot of the external store's data:
- subscribe: A function that registers a callback with the external store. React calls this to listen for data changes.
- getSnapshot: A function that returns the current value of the store. It must return a cached/stable value if the data hasn't changed.
- getServerSnapshot (Optional): A function that returns the initial snapshot during Server-Side Rendering (SSR) and hydration.
How It Works Internally (The Mechanics)
When a component mounts with useSyncExternalStore, React initializes a hidden internal subscription.
- The Handshake: React passes an internal updater function (the callback) to your subscribe function.
- The Watcher: The external store saves this callback. Whenever the data inside that store mutates, it must execute this callback.
- The Trigger: Calling the callback alerts React that an external change just happened.
- The Consistency Check: React immediately runs your getSnapshot() function. It compares the new snapshot value against the previous snapshot value using a strict reference check (Object.is). If the value or reference is different, React forces a synchronous re-render to keep the UI perfectly synced.
The Golden Rule of getSnapshot ⚠️
The most critical aspect of useSyncExternalStore is that getSnapshot must return a cached or immutable reference if the data hasn't changed.
For more details check react-optimisation of coreJs repo
React 19 Core Updates
React 19 focuses on Data Mutation and Form Automation, introducing native features to eliminate standard loading, error, and optimistic UI state boilerplate.
- Form Actions (Async Transitions): Pass async functions directly to the HTML <form> action. React automatically handles the pending lifecycle, errors, and form resets.
- New Built-in Hooks
- useActionState: Tracks the result, errors, and pending loading state of an async Action wrapper.
- useFormStatus: Allows nested child components to read parent form metadata without prop-drilling. Note: Must be nested inside a <form> element.
- useOptimistic: Instantly updates a display state to a predicted "successful outcome" while an async action runs, rolling back to source data on failure.
- The use API: Can be called conditionally to unpack Promises or read Context streams mid-render.
- Quality of Life Structural Upgrades
- No More forwardRef: ref is now a standard, regular prop; no need to wrap components in forwardRef().
- Context as a Provider: Simplified context usage: use the context variable directly (<Context>) as a wrapper instead of <Context.Provider>.
- First-Class Asset Management: Metadata tags placed anywhere in a component are automatically hoisted to the HTML <head>.
Key in React
In React, the key prop is a special attribute that is included when creating a list of elements. It helps React identify which items have changed, been added, or been removed.
Uniqueness: The key prop should be unique among siblings of the same parent. It helps React differentiate between components and efficiently update the virtual DOM.
Stability: The key should be stable across re-renders. Avoid using indexes as keys as if the list changes, it can lead to unexpected behaviour. E.g. If you insert an item at the beginning of a long list, every subsequent item's index changes (0→1, 1→2, etc.). React assumes the entire list has fundamentally changed and destroys/re-creates every single DOM node instead of just inserting the new one.
Performance: Providing a key helps React optimise the rendering process. Without keys, React might need to recreate the entire list when there are changes, which can be less efficient.
JSX (JavaScript XML)
JSX is a syntax extension for JavaScript often used with React. It allows us to write HTML-like code directly within JavaScript files. JSX provides a more readable and concise way to describe the structure of user interfaces.
React Component
A React component is a self-contained, reusable module that encapsulates a specific piece of UI. Components in React are designed to be reusable, which promotes code modularity and maintainability.
In React, components can be thought of as JavaScript functions or classes that return elements, specifying what should be rendered on the screen. They can also maintain their own internal state and receive data through "props".
There are two main ways to define a React component: 1. Functional Components, 2. Class Components
Component State and Props
Props (Properties):
Props are inputs to a React component. They are passed down from a parent component to a child component. Props are read-only and cannot be modified within the component itself. They allow us to customise the behaviour or appearance of a component.
State:
State is a way for a component to keep track of information that may change over time. It represents the current state of the component and determines how it should render. Unlike props, state is managed within the component itself.
React.CreateElement
React.createElement is a method provided by the React library for creating React elements, which are the building blocks of React applications.
const element = React.createElement(elementType, props, children);
e.g.
const element = React.createElement('div', { className: 'my-class' }, 'Hello, World!');
i.e.
const element = <div className="my-class">Hello, World!</div>;
Functional vs Class components
Functional | Class |
Functional components are just JavaScript functions that take props as an argument and return React elements. | Class components are ES6 classes that extend React.Component. |
With Hooks, functional components can use useState and other hooks to manage state. | Class components can manage their own local state using this.state and this.setState(). |
useEffect for life cycle methods | Class components have access to lifecycle methods like componentDidMount, componentDidUpdate, and componentWillUnmount |
Functional components are typically faster and more lightweight than class components. This is because they don't have the overhead associated with class instantiation and lifecycle methods. |
|
React Life Cycle Methods
React lifecycle methods are special functions that get called at different stages in a component's life. They allow us to hook into these different points to perform actions, such as setting up a component, updating it, or cleaning it up. As of React 16.3, some lifecycle methods have been deprecated in favour of safer alternatives called "lifecycle hooks" which are used with functional components and React Hooks.
Class Component: componentDidMount, componentDidUpdate, and componentWillUnmount
Functional Component: useState, useEffect, useRef, useContext, useReducer
React Hooks
React Hooks are functions that allow us to use state and other React features in functional components. They were
e.g. useState, useEffect, useRef, useContext, useReducer
Custom Hooks
A custom hook in React is a JavaScript function that uses one or more built-in hooks. It helps us to encapsulate, separate complex logic and reuse stateful logic across multiple components.
Custom hooks must always start with the word "use”, it helps React to recognise them as hooks.
Features of custom hooks:
- Reusing Logic: Custom hooks allows us to extract and reuse stateful logic that may be used in multiple components. This can include state management, side effects, subscriptions, and more.
- Separation of Concerns: Custom hooks promote separation of concerns. They allow us to keep our components focused on rendering and user interactions, while moving complex logic into hooks.
- Composable: Custom hooks can be composed together. Multiple custom hooks can be used in a single component, allowing us to reuse different pieces of logic independently.
- Manage State and Side Effects: Custom hooks can use built-in hooks like useState, useEffect, etc. This enables us to manage component state and perform side effects within the custom hook.
Stateful Logic
Any program's logic that depends on or interacts with the current state of the component. It involves managing and updating the state of the component based on user interactions, data changes, or other events.
Examples of stateful logic in a React application might include:
- Managing form input values and validation.
- Controlling the visibility or behaviour of UI elements based on user actions.
- Handling data fetching and updates from an API.
- Implementing conditional rendering based on the current state.
Side Effect
In React, a side effect refers to any code that affects something outside the scope of the current component. Side effects are often asynchronous operations that don't happen immediately.
Common examples of side effects in React components include:
- Data Fetching: Making API requests to get data for a component.
- Event Listeners & Subscriptions: Setting up and tearing down event listeners or subscriptions.
- Manual DOM Manipulation: Directly interacting with the DOM using methods like getElementById, etc.
- Timers: Setting and clearing intervals or timeouts.
- Updating State Based on Props: Using useEffect to update component state based on changes in props.
The primary way to manage side effects in functional components is through the useEffect hook.
useEffect(() => {
// Side effect code here
return () => {
// Cleanup code (optional)
};
}, [/* dependencies */]);
Synthetic Events
Synthetic events are a cross-browser wrapper around the native browser events. They provide a consistent interface for handling events across different browsers.
Event Pooling: React reuses synthetic event objects. This means that the event object is nullified after the event callback has been called. If you need to access event properties asynchronously, you should call event.persist().
function handleClick(event) {
event.persist(); // This allows you to access event properties later
setTimeout(() => {
console.log(event.type); // This will work now // without event.persist(), event will be null here
}, 100);
}
Error Handling in React Application
- Try Catch Statement: We can use try catch at places/code logic (react side-effects) where we think that something might fail, and display meaningful messages to users when code goes to catch.
- then/catch for promises: We can also display meaningful messages to user when code goes to catch while using then/catch for promises
- React Error Boundary: It is used to gracefully handle errors in React Apps. It helps us prevent the full app crash and let us define a fallback UI when an error is detected in our Application. It is generally wrapped at the top level of the app so that errors at any of the children components can be detected by our error boundary.
What triggers a react functional component re-render and what is happening after the rerender?
- A react functional component can re-render if:
- Its state changes
- Its props changes
- Its parents component re-renders
- context changes (if the component consumes any context and any value in the context get changed)
After rerendering, React compares the new Virtual DOM with the previous one, and finds the most optimised way (minimum number of operations - add/delete/modify) to update the actual DOM.
How can we diagnose unnecessary re-renders and fix them with clarity?
- Component Composition (Move State Down): Instead of forcing a heavy parent component to hold high-frequency state (like inputs or scroll tracking), split that state out into its own dedicated child component.
- React.memo + Stable References (useMemo and useCallback): If a child component must live inside a shifting parent, wrap the child in React.memo so it only updates if its props change. If we pass an object, array, or function as a prop, JavaScript recreates their memory references on every render, which breaks React.memo. so must lock those references down using useMemo and useCallback.
- Creating Store & useSyncExternalStore: Create a external store for a state, and subscribe to it using useSyncExternalStore in the components where that state is used. Or we can also use any state management libraries like redux, zustand, jotai
ref: react-optimisation of coreJs Repo
How was your experience in optimizing the performance of React applications. What tools, techniques, or strategies have you employed to enhance rendering speed and reduce load times?
I target a LCP under 2.5s and INP under 200ms, I profile bottlenecks using pagespeed, Bundle Analyzer.
Techniques I use:
- State Localization: Moved state down to the leaf nodes, preventing minor data updates from triggering app-wide re-render chains.
- Automated Memoization: Leveraged the React Compiler to automate reference equality, eliminating manual useMemo/useCallback boilerplate.
- Intelligent Caching: Swapped unoptimized useEffect fetch blocks for React Query to gain instant server-state caching and kill duplicate API calls.
- Bundle Splitting & Deferred Loading: Used React.lazy() and Suspense to split code at route boundaries, while deferring non-critical telemetry scripts until after initial paint to maintain a 95+ Web Vital score.
- List Virtualization: Implemented DOM windowing for heavy data tables to render only viewport-visible elements, keeping paint times sub-millisecond.
HOC
Higher-Order Component is basically a component in react that takes a component as an argument and returns a new component with additional props or functionality.
It is used to enhance the functionality of a component by wrapping it with another component. HOCs are a way to reuse code logic and add additional features to a component without modifying the component itself.
e.g. HOCs are commonly used for tasks like authentication and authorization. They can check if a user is authenticated and conditionally render components based on their authentication status.
Suspense
React Suspense is a feature that allows components to "suspend" rendering while they wait for some data to load. This is especially useful for handling asynchronous operations like dynamic import of components using lazy loading.
const LandingPage = lazy(() => import('./pages/LandingPage'));
<Suspense fallback={<div>Loading...</div>}>
<LandingPage />
</Suspense>
Routing in React
Routing in React is the process of managing the navigation of a web app by dynamically rendering different components based on the URL. This allows users to move between different parts of the app without the need for a full page refresh.
To implement routing in React, we typically use a library called react-router.
npm install react-router-dom
React Pure Component (React.memo in functional comps)
PureComponent is similar to Component but it skips re-renders for the same props and state. To skip re-rendering a class component for the same props and state, extend PureComponent instead of Component.
React.PureComponent provides a default implementation of the shouldComponentUpdate method that performs a shallow comparison of the current and next props and state. It helps to optimise the rendering performance by preventing unnecessary re-renders when the props and state are unchanged.
Stateless Component
Stateless components typically refer to components in a user interface that do not manage their own state.
Stateless components receive data through their props (properties) and render it in a certain way, but they don't have their own internal state. This means that their behaviour is purely based on the data they receive, and they don't have the ability to modify that data directly.
e.g. button, loader, etc
Controlled vs. Uncontrolled Components
Controlled Component: The form data is handled directly by a React component's state.
Uncontrolled Component: The form data is handled directly by the browser DOM itself.
function ControlledForm() {
const [username, setUsername] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
console.log(`Submitting Username: ${username}`); // Data is already in state
};
return (
<form onSubmit={handleSubmit}>
<label>Username:</label>
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
<button type="submit">Submit</button>
</form>
);
}
function UncontrolledForm() {
const inputRef = useRef(null);
const handleSubmit = (e) => {
e.preventDefault();
// Reaching out directly to the DOM node to read its current value
console.log(`Submitting Username: ${inputRef.current.value}`);
};
return (
<form onSubmit={handleSubmit}>
<label>Username:</label>
<input type="text" ref={inputRef} />
<button type="submit">Submit</button>
</form>
);
}
If we add defaultValue attribute to the input field of a controlled component, then it will throw a warning, that it contains both defaultValue and value. Input elements must be either controlled or uncontrolled (specify either the value prop, or the defaultValue prop, but not both)
React Memo
React memo is used to stop re-rendering of a component if its props have not changed.
import { memo } from "react";
const Todos = ({ todos }) => {
console.log("child render");
return (
<>
<h2>My Todos</h2>
{todos.map((todo, index) => {
return <p key={index}>{todo}</p>;
})}
</>
);
};
export default memo(Todos);
There are two ways to prevent child components from re-rendering.
- wrapping them in `memo`
- passing them as `children` prop of a parent component
useReducer
It is an alternative to using useState when the state logic involves multiple sub-values or when the next state depends on the previous one.
The useReducer hook takes two arguments: a reducer function and an initial state. It returns an array with two elements: the current state and a dispatch function. The reducer function is responsible for specifying how the state should change in response to dispatched actions.
e.g.
import React, { useReducer } from 'react';
// Reducer function
const reducer = (state, action) => {
switch (action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
case 'DECREMENT':
return { count: state.count - 1 };
default:
return state;
}
};
// Component using useReducer
const Counter = () => {
// Initial state
const initialState = { count: 0 };
// useReducer returns [state, dispatch]
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>Increment</button>
<button onClick={() => dispatch({ type: 'DECREMENT' })}>Decrement</button>
</div>
);
};
useLayoutEffect
We can use useLayoutEffect when we need an immediate DOM update before the screen paints.
- useEffect (Asynchronous): React updates the DOM → The browser paints the screen → useEffect runs. (Can cause a visual "flicker" if you change styles here).
- useLayoutEffect (Synchronous): React updates the DOM → useLayoutEffect runs → The browser paints the screen. The user only sees the final, corrected result.
Rule of thumb: Default to useEffect. Only use useLayoutEffect if you are measuring DOM nodes (like tooltips, popups, or animations) and need to prevent a visual flash.
import { useState, useLayoutEffect, useRef } from 'react';
function Tooltip({ targetRef, children }) {
const [tooltipHeight, setTooltipHeight] = useState(0);
const tooltipRef = useRef(null);
useLayoutEffect(() => {
if (tooltipRef.current) {
// 1. Measure the real DOM element before the browser paints
const { height } = tooltipRef.current.getBoundingClientRect();
// 2. Set state immediately to adjust position
setTooltipHeight(height);
}
}, []); // Run once on mount
// If we used useEffect here, the user would see the tooltip flash at top: 0px for a split second before jumping to its real position.
return (
<div
ref={tooltipRef}
style={{ position: 'absolute', top: `-${tooltipHeight}px` }}
>
{children}
</div>
);
}
useMemo vs useCallback
useMemo:
The useMemo hook is used to memoize a value, such as a computed result or an object. It takes a function and an array of dependencies. The value returned by the function will only be recomputed if one of the dependencies has changed.
e.g.
const MyComponent = ({ data }) => {
const memoizedValue = useMemo(() => {
return computeExpensiveValue(data)
}, [data]);
return <div>{memoizedValue}</div>;
};
useCallback:
useCallback hook memoizes/caches a function definition between re-renders. The dependency array (the second argument) acts as the cache-validation mechanism. It tells React when to preserve the existing function reference and when to discard it and create a new one.
- If a variable used inside the function is left out of the dependency array, it creates a stale closure.
- The Problem: The function locks into the values from the render when it was first created.
- The Result: Even if the state updates in the component, the function will continue reading the old, outdated value forever.
- If you include variables that aren't used inside the function, or pass objects that regenerate on every render, you trigger unnecessary cache clears.
- The Problem: The function is forced to rebuild from scratch far too often.
- The Result: The function gets a brand new memory reference on almost every render. This completely defeats the purpose of useCallback, forcing any downstream components wrapped in React.memo to break cache and re-render needlessly.
Every time ParentComponent renders, a new handleClick function is created. This can lead to unnecessary re-renders of ChildComponent because it receives a new function reference each time, even if the function logic is the same, which can be optimised using useCallback
e.g.
const MyComponent = ({ onClick }) => {
const memoizedCallback = useCallback(() => {
// callback logic
}, [/* dependencies */]);
return <childComponent onClick={memoizedCallback}>Click me</childComponent>;
};
Adding Style in React
- Inline Styles: style={}
- External Stylesheets: import './MyComponent.css';
- CSS Modules: import styles from './MyComponent.module.css'; CSS Modules are a way to locally scope your CSS by default. Each component gets its own CSS file, and the class names are scoped to that component.
- Styled Components (Library):
- npm install styled-components
- import styled from 'styled-components';
- const StyledDiv = styled.div`
color: blue;
font-size: 20px;
`;
Data Flow (Flux Architecture)
Flux is an architectural pattern that helps manage the flow of data in a React application.
Flux follows unidirectional data flow which increases predictability of application code.
The Flux pattern consists of the following parts:
- Actions: Actions are plain JavaScript objects that describe something that has happened in the application. They are dispatched by components or other parts of the application to notify the store about what has occurred.
- Dispatcher: The Dispatcher is responsible for distributing actions to the stores. It acts as a central hub for all actions in the application. There is typically only one dispatcher in a Flux application.
- Stores: Stores are responsible for managing the state of the application. They listen for actions dispatched by the dispatcher and update their state accordingly. Stores are essentially containers for the application's state.
React Context
The React Context API is a way to pass data deep down the nested comp without having to manually pass props through every level of comp (prop drilling). It provides a way to share data between components that are not directly connected.
It is mainly used to pass global data, such as theme information, user authentication, or language preferences, etc.
Here's an overview of how the Context API works:
- Creating a Context: First, you need to create a context using createContext(). This function returns an object with a Provider and a Consumer.
- Provider Component: The Provider component is used to wrap the part of the component tree where we want to make the context available.
- Consume the Context: useContext hook is used to consume the context.
How would you prevent widespread re-renders when using Context for frequently changing state?
- Split the Context: Separate your State and Dispatch (Updater) into two distinct contexts. Components that only trigger actions won't re-render when state changes.
- Colocate & Minimize: Break massive global contexts into small, isolated domain-specific contexts (e.g., separate CartContext from a high-frequency FormContext).
- Memoize Consumers: Extract the heavy UI into a child wrapped in React.memo, then pass the specific context values into it as props from a lightweight wrapper.
- Stabilize the Provider Value: Always wrap your context object in useMemo so it maintains reference stability across parent renders.
- Switch to an External Store: For high-frequency state (e.g., animations, forms, real-time data), swap Context for selector-based libraries like Redux, Zustand, Jotai, or Signals which bypass React's top-down render chain completely.
State Management Libraries (Redux, Zustand, Jotai) vs React Context
In react context, when value in the context updates then the complete component tree re-renders because state lives in the parent component (where we wrapped with Context.Provider) and as the state updates parent re-renders hence the complete component tree (under the parent) re-renders.
But in state management libraries state exists outside React component (in a plain JavaScript store).
- React Context: For static, low-frequency, global architectural data (e.g., UI themes, user authentication sessions, app language settings).
- Atomic State (Zustand): For medium-to-high frequency updates shared across decoupled components (e.g., interactive dashboards, shopping carts, checkout funnels).
- Unidirectional Flux (Redux Toolkit): For enterprise-scale applications with complex background pipelines (e.g., multi-team systems requiring strict action logging, offline sync, or infinite undo/redo).
Feature | React Context / Parent State | Zustand / Jotai / Redux |
State Location | Inside React Component State | Outside React Component Tree |
Parent Component | Re-renders on every update | Does not re-render on updates |
Render Cascade | Starts at the top and goes down | Never starts |
Need React.memo? | Yes, required on parents to block selection updates | No, not needed for selection updates |
Updates | Propagates down from Provider | Directly updates subscribing nodes |
Render Efficiency | O(N) renders by default (unless heavily optimized with memo) | O(1) renders out-of-the-box (only subscribing components re-render) |
Business Logic Location | Coupled inside React hooks and UI components | Decoupled into pure JS/TS store files |
Testing Complexity | High; requires mounting React component trees & DOM simulators | Low; test transitions via simple pure function assertions |
State Persistence | Manual; requires custom hooks and handling browser hydration lifecycle | Automatic; comes with built-in persist middleware options |
Debugging & Tooling | Limited to standard React DevTools component tree inspection | Rich tools (e.g. Redux DevTools) with time-travel and action logs |
Middleware Support | No; requires writing custom abstraction layers | Yes; native pipelines for logging, sync, and async side- |
Redux
Redux is a popular library that implements the Flux architecture with some modifications. Redux provides a predictable and manageable way to handle the state of a React application. Redux simplifies the flow of data by unifying the state management in a single, immutable store.
Redux has the following key components:
- Store: The store is a single source of truth that holds the entire state of the application. It is read-only, and the only way to change its state is by dispatching actions.
- Actions: Actions are plain JavaScript objects that describe what happened in the application. They must have a type property to indicate the type of action, and they can optionally carry additional data (payload). You can think of an action as an event that describes something that happened in the application.
- Reducers: Reducers are pure functions that specify how the application's state changes in response to actions. They take the current state and an action, and return to the new state.
- Dispatch: Dispatching an action is the process of sending an action to the Redux store. This is how you trigger a state change.
Data Flow in Redux:
- Component Triggers Action: A React comp. dispatches an action when an event occurs (e.g. a button is clicked).
- Action is Dispatched: The action is dispatched to the Redux store.
- Reducer Updates State: The reducer processes the action and updates the state in the Redux store based on the action type.
- Store Notifies Subscribers: The store notifies all subscribed components that the state has changed.
- Components Re-render: React components that are connected to the store receive the updated state as props and re-render with the new data.
In Redux, how do you decide what belongs in the global store versus staying as local component state?
Default to local state unless you are needed to lift it up.
Put it in Redux if:
- Shared: Multiple distant components need to read or update it (e.g., Auth, Cart).
- Persistent: The data must survive route changes or components unmounting (e.g., multi-step forms).
- Cached: It is server data fetched from an API that needs to be cached and shared (e.g., via RTK Query).
Keep it in Local State if:
- UI-only: It tracks presentation details unique to that component (e.g., isOpen, isLoading, active tabs).
- Ephemeral: The data is useless once the component unmounts (e.g., a modal's text field).
- High-Frequency: It tracks rapid updates like keystrokes in a text input (to avoid Redux pipeline lag).
In Redux Toolkit, how do you model and manage async server state (loading/error/data) without mixing it into unrelated Ul state?
- RTK Query (Automatic Isolation)
- RTK Query manages server state automatically in a separate, internal cache reducer. It completely removes the need to write manual loaders or error actions.
- const { data, isLoading, error } = useGet<apiname>Query();
- Redux Saga (Event-driven Lifecycle)
- Sagas use ES6 Generator functions (yield) to intercept a trigger action, execute the async side effect via a try/catch block, and dispatch specific success or failure actions back to the reducer.
E.g.
function* fetchDataSaga() {
try {
yield put({ type: 'products/fetchLoading' });
const response = yield call(apiFetch, '/products');
yield put({ type: 'products/fetchSuccess', payload: response.data });
} catch (err) {
yield put({ type: 'products/fetchError', payload: err.message });
}
}
Reducer
Reducers are a pure function that is used to modify redux store. it takes the previous state and the action being dispatched as arguments and -> returns the new state.
Redux Side Effect Manager
The normal Redux flow is: action dispatched, then some state is changed. However, a Redux app won’t be very useful if the only function is changing state.
Actions like talking to the server, accessing local storage, and recording analytics events require reaching out to the outside world. Anything that occurs outside of the Redux normal flow is considered a Redux side-effect.
Redux Saga
Redux-saga is a Redux side effect manager library that aims to manage application side effects (i.e. asynchronous things like data fetching and impure things like accessing the browser cache) easier to manage, more efficient to execute, easy to test, and better at handling failures.
How can we fetch API data without using the useEffect
- useState
- Custom useFetch hook
- useQuery hook of react-query 3rd party library
- HOC
What are the different ways to improve React Applications?
- Lazy loading components/routes
- Avoid unnecessary re-renders: React memo
- Creating Reusable components
- Writing modular code
- Memoization - useMemo, useCallback
- Avoid nesting too many components
- Bundle Splitting
- Code Obfuscation - code minifying
- Debounce/throttle
- Perception - use Loader, error
- Pagination / Infinite Scrolling / Virtualisation
- Optimising Web Vitals
- src-set for images, loading images lazily
Jest & React Testing library
Jest and React Testing Library are popular JavaScript testing libraries used in the React ecosystem for writing unit tests.
React Testing Library and Jest are often used together for testing React comps. Jest provides the testing framework and the ability to run tests, while React Testing Library provides utilities for rendering components and interacting with them.
Jest
Jest is a zero-config, all-in-one testing framework for JavaScript. It's widely used for testing JavaScript code, including React applications. Key features of Jest include:
- Snapshot Testing: Jest can capture a "snapshot" of the rendered output of a component. It allows you to detect unintended changes to the UI.
- Mocking Functions and Modules: Jest provides built-in mechanisms for mocking functions and modules, allowing you to isolate components for testing.
- Asynchronous Testing Support: Jest has built-in support for testing asynchronous code, making it easy to test components that rely on promises or callbacks.
- Test Coverage Reporting: Jest can generate detailed reports on code coverage, helping you identify areas that may need more thorough testing.
React Testing Library
React Testing Library is a set of utility functions designed to make testing React components more intuitive and user-centric. It encourages writing tests that closely resemble how users interact with your application.
Key features of React Testing Library include:
- Querying the DOM by Accessibility: Instead of querying the DOM using implementation details (like class names or element types), IT encourages querying by accessibility roles, labels, and text content.
- Rendering Components: React Testing Library provides utilities for rendering components into a virtual DOM environment, allowing you to interact with them in a controlled manner.
- Fire Events: It provides utilities to simulate user interactions, such as clicks, typing, and submitting forms.
- Support for Async Code: React Testing Library is designed to work seamlessly with asynchronous code, making it easy to test components that rely on data fetching.
TypeScript
What is TypeScript?
TypeScript adds syntax on top of JavaScript, allowing developers to add types. TypeScript allows specifying the types of data being passed around within the code, and has the ability to report errors when the types don't match.
Compiling TypeScript: tsc yourFileName.ts -> it compile the TS file into a JS file
Running the JavaScript: node yourFileName.js
Using TypeScript with a Project: For larger projects, it's common to use a tsconfig.json file to configure TypeScript settings. This file specifies compiler options, file globs, and other settings for the project.
Implicit & Explicit Typing
Implicit Typing: TypeScript will "guess" the type, based on the assigned value.
We can disable implicit variable type assignment by enabling the compiler option: noImplicitAny.
We can enable 'undefined' & 'null' types to be accounted for by enabling the compiler option: strictNullChecks
e.g.
let myString = "Hello"; // TypeScript infers the type as string
let myNumber = 42; // TypeScript infers the type as number
function add(x, y) {
return x + y; // TypeScript infers x and y as any
}
Explicit Typing: Explicit typing involves explicitly stating the type of a variable, function parameter, return type, or any other part of your code where you want to be specific about the type.
e.g.
let myString: string = "Hello";
let myNumber: number = 42;
function add(x: number, y: number): number {
return x + y;
}
typeScript Interfaces
Interfaces are used to define the structure of an object.
interface Person {
name: string;
age: number;
}
let person: Person = { name: "John Doe", age: 30 };
Generics
Generics in TypeScript provide a way to create reusable components and functions that can work with a variety of data types. They allow us to write code without committing to a specific type, making our code more flexible and reusable.
Generics can be assigned default values which apply if no other value is specified or inferred.
e.g.
function echo<T>(arg: T): T {
return arg;
}
let myString: string = echo("Hello, TypeScript!");
let myNumber: number = echo(42);
interface Pair<K, V> {
key: K;
value: V;
}
const pair1: Pair<number, string> = { key: 1, value: "first" };
const pair2: Pair<string, boolean> = { key: "second", value: true };
any & unknown
They are often used in situations where the type of a value is not known at compile time or when interacting with dynamic or external data.
The unknown type is a safer alternative to any. When a value is of type unknown, typeScript expects that we will perform some kind of type-check before we try to use it.
e.g.
let userInput: unknown = "Hello";
// 'unknown' requires a type check before use
if (typeof userInput === "string") {
let myString: string = userInput; // Valid because we checked the type
}
When to use any:
- Transitioning from JS
- Interacting with Dynamic or Unstructured Data
- Working with External Libraries
When to use unknown:
- Interacting with Dynamic Data Safely
- Library or API design
Never
In TypeScript, the never type represents a value that will never occur or a state that is impossible to reach.
It is fundamentally different from void (which means a function returns nothing useful) and any (which can be anything). never means the execution path finishes or terminates before a value can even be returned.
void: "I will finish, but I have nothing to say."
never: "I will literally never reach the finish line."
E.g.
// This function never returns a value because it crashes the execution
function throwError(message: string): never {
throw new Error(message);
}
// This function never returns because the loop runs forever
function infiniteLoop(): never {
while (true) {
console.log("Running...");
}
}
Utility Types
Utility types in TypeScript are a set of predefined generic types that provide commonly used functionalities to work with and manipulate types. They can help simplify and improve the readability of complex type definitions. These utility types are built into TypeScript and can be used out of the box.
- Partial<T>: Makes all properties of a type optional.
- Readonly<T>: Marks all properties of a type as read-only.
- Record<K, V>: Creates a type with keys of type K and values of type V.
- Pick<T, K>: Creates a type with selected properties K from the original type T.
- Omit<T, K>: Creates a type with all properties of T except for the ones specified in K.
- Exclude<T, U>: Excludes types from T that are assignable to U.
- Extract<T, U>: Extracts types from T that are assignable to U.
- NonNullable<T>: Excludes null and undefined from the type T.
Tuple
A tuple is a typed array with a predefined length and types for each index.
They allow each element in the array to be of known type and also they can be of different types.
Unlike arrays, which allow elements of the same type, tuples can contain elements of different types at specific indices.
let arr: string[] = [“yo”,”bro”,”nyc”,”pik”];
let tuple: [string, boolean, number] = [“yo bro”, false, 123];
readonly
readonly is used to make a variable immutable. Readonly variable is there simply to be read from and not modified.
e.g.
let readOnlyTuple: readonly [string, number] = ["Hello", 42];
readOnlyTuple[0] = "Hi"; // Error: Cannot assign to '0' because it is a read-only property
Type aliases
Type aliases allow us to create a custom name for a type, making it easier to refer to complex or frequently-used types. Interfaces are similar to type aliases, but they are only for object types.
e.g.
type MyString = string;
type MyNumber = number;
type Coordinate = {
x: number;
y: number;
};
let myString: MyString = "Hello";
let myNumber: MyNumber = 42;
let point: Coordinate = { x: 10, y: 20 };
Union
Union types are used when a value can be of more than a single type.
function printStatusCode(code: string | number) {
console.log(`My status code is ${code}.`)
}
printStatusCode(404);
printStatusCode('404');
Enums
An enum represents a group of constants (unchangeable variables). It comes in two flavours, string and numeric. By default, enums will initialise the first value to 0 and add 1 to each additional value.
enum CardinalDirections {
North, // 0
East, // 1
South, // 2
West // 3
}
let currentDirection = CardinalDirections.North; // 0
enum CardinalDirections {
North = 'North',
East = "East",
South = "South",
West = "West"
}
let currentDirection = CardinalDirections.North; // North
keyof
keyof is a TypeScript keyword that is used to create a union type of all the keys (property names) in a given type. It allows us to extract the keys from an object or interface.
e.g.
interface Person {
name: string;
age: number;
email: string;
}
type PersonKeys = keyof Person; // "name" | "age" | "email"
function getProperty(obj: Person, key: PersonKeys) {
return obj[key];
}
let personName = getProperty(person, "name"); // "John Doe"
let personAge = getProperty(person, "age"); // 30
Modeling Function
Using type (most common, most readable):
type Add = (a: number, b: number) => number;
const add: Add = (a, b) => a + b;
Using interface (call signature on an object):
interface Add {
(a: number, b: number): number;
}
const add: Add = (a, b) => a + b;
Modeling Union Types
Using type — straightforward:
type Status = "idle" | "loading" | "success" | "error";
Using interface — not possible.
An interface can only declare an object shape.
Declaration Merging
If we declare two interfaces with the exact same name in the same scope, TypeScript automatically merges their properties into a single type.
interface User { name: string; }
interface User { age: number; }
// Resulting type requires both:
const player: User = { name: "Aditya", age: 26 };
Module Augmentation
Module augmentation in TypeScript is a powerful feature that allows you to extend, modify, or add types to existing modules (including third-party libraries) without changing the original source code. It works by using the declare module syntax combined with TypeScript's declaration merging
// Inside your-types.d.ts or any augmented file
import { OriginalClass } from 'third-party-library';
declare module 'third-party-library' {
interface OriginalClass {
newMethod(): void;
extraProperty: string;
}
}
Definitely Typed
Definitely Typed is a community-driven repository that provides TypeScript type definitions for popular JavaScript libraries and frameworks that were originally written in JS. It enables TS developers to use these libraries in a type-safe manner.
npm install --save-dev @types/<library name>
TypeScript Casting
There are times when working with types where it's necessary to override the type of a variable, such as when incorrect types are provided by a library. Casting is the process of overriding a type.
let x: unknown = 'hello';
console.log((x as string).length);
console.log((<string>x).length)
Static Method of Class
Static methods of a class can be accessed directly without creating an object of that class.
static methods of a class are accessed, like <Class Name>.<static function name>
instead of <Object of that Class>.<static function name>
Access Modifier
Access modifiers are keywords that specify the level of access to class members (properties and methods) and constructors. They control how these members can be accessed from outside the class.
- Public: Accessible from anywhere. (default)
- Protected: Accessible within the class and subclasses
- Private: Accessible only within the class
e.g.
class Person {
public isMale: boolean = true;
protected age: number; = 25;
private readonly name: string;
public constructor(name: string) {
// name cannot be changed after this initial definition, which has to be either at its declaration or in the constructor.
this.name = name;
}
public getName(): string {
return this.name;
}
public getAge(): number {
return this.age;
}
}
const person = new Person("Jane");
console.log(person.isMale); // true
console.log(person.age); // Error, as age is protected and can only be accessed within the class and subclasses
console.log(person.name); // Error, as age is private and can only be accessed within the class
console.log(person.getName());
OOPS
OOP (Object-Oriented Programming) is a way of organizing code by bundling related DATA (state) and BEHAVIOR (functions that operate on that data) into single units called OBJECTS.
Instead of having scattered functions that work on loose data:
let name = "Fido"; let age = 3;
function bark(name) { ... }
We package them together:
class Dog { name; age; bark() {...} }
Class: Class is a template/blueprint for creating objects
Object: Object is a instance of a class. A concrete thing created using the blueprint with its own data
4 Pillar of Oops
- Encapsulation
- Inheritance
- Polymorphism
- Abstraction
Prototypal vs classical inheritance
Prototypal Inheritance:
- JS is a prototype-based lang, which means that objects can inherit properties/methods directly from other objects.
- Each object in JavaScript has a prototype. When a property or method is not found on an object, JavaScript looks up the prototype chain to find it in the object's prototypes.
let parent = {
name: 'John',
sayHello: function() {
console.log(`Hello, my name is ${this.name}`);
}
};
// Creating a new object that inherits from 'parent'
let child = Object.create(parent);
child.name = 'Jane';
child.sayHello(); // This will print: "Hello, my name is Jane"
Classical Inheritance: below stuffs
Inheritance: implements
Interfaces can be used to define the type a class must follow through the implements keyword.
interface Shape {
getArea: () => number;
}
class Rectangle implements Shape {
public constructor(protected readonly width: number, protected readonly height: number) {}
public getArea(): number {
return this.width * this.height;
}
}
const myRect = new Rectangle(10, 20);
console.log(myRect.getArea()); // 200
Inheritance: extends
Classes can extend each other through the extends keyword. A class can only extend one other class.
interface Shape {
getArea: () => number;
}
class Rectangle implements Shape {
public constructor(protected readonly width: number, protected readonly height: number) {}
public getArea(): number {
return this.width * this.height;
}
}
class Square extends Rectangle {
public constructor(width: number) {
super(width, width);
}
// getArea gets inherited from Rectangle
}
const mySq = new Square(20);
console.log(mySq.getArea()); // 400
Override
When a class extends another class, it can replace the members of the parent class with the same name.
Newer versions of TypeScript allow explicitly marking this with the override keyword.
Abstract Classes
Abstract classes serve as a template or blueprint that defines a common structure and behaviour for a group of related classes
Classes can be written in a way that allows them to be used as a base class for other classes without having to implement all the members. This is done by using the abstract keyword.
Abstract classes can contain abstract methods, which are methods that are declared but not implemented in the abstract class. Subclasses that inherit from an abstract class must provide concrete implementations for the abstract methods.
e.g.
abstract class Polygon {
public abstract getArea(): number;
public toString(): string {
return `Polygon[area=${this.getArea()}]`;
}
}
class Rectangle extends Polygon {
public constructor(protected readonly width: number, protected readonly height: number) {
super();
}
public getArea(): number {
return this.width * this.height;
}
}
const myRect = new Rectangle(10,20);
console.log(myRect.getArea());
TypeScript Decorator
TypeScript decorators are a way to modify or enhance the behaviour of a class or its members (properties or methods).
Here's a breakdown of how decorators work in TypeScript:
- Declaration: A decorator is essentially a function that is prefixed with an @ symbol and placed immediately before the declaration of a class, or its members.
- Metadata Injection: Decorators are called at runtime with information about the decorated declaration. They have the ability to observe, modify, or replace the decorated declaration.
Decorators are like stickers we can put on different parts of our code, like classes or its members. These stickers give instructions to our code on how to do special things with those parts.
For example, if you have a recipe book, you might put a sticker on a recipe that says "make it spicy." Then, whenever you use that recipe, you know to add some extra spice.
In coding, decorators are like those stickers. They help customise how our code works, making it do special things in certain situations. They're like little notes that tell our code to behave in specific ways.
CSS
Design Tokens
Design Tokens are the absolute smallest building blocks of a design system. They are agnostic, single-value variables - like hex codes for color, pixel values for spacing, or font names for typography. They are stored in a centralized format.
e.g. ref: design-system of machineCoding repo
token.css
:root {
/* PRIMITIVES TOKENS */
--color-off: antiquewhite;
--color-red: red;
--color-green: green;
}
[data-theme="dark"] {
/* PRIMITIVES TOKENS for dark theme */
--color-off: darkslategrey;
}
:root,
[data-theme="dark"] {
/* SEMANTIC TOKENS */
--bg-color-main: var(--color-off);
/* COMPONENT TOKENS */
--body-bg-main: var(--bg-color-main);
}
style.css
body {
background-color: var(--body-bg-main);
}
Responsive vs Adaptive Design
Responsive design is making a page automatically adjust the layout and content of a web app based on the screen size and device characteristics (using flexible grids and layouts, along with media queries in CSS) while Adaptive design involves creating multiple versions of a website or application, each optimised for specific device types or screen sizes.
CSS Position: absolute vs fixed vs relative
- absolute: An element with position: absolute is positioned relative to its nearest non-static ancestor (an ancestor that is not position: static, i.e it can be relative, absolute, fixed). If no positioned ancestor is found, the element is positioned relative to the initial containing block (usually the viewport).
- fixed: An element with position: fixed is positioned relative to the viewport (the browser window) and stays in the same place even when the page is scrolled
- relative: It positions an element relative to its normal position on the web page. When we use position: relative; we can then use the top, right, bottom, and left properties to offset the element from its normal position.
CSS Padding vs Margin
css margin can be negative, but padding can’t be negative
CSS letter-spacing vs word-spacing
word-spacing is used to configure the amount of space between words, while letter-spacing is for letters.
CSS box-sizing
box-sizing is a CSS property that defines how the total width and height of an element are calculated. It determines whether the specified width and height include padding and borders or not.
The property can take two values: content-box (the default) and border-box.
Border-box: In the border-box model, the specified width and height include padding and border. This is particularly useful when we want to set a specific width or height for an element and include padding and border within that space.
Content-box: In the default content-box model, the specified width and height of an element do not include the padding and border. They only apply to the content area.
ch unit
The ch unit stands for "character". It represents the width of the zero ('0') character of the current font
CSS clamp()
clamp(min, val, max) e.g. clamp(16px, calc(100vw / 12.5), 32px)
clamp() for font sizes, allows us to set a font-size that grows with the size of the viewport, but doesn't go below a minimum font-size or above a maximum font-size.
CSS Pseudo Classes
A pseudo-class is used to define a special state of an element.
For example, it can be used to:
- Style an element when a user mouses over it
- Style visited and unvisited links differently
- Style an element when it gets focus
e.g. :hover, :visited, :focus, :first-child, :nth-child(n)
CSS Pseudo Elements
A CSS pseudo-element is used to style specified parts of an element.
For example, it can be used to:
- Style the first letter, or line, of an element
- Insert content before, or after, the content of an element
e.g. ::after, ::before, ::first-letter, ::first-line, ::marker, ::selection
CSS Symbols
- > selects direct children.
- space selects descendants.
- ~ selects siblings that follow.
- + selects the immediately following sibling.
diff b/w display: none; visibility: hidden’ and opacity: 0 in terms of layout, interactivity, and accessibility?
Property | Takes up Layout Space? | Clickable / Interactive | Read by Screen Readers? | Animatable? |
display: none | ❌ No | ❌ No | ❌ No (Ignored) | ❌ No |
visibility: hidden | Yes | ❌ No | ❌ No (Ignored) | Yes (Step-transition) |
opacity: 0 | Yes | Yes | Yes (Read out loud) | Yes (Smooth fade) |
CSS Box Model

CSS Flexbox vs Grid
Grid
- Grid is used to create structured layouts in web pages.
- It divides the web pages into rows and columns.
- It is used to handle 2 Dimensional layouts in HTML, while flexbox can handle only 1D.
- It takes a basis on layout, i.e. it does not get affected by content.
Flexbox
- Flexbox is made for one-dimensional(1D) layouts, and the Grid is made for two-dimensional(2D) layouts.
- It means flexbox can work on either rows or columns at a time, but Grids can work on both.
- Flexbox takes a basis in the content while Grid takes a basis in the layout.
We should consider using grid layout when:
- We have a complex design to work with and want maintainable web pages
- We want to add gaps over the block elements
We should consider using flexbox when:
- We have a small design to work with a few rows and columns
- We need to align the element
- We don’t know how your content will look on the page, and you want everything to fit in.
Grid
To have N number of items in a row, and M number of items in a column
.grid {
display: grid;
grid-template-rows: repeat(N, 1fr);
grid-template-columns: repeat(M, 1fr);
}
1fr stands for one fractional unit. It represents a flexible share of the leftover space inside the grid container after all fixed-size items (like those set in pixels, percentages, or rems) have been accounted for.
Flex-shrink: 0
flex-shrink: 0; // don't let the height/width to go below the defined in any situation
Flex-grow: 0
flex-grow: 0 (default); // don't let the height/width to go below the defined in any situation
CSS Media Queries
/* On screens whose size is up to 800px (800px or less) */
@media screen and (max-width: 800px) { }
CSS Pre-Processor
CSS is primitive and incomplete. Building a function, reusing a definition or inheritance are hard to achieve. For bigger projects, or complex systems, maintenance is a very big problem. This is where CSS Preprocessors come to the rescue.
A CSS Preprocessor is a tool used to extend the basic functionality of default vanilla CSS through its own scripting language. It helps us to use complex logical syntax like – variables, functions, mixins, code nesting, and inheritance, etc. By using CSS Preprocessors, we can seamlessly automate menial tasks, build reusable code snippets, avoid code repetition and bloating and write nested code blocks that are well organised and easy to read.
e.g SCSS (Sassy CSS), LESS (Leaner Style Sheets), Stylus
CSS Mixins
CSS mixins are a way to group together a set of CSS declarations and reuse them throughout the stylesheet. They're similar to functions, allowing us to define a block of styles and then include or "mix in" those styles wherever needed.
e.g.
/* Define a mixin */
@mixin border-radius($radius) {
-webkit-border-radius: $radius;
-moz-border-radius: $radius;
-ms-border-radius: $radius;
border-radius: $radius;
}
/* Use the mixin */
.button {
@include border-radius(5px);
background-color: #3498db;
color: #ffffff;
padding: 10px 20px;
border: none;
cursor: pointer;
}
.box {
@include border-radius(10px);
border: 2px solid #ccc;
padding: 20px;
}
CSS BEM
The Block, Element, Modifier methodology (BEM) is a popular naming convention for classes in HTML and CSS. Its goal is to help developers better understand the relationship between the HTML and CSS in a given project.
E.g.
/* Block component */
.btn {}
/* Element that depends upon the block */
.btn__price {}
/* Modifier that changes the style of the block */
.btn--orange {}
.btn--big {}
CSS @import rule
The @import rule allows us to import a style sheet into another style sheet.
The @import rule must be at the top of the document (but after any @charset declaration).
The @import rule also supports media queries, so we can allow the import to be media-dependent.
e.g.
@import "navigation.css"; /* Using a string */
or
@import url("navigation.css"); /* Using a url */
On responsive design, how do you choose breakpoints and layout strategy so components adapt smoothly across devices without relying on many device-specific media queries?
- The Core Strategy: Mobile-First
- Write your base CSS for small screens without media queries. Use min-width media queries to layer on complexity only as space increases. It is much easier to scale a simple layout up than to strip a complex desktop layout down.
- Choose Content-Driven Breakpoints
- Forget specific device sizes. Start with your mobile layout and slowly widen your browser. The exact point where the design looks awkward or lines of text get too long—that is your breakpoint.
- Define breakpoints in em or rem so they adapt if a user zooms in or changes their browser's default font size.
- Use Intrinsic (Fluid) Layout
- Let components size themselves based on their available space instead of hardcoding widths at fixed breakpoints.
- CSS Grid auto-fit: Automatically calculates how many columns fit.
- grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
- Flexbox Wrapping: Items sit side-by-side if there is room, but cleanly drop to a new line and expand when space shrinks.
- flex-wrap: wrap;
- flex: 1 1 300px; /* grow, shrink, ideal basis */
- Fluid Typography: Use clamp() to smoothly scale typography between a defined minimum and maximum without media query jumps.
- font-size: clamp(1.5rem, 4vw + 1rem, 3rem);
- Leverage Container Queries
- Instead of asking "How wide is the screen?" (Media Queries), use Container Queries to ask "How much space does my parent element have?" This allows a single component to automatically look like a desktop version when placed in a wide main content area, or a mobile version when dropped into a narrow sidebar—with zero global media queries.
Web Tech Trends
React Server Components (RSC) & Server-First UI
- Data fetching and rendering happen entirely on the server close to the database.
- Zero-Bundle-Size: Components render to lightweight HTML/JSON. Zero client-side JavaScript impact for static or data-heavy UI blocks.
- Frameworks like Next.js, Remix, and Nuxt use this as their default behavior.
Edge Compute Rendering
- Routing and rendering are shifted from a single centralized data center to Edge Networks (e.g., Cloudflare Workers, Vercel Edge).
- Renders pages globally right next to the end-user for near-zero latency.
Partial Prerendering (PPR)
- Combines static and dynamic rendering on a single page.
- The static shell loads instantly from a CDN, while dynamic "holes" (like a personalized dashboard) stream in via server compute as soon as they are ready.
Performance-First Native Tooling
- e.g., Vite (moving to Rolldown), Turbopack, Biome.
- Hyper-fast compilers, linters, and bundlers written in low-level languages (Rust & Go) to completely replace legacy JS-written tooling (Webpack, Babel).
- Provides near-instant hot module replacement (HMR) and slashes enterprise build times from minutes to seconds.
Micro-Frontends & Isomorphic Frameworks
Astro (Deep Dive Update)
- Island Architecture: Delivers 100% pure static HTML by default. Components only initialize (hydrate) individually from the network when they scroll into view or require interactivity.
- Zero JS by default: No massive JavaScript runtime overhead shipped to the client browser.
- UI-agnostic: Seamlessly mix and match components from React, Preact, Svelte, Vue, or Solid on the exact same page.
AI Tooling
Agentic AI
- Shift from basic prompt-and-response chat to autonomous systems that can plan, reason, and execute multi-step workflows.
- Connects seamlessly with APIs, databases, and third-party tools to perform end-to-end tasks (e.g., automated customer onboarding or full-stack debugging) without human intervention.
- Driven by a massive drop in inference costs, making multi-step autonomous logic highly cost-effective.
Multi-Agent Systems
- A design pattern where specialized AI agents (e.g., a "Writer Agent", a "Reviewer Agent", and a "Tester Agent") collaborate with each other to solve complex problems.
Mixture of Experts (MoE)
- An architectural design where a model is broken down into smaller, specialized sub-networks ("experts").
- A central gating network routes your prompt only to the most relevant experts instead of activating the entire massive neural network.
- Why it matters: Drastically reduces compute power and slashes inference costs while maintaining state-of-the-art accuracy.
Advanced Multimodal Interfaces
- Native processing of text, vision, audio, and video simultaneously within a single model architecture.
- Enables real-time browser-based video processing, context-aware visual search, and hyper-realistic, low-latency voice translation.
Next-Gen AI Workspace Assistants
- e.g., Cursor, Windsurf, Claude Code, GitHub Copilot Workspace.
- Evolution from basic single-line inline code completion to full-workspace awareness.
- These tools can read an entire repository, write complete feature modules across multiple files, run tests, and self-correct compilation errors autonomously.
ES2022 (EcmaScript 2022)
- Private Class Fields: variable/function starts with # e.g #name = “aditya”
- Static Class Fields
- Top Level await: await can be used directly without having inside a async function
- Array.at() function: can be use with negative index to access element from back of the array
- Object.hasOwn() Function
- RegExp Match Indices
LightWight Frameworks
e.g. svelte, solid.js, Remix, Astro, qwik
Remix: A JS framework
almost 0KB size, very less javascript
Astro: A JS framework
Component Islands: A new web architecture for building faster websites in which component js is fetched individually from the network when needed. Components only hydrate when they scroll into view. If you don't see it, Astro won't load it.
Zero JS, by default: No JavaScript runtime overhead to slow you down.
UI-agnostic: Supports React, Preact, Svelte, Vue, Solid, Lit and more.
qwik: A JS Framework
syntax similar to react, No hydration, auto lazy-loading
javascript get loaded when user interact with the UI
preact signal
What makes Signals unique is that state changes automatically update components and UI in the most efficient way possible
Headless UI(from tailwind css)
When we use UI components from any library like materialUI, or any other, it comes with its own look and feel and do not match with our project’s theme, then we need to write our css overrides to make it feel as per our project’s theme.
Headless UI is a set of completely un-styled, fully accessible UI components for React and Vue
Tauri
- Tauri is an app construction toolkit that lets you build software for all major desktop operating systems using web technologies.
- It uses native renderer (like webkit for safari on mac) instead of embedding Chromium and Node.js
- Backend binding (like to access any native feature) language is Rust
- app setup size is smaller because it uses native renderer instead of embedding Chromium and Node.js
Electron.js
- Electron is a framework for building desktop applications using JavaScript, HTML, and CSS. By embedding Chromium and Node.js into its binary, Electron allows you to maintain one JavaScript codebase and create cross-platform apps that work on Windows, macOS, and Linux — no native development experience required.
- vsCode, Slack, facebook messenger uses electron
- Backend binding (like to access any native feature) language is Node.js
Satori
Vercel rolled out an awesome tool: Satori, an engine which converts your HTML to SVG!
Workerd
new Javascript Runtime(like node.js, deno) developed by cloudflare
Web Assembly (wasm)
Build Application for Web in languages other than JS.

DSA
DSA Patterns
Topic | Difficulty to Learn | Return on Investment |
Two Pointers | Easy | High |
Sliding Window | Easy | High |
Breadth-First Search | Easy | High |
Depth-First Search | Medium | High |
Backtracking | High | High |
Heap | Medium | Medium |
Binary Search | Easy | Medium |
Dynamic Programming | High | Medium |
Divide and Conquer | Medium | Low |
Trie | Medium | Low |
Union Find | Medium | Low |
Greedy | High | Low |
- Sliding Window - used to analyze specific sub-section of a Array / String
- window
- sub-array / substring / sub-sequence (meet some condition like max, min, target)
- METHODS:
- Expands or contracts the window to meet specific conditions
- Two Pointers - used to efficiently analyze specific segments of a Array / String
- Palindrome / Pair / Reverse
- METHODS:
- Same direction: used for scanning data in a single pass (e.g., fast and slow pointers to detect cycles or find middle elements).
- Opposite directions: used for finding pairs (e.g., sum of two numbers in a sorted array).
- Binary Search
- sorted stuffs (meet some condition like find, divide)
- BFS/DFS
- almost all graph (including tree) can be solved using them
- DFS: Dives deep into one path before exploring others
- BFS: Explores nodes level by level
- Priority Queue (Heap)
- kth largest / smallest / frequent / closest element
- top n largest / smallest / frequent / closest elements
- select something based on some priority
- Backtracking - extension of DFS - used to explore all possible paths
- go into depth looking for best optimized solution if the current is not optimized then go back and check at that point
- Builds the solution dynamically by making decisions and backtracking on invalid paths
- Dynamic Programming
- where ever recursion is used -> it can be optimized using DP
- Optimizes solutions by breaking problems into overlapping subproblems - solution of overlapping problems can be saved/memoized by pre-computing
- METHODS
- Top-down: recursive with memoization to store results.
- Bottom-up: solves smaller subproblems iteratively using a table.
- Greedy Algo
- pick best option at the point and move to next sub-problem
- min cost
- shortest path
- Divide & Conquer
- Divide problem in to non-overlapping sub-problems
Binary Search
- Implementation
- Closest To Target in Sorted Array: https://www.geeksforgeeks.org/problems/find-the-closest-number5513/1
- First Bad Version: https://leetcode.com/problems/first-bad-version/description/
- Peak Finder: https://leetcode.com/problems/find-peak-element/
- Search for a Range: https://leetcode.com/explore/interview/card/top-interview-questions-medium/110/sorting-and-searching/802/
- Search in Matrix: https://leetcode.com/problems/search-a-2d-matrix/description/
- Search in Rotated Sorted Array: https://leetcode.com/problems/search-in-rotated-sorted-array/description/
- Eating Banana: https://leetcode.com/problems/koko-eating-bananas/description/
Solution: https://github.com/adityasuman2025/CP/tree/master/JS/binarySearch
Sorting Algos
- Bubble Sort
- Selection Sort
- Merge Sort
- Quick Sort
- Insertion Sort
- Heap Sort
- Merge 2 Sorted Array: https://leetcode.com/problems/merge-sorted-array/description/
- Merge N Sorted Array: https://bigfrontend.dev/problem/merge-sorted-arrays
- Top K Frequent Elements: https://leetcode.com/problems/top-k-frequent-elements/description/
Solution: https://github.com/adityasuman2025/CP/tree/master/JS/sorting
Array
- Set Matrix Zeros: https://leetcode.com/problems/set-matrix-zeroes/description/
- Remove Duplicates from Sorted Array: https://leetcode.com/problems/remove-duplicates-from-sorted-array/description/
- Rotate Array: https://leetcode.com/problems/rotate-array/description/
- Rotate Matrix: https://leetcode.com/problems/rotate-image/description/
- Single Number: https://leetcode.com/problems/single-number/
- Find the Duplicate Number: https://leetcode.com/problems/find-the-duplicate-number/
- 2 Sum in Unsorted Array: https://leetcode.com/problems/two-sum/description/
- 4 Sum: https://leetcode.com/problems/4sum/description
- Maximum Sum Subarray: https://leetcode.com/problems/maximum-subarray/
- Best Time to Buy Sell Stock 2: https://leetcode.com/problems/best-time-to-buy-and-sell-stock-ii/
- Best Time to Buy Sell Stock 1: https://leetcode.com/problems/best-time-to-buy-and-sell-stock
- Next Permutation: https://leetcode.com/problems/next-permutation/description/
Solution: https://github.com/adityasuman2025/CP/tree/master/JS/array
Two Pointers
- Move Zeros: https://leetcode.com/problems/move-zeroes/description/
- 2 Sum in Sorted Array: https://leetcode.com/problems/two-sum-ii-input-array-is-sorted/
- 3 Sum: https://leetcode.com/problems/3sum/description/
- K Sum Pairs: https://leetcode.com/problems/max-number-of-k-sum-pairs/description
- Reverse Vowels of a String: https://leetcode.com/problems/reverse-vowels-of-a-string/description/
- Square of Sorted Array: https://leetcode.com/problems/squares-of-a-sorted-array/
- Container With Most Water: https://leetcode.com/problems/container-with-most-water/description/
- Trapping Rain Water: https://leetcode.com/problems/trapping-rain-water/
Solution: https://github.com/adityasuman2025/CP/tree/master/JS/twoPointers
Sliding Window
- Maximum No of Vowels In Substring: https://leetcode.com/problems/maximum-number-of-vowels-in-a-substring-of-given-length/description/
- Maximum Average Subarray: https://leetcode.com/problems/maximum-average-subarray-i/description/
- Maximum Consecutive Ones: https://leetcode.com/problems/max-consecutive-ones-iii/description/
- Longest SubString Without Repeating Characters: https://leetcode.com/problems/longest-substring-without-repeating-characters/
- str.indexOf: https://leetcode.com/problems/find-the-index-of-the-first-occurrence-in-a-string/description/
- Minimum Window Substring: https://leetcode.com/problems/minimum-window-substring/description/
Solution: https://github.com/adityasuman2025/CP/tree/master/JS/slidingWindow
Tree
- BST (Binary Search Tree) Implementation
- Sorted Array to Binary Search Tree: https://leetcode.com/problems/convert-sorted-array-to-binary-search-tree/description/
- Kth Smallest Element in Binary Search Tree: https://leetcode.com/problems/kth-smallest-element-in-a-bst/description/
- Lowest Common Ancestor: https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-tree/description/
- Root To Leaf Path Target Sum: https://leetcode.com/problems/path-sum/description/
- Count Good Node: https://leetcode.com/problems/count-good-nodes-in-binary-tree/description
- Binary Tree Maximum Path Sum: https://leetcode.com/problems/binary-tree-maximum-path-sum/description/
- Longest Zig Zag Path in Binary Tree: https://leetcode.com/problems/longest-zigzag-path-in-a-binary-tree/description/
- Count Path Sum in Binary Tree: https://leetcode.com/problems/path-sum-iii/description/
- Dirty Tree
Solution: https://github.com/adityasuman2025/CP/tree/master/JS/tree
Heap
- Min Heap Implementation
- Priority Queue Implementation
- Kth Largest Element in an Array: https://leetcode.com/problems/kth-largest-element-in-an-array/description/
- Kth Largest Element in Stream: https://leetcode.com/problems/kth-largest-element-in-a-stream/description/
- Minimum Rope Cost: https://practice.geeksforgeeks.org/problems/minimum-cost-of-ropes-1587115620/1
- 'K' Closest Points to Origin: https://leetcode.com/problems/k-closest-points-to-origin/description/
- Task Scheduler: https://leetcode.com/problems/task-scheduler/description/
Solution: https://github.com/adityasuman2025/CP/tree/master/JS/heap
Graph
- Implementation
- Number of Connected Components in an Undirected Graph: https://neetcode.io/problems/count-connected-components/question
- Can Visit All Rooms: https://leetcode.com/problems/keys-and-rooms/
- Number of Provinces: https://leetcode.com/problems/number-of-provinces/description/
- Topological Sort (Used in Google Sheets for cell dependency resolution): https://www.geeksforgeeks.org/problems/topological-sort/1
- Dijkstra Algorithm: https://practice.geeksforgeeks.org/problems/implementing-dijkstra-set-1-adjacency-matrix/1
- Nearest Exit From Maze: https://leetcode.com/problems/nearest-exit-from-entrance-in-maze/description/
- Biggest Island: https://leetcode.com/problems/max-area-of-island/
- Flood Fill: https://leetcode.com/problems/flood-fill
- Rotten Oranges: https://leetcode.com/problems/rotting-oranges/
- Number of Islands: https://leetcode.com/problems/number-of-islands/
Solution: https://github.com/adityasuman2025/CP/tree/master/JS/graph
Stack
- Implementation
- Removing Star From String: https://leetcode.com/problems/removing-stars-from-a-string/description/
- Valid Parentheses: https://leetcode.com/problems/valid-parentheses/description/
- Longest Valid Parentheses: https://leetcode.com/problems/longest-valid-parentheses/
- Asteroid Collision: https://leetcode.com/problems/asteroid-collision/description/
- Next Greater Element: https://practice.geeksforgeeks.org/problems/next-larger-element-1587115620/1
- Next Greater Element 2: https://leetcode.com/problems/next-greater-element-ii/
- Maximum Area in Matrix (Maximum Rectangle): https://leetcode.com/problems/maximal-rectangle/
- Infix To Postfix: https://leetcode.com/problems/basic-calculator/description/
Solution: https://github.com/adityasuman2025/CP/tree/master/JS/stack
Queue
- Implementation
Solution: https://github.com/adityasuman2025/CP/blob/master/JS/Queue.js
Linked List
- Implementation
- Palindrome linked list: https://leetcode.com/problems/palindrome-linked-list/
- Merge sorted linked list: https://leetcode.com/problems/merge-two-sorted-lists/submissions/
- Rotate List by k: https://leetcode.com/problems/rotate-list/description/
- Reverse in k Group: https://leetcode.com/problems/reverse-nodes-in-k-group/description/
- Odd/Even linked list: https://leetcode.com/problems/odd-even-linked-list/description/
- Killing in Circular Table: https://leetcode.com/problems/find-the-winner-of-the-circular-game/
- Merge N Sorted linked list: https://practice.geeksforgeeks.org/problems/flattening-a-linked-list/1
Solution: https://github.com/adityasuman2025/CP/tree/master/JS/linkedList
Backtracking
- Subsets: https://leetcode.com/problems/subsets/description/
- Combination Sum 1: https://leetcode.com/problems/combination-sum/
- Combination Sum 2: https://leetcode.com/problems/combination-sum-ii/
- Combination Sum 3: https://leetcode.com/problems/combination-sum-iii/
- Permutations: https://leetcode.com/problems/permutations/description/
- Letter Combinations: https://leetcode.com/problems/letter-combinations-of-a-phone-number/description
- Generate Valid Parenthesis: https://leetcode.com/problems/generate-parentheses/description/
- Word Search: https://leetcode.com/problems/word-search/description/
Solution: https://github.com/adityasuman2025/CP/tree/master/JS/backtracking
Greedy
- Fractional Knapsack: https://practice.geeksforgeeks.org/problems/fractional-knapsack-1587115620/1
- Activity Selection: https://practice.geeksforgeeks.org/problems/activity-selection-1587115620/1
- Coin Change: https://leetcode.com/problems/coin-change-ii/description/,
- Job Sequencing https://practice.geeksforgeeks.org/problems/job-sequencing-problem-1587115620/1
- Non-overlapping Intervals: https://leetcode.com/problems/non-overlapping-intervals
- Meeting Room II: https://neetcode.io/problems/meeting-schedule-ii/question
- Lamp Light
- Minimum Jumps to Reach End (Jump Game II): https://leetcode.com/problems/jump-game-ii/description
- Jump Game 1: https://leetcode.com/problems/jump-game/
- Minimum Coin Change: https://leetcode.com/problems/coin-change/description/
- Minimum Platform: https://practice.geeksforgeeks.org/problems/minimum-platforms-1587115620/1
- Max Guests In Party: https://practice.geeksforgeeks.org/problems/maximum-intervals-overlap5708/1
- Minimum Number of Arrows to Burst Balloons: https://leetcode.com/problems/minimum-number-of-arrows-to-burst-balloons
Solution: https://github.com/adityasuman2025/CP/tree/master/JS/greedy
Dynamic Programming (DP)
- Min Cost Climbing Stairs: https://leetcode.com/problems/min-cost-climbing-stairs
- House Robber: https://leetcode.com/problems/house-robber/description
- Longest Increasing Subsequence: https://leetcode.com/problems/longest-increasing-subsequence/description/
- Unique Paths: https://leetcode.com/problems/unique-paths/description/
- Longest Common SubSequence: https://leetcode.com/problems/longest-common-subsequence/
Solution: https://github.com/adityasuman2025/CP/tree/master/JS/dp
Maths
- Maths Formulas
- Decimal to Binary & Vice Versa
- Math.Pow(x, n): https://leetcode.com/problems/powx-n/description
- Math.sqrt(n): https://leetcode.com/problems/sqrtx/description/
- Excel Sheet Column Number: https://leetcode.com/problems/excel-sheet-column-number/description/
Solution: https://github.com/adityasuman2025/CP/tree/master/JS/maths
LRU Cache
https://leetcode.com/problems/lru-cache/
Solution: https://github.com/adityasuman2025/CP/blob/master/JS/LRUCache.js
Largest Subarray with 0 Sum
https://practice.geeksforgeeks.org/problems/largest-subarray-with-0-sum/1
Solution: https://github.com/adityasuman2025/CP/blob/master/JS/largestSubArrWith0Sum.js
All SubSequence of a String
Solution: https://github.com/adityasuman2025/CP/blob/master/JS/allSubSeqOfString.js
All SubStrings of a String
Solution https://github.com/adityasuman2025/CP/blob/master/JS/allSubStrOfString.js
Longest Consecutive Sequence
https://leetcode.com/problems/longest-consecutive-sequence/
Solution: https://github.com/adityasuman2025/CP/blob/master/JS/longestConsecSeqInArr.js
Group Anagrams
https://leetcode.com/problems/group-anagrams/description
Solution: https://github.com/adityasuman2025/CP/blob/master/JS/groupAnagrams.js
Overlapping Rectangle
https://leetcode.com/problems/rectangle-overlap/
Solution: https://github.com/adityasuman2025/CP/blob/master/JS/overlappingRect.js
Reverse Array
Solution: https://github.com/adityasuman2025/CP/blob/master/JS/reverseArray.js
3Sum Closest
https://leetcode.com/problems/3sum-closest/
Solution: https://github.com/adityasuman2025/CP/blob/master/JS/threeSumClosest.js
Valid Anagram
https://leetcode.com/problems/valid-anagram/
Solution: https://github.com/adityasuman2025/CP/blob/master/JS/validAnagram.js
Valid Palindrome
https://leetcode.com/problems/valid-palindrome/
Solution: https://github.com/adityasuman2025/CP/blob/master/JS/validPalindrome.js
