Closures, prototype chain, event loop, async patterns, and engine internals: everything that separates JS experts from copy-pasters.
What is Hoisting In JavaScript
Hoisting is a JavaScript behavior where variable and function declarations are moved
("hoisted") to the top of their scope during compilation, before the code executes.
Variable declared with var and Normal function supports hoisting
let & const are hoisted but remain in the Temporal Dead Zone (TDZ) until their declaration is reached, unlike var which is initialized as undefined
console.log(name); // ✅ Undefined
var name = 'John';
console.log(name); // John
var name; // Hoisted to the top
console.log(name); // undefined
name = 'John';
console.log(name); // John
Temporal Dead Zone
The Temporal Dead Zone is the period between the start of a function or block execution and
the point where a let or const variable is initialized.
function printName() {
var name = 'sss';
console.log(x); // ❌ ReferenceError
let x = 10;
console.log(x); // ✅ 10
}
What is a Closure?
A closure is a function that remembers the variables from its lexical scope even after the
function has finished executing.
A closure is created when an inner function accesses variables from its outer function even
after the outer function has executed.
When JavaScript executes a function, it creates an execution context with:
Memory Allocation: The variable (count) is not garbage collected because the returned function still references it.
Closure Memory Management: Dereferencing to Enable Garbage Collection
Closures keep outer scope variables alive in memory. Setting the reference to null dereferences the closure, allowing the JavaScript engine's garbage collector to clean up the allocated memory.
function createCounter() {
let count = 0;
return {
increment: function () {
count++;
console.log(count);
},
decrement: function () {
count--;
console.log(count);
},
};
}
let counter = createCounter();
counter.increment(); // 1
counter.increment(); // 2
counter.decrement(); // 1
counter = null;
Lexical Scope
Lexical scope means that a function's scope is determined by its physical placement in the source code: not where it’s called from.
inner() is defined inside outer(), so it lexically gets access to name, even when it's called later from outside.
Think of lexical scope like the function carrying its own backpack: wherever it goes, it still has access to the stuff (variables) that were available at the place it was born.
function outer() {
let name = 'Chetan';
function inner() {
console.log(name); // Accessing variable from outer scope
}
return inner;
}
const fn = outer();
fn(); // Output: "Chetan"
anonymous Function
An anonymous function is a function that does not have a name. These functions are often used in callbacks,
event handlers and functional programming.
let greet = function () {
console.log('Hello, World!');
};
setTimeout(function () {
console.log('Executed after 2 seconds');
}, 2000);
document.getElementById('btn').addEventListener('click', function () {
console.log('Button Clicked!');
});
A Named Function is a kind of anonymous function that has an explicit name,
which can be used inside the function itself for recursion and debugging.
const greet = function sayHello() { console.log("Hello!"); }; greet(); //
Output: Hello! sayHello(); // ❌ ReferenceError: sayHello is not defined (name
is local)
const factorial = function fact(n) {
if (n <= 1) return 1;
return n * fact(n - 1);
// Using the function name inside itself (recursion)
};
console.log(factorial(5)); // Output: 120
What is a Pure Functions in JavaScript
A pure function is a function that:
Always produces the same output for the same input.
Has no side effects (does not modify external states).
A function where we don't use any global variable
function add(a, b) {
return a + b;
}
console.log(add(3, 5)); // 8
console.log(add(3, 5)); // 8 (Same input, same output)
const numbers = [1, 2, 3, 4];
const squaredNumbers = numbers.map((num) => num * num);
// ✅ Pure function
console.log(squaredNumbers); // [1, 4, 9, 16]
First-Class Functions in JavaScript
functions which can do following things
Assigned to variables (a function can be stored into a variable).
Passed as arguments to other functions (we can pass a function as an argument while executing another function).
Returned from functions (a function can return another function)
Stored in data structures like arrays or objects (functions can be stored as object properties or in arrays)
function multiplier(factor) {
return function (num) {
return num * factor;
};
}
const double = multiplier(2);
console.log(double(5));
// Output: 10
const operations = [
function (a, b) {
return a + b;
},
function (a, b) {
return a - b;
},
function (a, b) {
return a * b;
},
];
console.log(operations[0](4, 2)); //: 6
console.log(operations[1](4, 2)); //: 2
const mathOperations = {
add: function (a, b) {
return a + b;
},
multiply: function (a, b) {
return a * b;
},
};
console.log(mathOperations.add(3, 4)); // 7
console.log(mathOperations.multiply(3, 4)); // 12
Higher-Order Functions in JavaScript
A Higher-Order Function (HOF) is a function that takes another function as an argument or returns a function as its result.
This is possible because JavaScript has first-class functions, meaning functions can be treated like variables (assigned, passed, and returned)
JavaScript provides many built-in Higher-Order Functions in Array.prototype like:
.map()
.filter()
.reduce()
.forEach()
.sort()
.map()
.filter()
.reduce()
.forEach()
.sort()
function greet(name, callback) {
console.log('Hello, ' + name);
callback();
}
function sayGoodbye() {
console.log('Goodbye!');
}
greet('Alice', sayGoodbye);
function multiplier(factor) {
return function (num) {
return num * factor;
};
}
const double = multiplier(2); // Returns a function
console.log(double(5)); // Output: 10
What is a callback function in JavaScript
A callback function is a function passed as an argument to another function and executed later. This is useful for handling asynchronous operations, event handling, and higher-order functions.
A callback function is just a function that is passed as an argument to another function and gets executed after some operation completes.
whereas Promises give us a much cleaner and more structured way to deal with async operations, especially when chaining multiple tasks.
function greet(name, callback) {
console.log('Hello, ' + name);
callback(); // Execute the callback function
}
function sayGoodbye() {
console.log('Goodbye!');
}
greet('Alice', sayGoodbye);
A Self-Executing Function, also known as an Immediately Invoked function
, is a function that executes immediately after being defined.
(function () {
console.log('This function runs immediately!');
})();
(function (name) {
console.log('Hello, ' + name + '!');
})('Alice');
(() => {
console.log('IIFE using an arrow function!');
})();
Functional Programming in JavaScript
Functional Programming is a way we structure our code where you mostly construct and structure your code using functions. These functions take input which is called as
arguments then shows the output based on the inputs being taken which, given the same input always results in the same output.
✔ Functional Programming makes code predictable, reusable, and easier to test.
✔ Focuses on pure functions, immutability, and higher-order functions.
✔ Uses .map(), .filter(), .reduce() for clean and declarative code.
✔ Avoids side effects and global state changes.
call()
In JavaScript, functions are objects, and they have built-in methods like call(), apply(), and bind()
that help control how a function is executed and what value this refers to.
call() invokes a function immediately and allows passing arguments one by one.
apply() also invokes the function immediately, but arguments are passed as an array instead of individual values.
bind() does not call the function immediately. Instead, it returns a new function with this permanently set.But we can pass argument only not array and we can execute it in future
function welcomeUser(role, city) {
console.log(`Name is ${this.name},
and I am a ${role} & city is ${city}`);
}
const user = { name: 'Shyam' };
welcomeUser.call(user, 'developer', 'Pune');
function welcomeUser2(role, city) {
console.log(`Name is ${this.name},
and I am a ${role} & city is ${city}`);
}
const user2 = { name: 'Shyam' };
welcomeUser2.apply(user2, ['QA', 'Mumbai']);
function welcomeUser3(role, city) {
console.log(`Name is ${this.name},
and I am a ${role} & city is ${city}`);
}
const user3 = { name: 'Shyam' };
const newData = welcomeUser3.bind(user2);
newData('Developer', 'Thane');
Currying
Currying means changing a function that takes multiple arguments into a series of smaller functions that each take one argument at a time.
function addNum(num1) {
return function (num2) {
return num1 + num2;
};
}
console.log(addNum(4)(7)); //11
function addNum(num1) {
return function (num2) {
return function (num3) {
return num1 + num2 + num3;
};
};
}
console.log(addNum(2)(3)(4)); //9
function* getUniqueId(start = 1) {
let id = start;
while (true) {
yield `id-${id++}`;
}
}
const generateUnqId = getUniqueId(33);
console.log(generateUnqId.next().value); //id-33
console.log(generateUnqId.next().value); //id-34
console.log(generateUnqId.next().value); //id-35
Object Methods / Object Iteration
Object.keys() => get array of keys from Object
Object.values() => get array of value from Object
Object.entries() => get array of key value pair
Object.keys()
Object.values()
Object.entries()
You can use this to define dynamic properties or encapsulate logic
this is like new Field we have created and to read we use get method and to update we use set method
but if you want to update you need set method too
You can use this to define dynamic properties or encapsulate logic
this is like new Field we have created and to read we use get method and to update we use set method
These methods control the mutability of objects at different levels:
- Object.freeze(): Completely immutable. Properties cannot be added, modified, or deleted.
- Object.seal(): Properties can be modified, but cannot be added or deleted.
- Object.preventExtensions(): New properties cannot be added, but existing properties can be modified or deleted.
When you want prototype-based inheritance without class.
For creating lightweight objects that share behavior.
Prototype is a mechanism in JavaScript that allows objects to inherit properties and methods from another object.
Every JavaScript function automatically has a prototype property that allows for inheritance.
__proto__ vs prototype
prototype: A property of constructor functions used to define methods for objects.
__proto__: A reference to the prototype of an instance.
Concept
.prototype
__proto__
Belongs To
A function (constructor)
An object (any JS object)
Used For
Defining properties/methods for instances
Points to the prototype (used for lookup)
Type
Property of a function
Property of an object
Purpose
Blueprint for creating new objects
Links an object to its prototype chain
prototype: A property of constructor functions used to define methods for objects.
__proto__: A reference to the prototype of an instance.
function myCar(car) {
this.myCarName = car;
}
myCar.prototype.printName = function () {
console.log(`my car name is ${this.myCarName}`);
};
const tesla = new myCar('Tesla');
console.log(tesla.__proto__ == myCar.prototype); //true
console.log(myCar.prototype.constructor === myCar); //true
Adding Methods to Prototypes
Detailed explanation and practical code examples below.
function animal(name) {
this.animalName = name;
}
animal.prototype.speak = function () {
console.log(`${this.animalName} makes a sound`);
};
animal.prototype.eat = function (item) {
console.log(`${this.animalName} eats ${item}`);
};
const dog = new animal('Dog');
dog.eat('Chips');
dog.speak();
Prototype-Based Inheritance
Detailed explanation and practical code examples below.
function person(name) {
this.personName = name;
}
person.prototype.getPersonName = function () {
console.log(`Teacher Name is ${this.personName}`);
};
function teacher(name, subject) {
person.call(this, name);
this.subjectName = subject;
}
teacher.prototype = Object.create(person.prototype);
teacher.prototype.constructor = teacher;
teacher.prototype.teaches = function () {
console.log(`${this.personName} teaches ${this.subjectName}`);
};
const sahil = new teacher('Sahil', 'Angular');
sahil.getPersonName(); //Teacher Name is Sahil
sahil.teaches(); //Sahil teaches Angular
let student = { name:'Chetan', city:'Nagpur', getIntro : function() {
console.log(this.name + ' is From '+ this.city); } } let student2 = {
name:"Ankita" }; student2.__proto__ = student; =>here we are assigning prototype
of student object to student2 we can access getIntro function and city from
student obj and name will be taken from student2 obj
console.log(student2.getIntro());
Checking Prototypes
Detailed explanation and practical code examples below.
Create a new method which will work on array to get Last item
Array.prototype.getLastElement = function () {
return this[this.length - 1];
};
var cityList = ['Pune', 'Nagpur', 'Mumbai', 'Thane'];
console.log(cityList.getLastElement()); //Thane
Class-Based Inheritance
Detailed explanation and practical code examples below.
class Person {
constructor(name) {
this.personaName = name;
}
getPersonName() {
return `person Name is ${this.personaName}`;
}
}
class Employee extends Person {
constructor(personaName, id) {
super(personaName);
this.empId = id;
}
getEmployeeId() {
return `Person name is ${this.personaName} and his empId is ${this.empId}`;
}
}
const newEmployee = new Employee('Chetan', 'ERP-22334');
debugger;
console.log(newEmployee.getPersonName()); //person Name is Chetan
console.log(newEmployee.getEmployeeId()); //Person name is Chetan and his empId is ERP-22334
JavaScript Class
Classes in JavaScript are templates for creating objects. They encapsulate data with code to work on that data. Classes were introduced in ES6 and provide a more structured and cleaner syntax to work with objects and inheritance compared to traditional prototype-based inheritance.
Defining a Class
Creating Objects from a Class
Key Features of JavaScript Classes
this.surName and this.emailId attach properties to the object being created.
When you do new User(...), a fresh object is created, and this inside the constructor refers to that specific object.
Constructor Method: A special method that initializes object properties when an object is created.
Methods: Functions inside a class that define behaviors (e.g., greet())..
Encapsulation: Keeping properties and methods inside a class, allowing controlled access.
Inheritance: One class can inherit properties and methods from another using extends.
this.surName and this.emailId attach properties to the object being created.
When you do new User(...), a fresh object is created, and this inside the constructor refers to that specific object.
Constructor Method: A special method that initializes object properties when an object is created.
Methods: Functions inside a class that define behaviors (e.g., greet())..
Encapsulation: Keeping properties and methods inside a class, allowing controlled access.
Inheritance: One class can inherit properties and methods from another using extends.
const ramesh = new Student('Tendulkar', 'sachin@gmail.com'); //output Tendulkar sachin@gmail.com
Inheritance in JavaScript Class
Inheritance allows a class to extend another class, inheriting its properties and methods. This is achieved using the extends keyword
When child class has its own constructor it is mandatory to have super.
If you override a method but still want to run the parent’s version of it then also it is mandatory to have super.
class person {
constructor(surname, email) {
this.surName = surname;
this.emailId = email;
}
printDetail() {
console.log(this.surName + ' ' + this.emailId);
}
}
class Employee extends person {
constructor(surname, email, subName) {
super(surname, email);
this.subjectName = subName;
}
printSubName() {
console.log('Subject is ', this.subjectName);
}
}
const sachin = new Employee('Tendulkar', 'sachin@gmail.com', 'English');
sachin.printDetail();
sachin.printSubName();
class Product {
constructor(name) {
this.productName = name;
}
printProductName() {
console.log(`Product Name -${this.productName}`);
}
}
class ProductSku extends Product {
printProductInfo() {
const randomId = Math.floor(Math.random() * 10 + 1);
console.log(`Product Name is ${this.productName} and Id is ${randomId}`);
}
}
const newProduct = new ProductSku('Mobile');
newProduct.printProductName();
newProduct.printProductInfo();
Override Method by Inheritance in JavaScript Class
if you create a method in a parent class, you can totally override that method in a child class using inheritance via the extends keyword.
class EmployeeBasic {
constructor(name) {
this.empName = name;
}
printName() {
console.log(`Employee Name is ${this.empName}`);
}
}
class EmployeeDetails extends EmployeeBasic {
constructor(name, contactNo) {
super(name);
this.contactNo = contactNo;
}
printName() {
console.log(
`Employee Name is - ${this.empName} and Mobile No is:${this.contactNo}`,
);
}
}
const newEMp = new EmployeeDetails('Chetan', '9900990000');
newEMp.printName();
Private & Public Fields
Detailed explanation and practical code examples below.
class product {
#sku;
constructor(sku, name) {
this.#sku = sku;
this.productName = name;
}
printProductInfo() {
console.log(`Product SKU is ${this.#sku} and Name is ${this.productName}`);
}
getProductSku() {
return this.#sku;
}
}
const mobile = new product('mob-11', 'A2');
mobile.printProductInfo();
console.log(mobile.getProductSku());
//console.log(mobile.#sku)
Static methods
Static methods are defined on the class itself, not on instances of the class. They are called on the class, not on objects created from the class.
class MathOperation {
static add(num1, num2) {
return num1 + num2;
}
}
console.log(MathOperation.add(3, 7));
const math = new MathOperation();
//console.log(math.add(22,33)) //math.add is not a function
What are getters and setters in JavaScript classes?
Getters and setters allow you to define object accessors (computed properties). A getter method is used to get the value of a property, and a setter method is used to set the value of a property..
class Person {
constructor(name) {
this._name = name;
}
get name() {
return this._name;
}
set name(newName) {
this._name = newName;
}
}
const person = new Person('Alice');
console.log(person.name); // Output: Alice
person.name = 'Bob';
console.log(person.name); // Output: Bob
what's use of get and set
So yeah, get and set are optional, but they give you a clean, powerful, and controlled way to manage access to class data: especially useful in real-world, complex apps.
class ProductPrice {
constructor(name, price) {
this.name = name;
this.price = price;
}
get _price() {
return this.price;
}
set _price(value) {
debugger;
if (value != undefined && value != null && value != 0) {
this.price = value;
}
}
}
const mobile2 = new ProductPrice('Camred', 1200);
mobile2._price = 0;
debugger;
console.log(mobile2._price);
static means the property belongs to the class, not its instances.
console.log(m1.count); => undefined: static properties belong to the class and are not accessible on instances
console.log(Magic.count); 2 because we created two instances and each one increased the static count.
class Magic {
static count = 0;
constructor() {
Magic.count++;
}
}
const m1 = new Magic();
const m2 = new Magic();
console.log(m1.count);
console.log(Magic.count);
What is Async/Await in JavaScript?
async/await is a feature in JavaScript used to handle asynchronous operations more efficiently.
It is built on Promises and allows writing asynchronous code in a synchronous style, improving readability and maintainability.
fetch() returns a Promise => await waits for the response => response.json() returns another Promise, which is awaited. => Finally, the user data is logged.
Declaring a function as async ensures that it always returns a promise.
Even if you return a simple value, JavaScript wraps it in a Promise.
The await keyword pauses execution of an async function until the Promise is resolved.
It must be used inside an async function.
async function getSquare(num) {
return num * num;
}
const data = getSquare(3);
data.then((res) => {
console.log(res);
});
async function getUser() {
let response = await fetch('https://jsonplaceholder.typicode.com/users/1');
let user = await response.json();
console.log(user);
}
getUser();
What is Promise
A Promise in JavaScript is an asynchronous operation that may resolve (success) or reject (failure) in the future.
It helps avoid callback hell and makes async code more readable.
resolve(value): Marks the promise as successful. reject(error): Marks the promise as failed.
.then(successCallback): Runs on resolve()..catch(errorCallback): Runs on reject().
.finally(finalCallback): Runs after success/failure.
fetch() returns a Promise => await waits for the response => response.json() returns another Promise, which is awaited. => Finally, the user data is logged.
A Promise represents the eventual completion or failure of an asynchronous operation.
Has three states: Pending, Fulfilled (resolved), and Rejected.
.then() handles success, .catch() handles errors, and .finally() runs in either case.
Avoids callback hell through clean promise chaining.
async function getUser() {
let response = await fetch('https://jsonplaceholder.typicode.com/users/1');
let user = await response.json();
console.log(user);
}
getUser();
Chaining Promises
Each .then() returns a new Promise, allowing sequential execution.
Each .then() passes its return value to the next .then().
Each .then() returns a new Promise, allowing sequential execution.
Each .then() passes its return value to the next .then().
Detailed explanation and practical code examples below.
function delay(sec) {
return new Promise((suc, error) =>
setTimeout(() => error('api takes time to load'), sec),
);
}
Promise.race([fetch('https://jsonplaceholder.typicode.com/users'), delay(90)])
.then((res) => {
console.log(res);
})
.catch((error) => {
console.log('API call Canceled');
});
How is a Promise different from a callback
A callback is simply a function passed as an argument to another function, which is then invoked after some task is completed
A Promise is an object that represents the eventual completion or failure of an async operation, and its resulting value. It can have 3 states
pending
fulfilled
rejected
function test(callback) {
setTimeout(() => {
console.log('Test Set timeout Executed');
callback();
}, 2000);
}
test(function print() {
console.log('Call Back Got Executed');
});
function getApi(callback) {
fetch('https://jsonplaceholder.typicode.com/users')
.then((res) => res.json())
.then((res) => {
callback(res);
});
}
var printApiResult = function (data) {
console.log('API Result' + data.length);
};
getApi(printApiResult);
Variables declared with let and const are stored in the Temporal Dead Zone and
not added to window.
var declared variables are properties of the global object in global scope.
Feature
var
let
const
Scope
Function-scoped
Block-scoped
Block-scoped
Hoisting
Yes (initialized as undefined)
Yes (but in TDZ)
Yes (but in TDZ)
Re-declaration
✅ Allowed (in same scope)
❌ SyntaxError
❌ SyntaxError
Re-assignment
✅ Allowed
✅ Allowed
❌ Not Allowed (but object props can be mutated)
Temporal Dead Zone
❌ Not applicable
✅ Exists
✅ Exists
Variables declared with let and const are stored in the Temporal Dead Zone and
not added to window.
var declared variables are properties of the global object in global scope.
function printVal() {
console.log(x); //ReferenceError
let x = 10;
if (true) {
let x = 20;
console.log(x); //20
}
}
printVal();
function printVal2() { console.log(x);//ReferenceError: Cannot access 'x' before
initialization let x = 10; if (true) { let x = 20; console.log(x)//20 } }
printVal2()
function printVal2() {
let x = 10;
console.log(x); //10
if (true) {
let x = 20;
console.log(x); //20
}
}
printVal2();
const – Immutable Bindings, not immutable values
Means when we declare variable with const It Cannot be reassigned
But if its Array or Object we can change its Value but still can't be reassigned
Basically you can't use = operator again.
Means when we declare variable with const It Cannot be reassigned
But if its Array or Object we can change its Valye but still can't reassigned
normal function "this" refers to global object
in arrow func this refers to surrounding scope where it is created/ or
executed
Normal func can be invoked by new operator
Arrow fun can't be invoked with new
yes it's short to write but not just that so many difference are there
if single statement return is there no Need of return keyword
var name = 'Roy';
const person = {
name: 'Alice',
sayHello: function () {
console.log('Hello from', this.name);
},
sayHelloArrow: () => {
console.log('Hello from', this.name);
},
};
person.sayHello(); //Alice
person.sayHelloArrow(); //Roy
const user = {
age: 25,
showAgeNormal: function () {
setTimeout(function () {
console.log('Normal:', this.age);
}, 1000);
},
showAgeArrow: function () {
setTimeout(() => {
console.log('Arrow:', this.age);
}, 1000);
},
};
user.showAgeNormal(); // Normal: undefined ❌ (this is window)
user.showAgeArrow(); // Arrow: 25 ✅ (this is user)
function test() {
console.log(arguments);
}
test(1, 2, 3); //Works
function addNumbers(...args) {
let sum = 0;
for (let i = 0; i < args.length; i++) {
sum = sum + args[i];
}
return sum;
}
const result = addNumbers(2, 3, 4, 5);
console.log(result);
const result2 = addNumbers(3, 3, 5, 5);
console.log(result2);
const arrowTest = () => {
console.log(arguments); //arguments is not defined
};
arrowTest(1, 2, 3);
// Arrow functions do not have an 'arguments' object, but they CAN use rest parameters (...args)
function Product(name) {
this.productName = name;
console.log(this.productName);
}
new Product('Mobile');
const Print = () => {};
const text = new Print(); //Print is not a constructor
Template Literals
are string literals that allow embedded expressions, multi-line strings, string
interpolation, and even function-based tag processing.
They are enclosed by backticks (`) instead of single ' or double " quotes.
concatenation
string interpolation
multi-line
Function Calls Inside Interpolation
Tagged Template Literals
are string literals that allow embedded expressions, multi-line strings, string
interpolation, and even function-based tag processing.
They are enclosed by backticks (`) instead of single ' or double " quotes.
var firstName = 'Chetan';
var lastName = 'Jogi';
var fullName = firstName + ' ' + lastName;
var fullName2 = `${firstName} ${lastName}`;
var num1 = 12;
var num2 = 23;
var result = `Sum is ${num1 + num2}`;
var productDetails = `Mobile is a product
It has 2 Variants and
a discounted price`;
function printSquare(num) {
return num * num;
}
var squareStr = `Square of 3 is ${printSquare(3)}`;
function printStr(strings, ...values) {
return strings[0] + values[0] + strings[1];
}
var studName = 'Chetan';
var text = printStr`Hello ${studName}, Welcome`;
console.log(text);
Spread Operator
Spread Operator lets you Expands an iterable (like array, string, object) into
individual elements.
var state1City = ['Pune', 'Nagpur'];
var state2City = ['Mumbai', 'Thane'];
var state1Copy = [state1City];
var stateList = [...state1City, ...state2City];
var stateList2 = [...state1City, 'Jabalpur', ...state2City];
var stateList3 = [...state2City, 'Panji', 'Bhopal'];
Shallow Copy
Copies only the top-level properties of an object.
If the object contains nested objects, it copies references, not actual
copies.
Changes to nested data affect both the original and the copied object.
Deep Copy
Copies everything recursively, including all nested objects and arrays.
The copy is completely independent of the original.
Changes in the copy do not affect the original and vice versa.
Copies only the top-level properties of an object.
If the object contains nested objects, it copies references, not actual
copies.
Changes to nested data affect both the original and the copied object.
Copies everything recursively, including all nested objects and arrays.
The copy is completely independent of the original.
Changes in the copy do not affect the original and vice versa.
const original = {
name: 'Alice',
address: {
city: 'Delhi',
},
};
const deepCopy = structuredClone(original);
deepCopy.address.city = 'Mumbai';
console.log(original.address.city); // 👉 'Delhi' (original is NOT affected)
Rest Operator
Collects multiple elements into a single array or object.
rest should be last only
function addAnyNo(...nums) {
let sum = 0;
for (let index = 0; index < nums.length; index++) {
sum = sum + nums[index];
}
return sum;
}
console.log(addAnyNo(2, 4, 5));
console.log(addAnyNo(1, 3, 6, 7, 8, 9));
Object destructuring is a shorthand syntax in JavaScript to extract properties from
objects into variables.
variable name should be same as key name then only you will be able to
extract value
If you need some different variable name instead of key name we do like below
ie: a
Array Destructuring
var product = { pName: 'Laptop', price: 55000 };
var { pName, price } = product;
console.log(pName); //Laptop
console.log(price); //55000
var product = { pName: 'Laptop', price: 55000 };
var { pName: prodNAme } = product;
console.log(prodNAme); //Laptop
var studentList = ['Chetan', 'Rahul'];
var [stud1, stud2] = studentList;
console.log(stud1); //Chetan
console.log(stud2); //Rahul
The execution context in JavaScript refers to the environment in which JavaScript code is executed. It determines what variables, functions, and objects are accessible at any given point in the code.
Created when the JavaScript script starts executing.
It represents the global scope window
Variables and functions declared in the global scope belong here.
Created whenever a function is called / Executing.
Each function has its own execution context.
Contains its own variables, function-specific scope, and arguments.
Phases of Execution Context
Creation Phase (Memory Allocation)
Memory Allocation for variables and functions.
Variables are hoisted with undefined as their initial value.
Function declarations are hoisted with their full definition.
this is determined based on the execution context (global or function).
Execution Phase
The code is executed line by line.
Variables are assigned values.
Functions are executed when invoked.
Memory Allocation for variables and functions.
Variables are hoisted with undefined as their initial value.
Function declarations are hoisted with their full definition.
this is determined based on the execution context (global or function).
The code is executed line by line.
Variables are assigned values.
Functions are executed when invoked.
Call Stack & EventLoop
JavaScript is single-threaded, meaning it can execute one task at a time. It manages code execution using the Call Stack and handles asynchronous operations with the Event Loop.
When a function is invoked, JavaScript creates a Function Execution Context (FEC) and pushes it onto the Call Stack.
The function executes.
Once execution is complete, the function's execution context is removed (popped) from the stack.
JavaScript continues executing the next function on top of the stack.
The Call Stack executes all synchronous code first.
in case of asynchronous operation (setTimeout,setInterval,Promises,Fetch) it is sent to Web APIs For Executions
Once completed, the callback function is moved to the Callback Queue (or Microtask Queue in case of Promises).
The Event Loop checks if the Call Stack is empty. If empty, the Event Loop pushes callbacks from the queue into the stack for execution.
Microtask Queue: Contains tasks from Promises
Callback Queue (Macrotask Queue): Contains tasks from setTimeout, setInterval, setImmediate, and event listeners.
Microtasks are executed before Macrotasks. Means (Promises will execute before setTimeout)
start end Outside Promise setTimeout Inside Promise
Lexical Scope in JavaScript
Lexical Scope refers to the way
JavaScript determines the scope of variables based on their position in the code at the time of writing
Lexical Scope allows functions to access variables from their parent function.
Helps in creating private variables (encapsulation).
function createCounter() {
let count = 0; // Private variable (Lexical Scope)
return {
increment: function () {
count++;
console.log(`Count: ${count}`);
},
decrement: function () {
count--;
console.log(`Count: ${count}`);
},
getCount: function () {
return count;
},
};
}
let counter = createCounter();
counter.increment(); // Count: 1
counter.increment(); // Count: 2
counter.decrement(); // Count: 1
console.log(counter.getCount()); // 1
console.log(counter.count); // ❌ Undefined (count is private)
function setupButton() {
let clickCount = 0; // Private variable (Lexical Scope)
document.getElementById('myButton').addEventListener('click', function () {
clickCount++;
console.log(`Button clicked ${clickCount} times`);
});
}
setupButton();
JavaScript Execution in the Browser
When a browser loads a web page, the HTML parser builds the DOM tree until it encounters a script tag. The JavaScript engine (like V8) parses code into an Abstract Syntax Tree (AST), compiles it via JIT into bytecode and machine code, and executes it on the single main thread while delegating asynchronous tasks (DOM events, timers, fetch) to the browser's Web APIs.
What is a JavaScript Engine?
A JavaScript Engine is a program that executes JavaScript code.
It consists of the following components:
Parser → Converts JS code into an Abstract Syntax Tree (AST)
Interpreter → Executes the code line-by-line (or via bytecode)
Compiler (JIT) → Converts frequently executed "hot code" into optimized
machine code
Garbage Collector → Automatically manages memory by cleaning unused
objects
Runtime → Provides environment APIs (like DOM, timers, Web APIs): not
technically part of the engine
Popular JavaScript Engines:
V8 – Used in Chrome & Node.js (most widely used)
SpiderMonkey – Firefox
JavaScriptCore (Nitro) – Safari
Chakra (Legacy) – Old Microsoft Edge, now replaced with V8
Parser → Converts JS code into an Abstract Syntax Tree (AST)
Interpreter → Executes the code line-by-line (or via bytecode)
Garbage Collector → Automatically manages memory by cleaning unused
objects
Runtime → Provides environment APIs (like DOM, timers, Web APIs): not
technically part of the engine
V8 – Used in Chrome & Node.js (most widely used)
SpiderMonkey – Firefox
JavaScriptCore (Nitro) – Safari
Chakra (Legacy) – Old Microsoft Edge, now replaced with V8
JavaScript Execution in the Browser
An Abstract Syntax Tree (AST) is a tree representation of the structure of source code.
It is generated by parsing the code and is used by compilers and interpreters (like
JavaScript engines) to understand and execute the code.
Example: let x = 5; → tokens: let, x, =, 5, ;
Program
├── VariableDeclaration (let)
│ ├── Identifier (x)
│ ├── AssignmentExpression (=)
│ ├── BinaryExpression (+)
│ ├── NumericLiteral (5)
│ ├── NumericLiteral (2)
V8 in Chrome
SpiderMonkey in Firefox
Microsoft Edge Chakra (Old) → Now uses V8
Apple Safari JavaScriptCore (Nitro)
Parsing : Converting Js code into Abstract Syntax Tree (AST).
Compilation
Execution
JavaScript Execution Process
Step 1: Parsing (Converting Code into AST)
Step 2: Compilation (JIT Compilation):
Interpreter → Quickly converts AST into bytecode.
Compiler → Optimizes bytecode into high-performance machine code.
Execution:
The optimized machine code runs directly on the CPU.
The Garbage Collector (GC) manages memory by cleaning up unused variables.
Step 1: Parsing (Converting Code into AST)
Step 2: Compilation (JIT Compilation):
Interpreter → Quickly converts AST into bytecode.
Compiler → Optimizes bytecode into high-performance machine code.
Interpreter → Quickly converts AST into bytecode.
Compiler → Optimizes bytecode into high-performance machine code.
Execution:
The optimized machine code runs directly on the CPU.
The Garbage Collector (GC) manages memory by cleaning up unused variables.
The optimized machine code runs directly on the CPU.
The Garbage Collector (GC) manages memory by cleaning up unused variables.
Garbage Collection in JavaScript
Garbage collection (GC) in JavaScript is the process of automatically freeing up memory by
removing objects that are no longer needed.
When You Create A variable in JavaScript it Allocates memory like below
JavaScript engines use the Mark-and-Sweep Algorithm for GC.
Stack Memory → Stores primitive values (number, string, boolean, etc.) and function
calls.
Heap Memory → Stores objects ({}), arrays ([]), and functions.
Mark Phase (Finding Reachable Objects)
The engine marks all active (reachable) objects.
Reachable objects are those still accessible from the global scope or function
scopes.
function example() {
let obj = { x: 42 }; // Reachable
} // obj becomes unreachable when function ends
The engine marks all active (reachable) objects.
Reachable objects are those still accessible from the global scope or function
scopes.
function example() {
let obj = { x: 42 }; // Reachable
} // obj becomes unreachable when function ends
Sweep Phase (Removing Unreachable Objects)
The engine removes unmarked (unreachable) objects from memory.
The engine removes unmarked (unreachable) objects from memory.
function example() {
let obj = { x: 42 }; // Reachable
} // obj becomes unreachable when function ends
Stack vs Heap
Primitive types are stored in the stack because they are small and fixed in size, allowing
fast access and cleanup. Objects and arrays are stored in the heap because they can grow
dynamically and need flexible memory allocation. The stack stores a reference to the object
in the heap, not the object itself.
They're simple, fixed-size, and immutable
Can be stored and accessed quickly
Automatically removed when the function context ends
They can have dynamic size and structure
Not fixed in memory
Passed by reference, not by value
Environments where JavaScript is Being Used
There are wide areas where we can Use JavaScript
Web Development : Websites, SPA
Server-Side Development: NodeJs, Express.Js,
Mobile App Development: React Native , Ionic ,
Desktop Application Development: Electron.js (Slack, VS Code)
Game Development:
Internet of Things (IoT):Johnny-Five.js, Raspberry Pi & Node.js
Machine Learning & AI: TensorFlow.js,Brain.js, D3.js
Automation & Scripting : Puppeteer,Selenium (with JavaScript) ,
Web Development : Websites, SPA
Server-Side Development: NodeJs, Express.Js,
Mobile App Development: React Native , Ionic ,
Desktop Application Development: Electron.js (Slack, VS Cod)
Game Development:
Internet of Things (IoT):Johnny-Five.js, Raspberry Pi & Node.js
ECMAScript (ES) is the official specification for JavaScript.
It defines:
Version Till now
ECMAScript Versions Timeline:
- ES1 (1997): First edition
- ES3 (1999): Regular expressions, try/catch
- ES5 (2009): Strict mode, JSON, Array methods
- ES6/ES2015: let/const, arrow functions, classes, Promises, modules
- ES2016: Array.includes(), exponentiation (**)
- ES2017: async/await, Object.entries(), Object.values()
- ES2018: Rest/Spread for objects, Promise.finally()
- ES2019: Array.flat(), flatMap(), Object.fromEntries()
- ES2020: Optional chaining (?.), Nullish coalescing (??), BigInt
- ES2021: String.replaceAll(), Promise.any()
- ES2022: Top-level await, private class fields (#), at()
- ES2023: toSorted(), toReversed(), findLast()
- ES2024: Promise.withResolvers(), Object.groupBy()
Syntax & Grammar
Data Types & Objects
Functions & Scope
Asynchronous Programming
Modules & Classes
JavaScript Execution Context
Detailed explanation and practical code examples below.
Fibonacci Series
The Fibonacci sequence is a series where each number is the sum of the two preceding ones: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34... The loop iteratively updates two tracker variables to generate the sequence.
let n1 = 0,
n2 = 1;
for (let index = 0; index < 10; index++) {
let temp = 0;
console.log(n1);
temp = n1 + n2;
n1 = n2;
n2 = temp;
}
Remove Duplicates From Array
using filter
using Set
without using 2nd Array Remove duplicate from original array
get array of element which are not repeated
remove Duplicate from array of Object
remove Duplicate from array of Object with more than 2 fields
Build an element frequency hash map by iterating through the array. For each element, either initialize its count to 1 or increment its existing count: (obj[item] || 0) + 1.
A palindrome reads the same backwards as forwards (e.g. 'naman', 'racecar'). The easiest approach reverses the string by splitting into characters, reversing the array, joining, and comparing with the original.
Count the frequency of each character using an object hash map in the first pass, then iterate through the string in the second pass to return the first character with a frequency count of 1.
Two strings are anagrams if they contain the exact same characters with the same frequencies. Sorting both strings alphabetically and checking equality confirms if they are permutations.
var firstWord = 'listen';
var secondWord = 'silent';
var firststrArr = firstWord.split('').sort().join();
var secondStrArr = secondWord.split('').sort().join();
console.log(firststrArr === secodnStrArru);
Factorial of a Number
The factorial of n (n!) is the product of all positive integers from 1 to n (e.g. 5! = 5 × 4 × 3 × 2 × 1 = 120). It can be computed iteratively with a for loop or recursively.
function getFact(num) {
let result = 1;
debugger;
for (var index = 2; index <= num; index++) {
result = result * index;
}
return result;
}
console.log(getFact(5));
Longest Word in a String
to get single large word
to get multple large word
var originalString = 'The quick brown fox jumped';
var strArr = originalString.split(' ');
var longestWord = '';
for (let word of strArr) {
if (word.length > longestWord.length) {
longestWord = word;
}
}
console.log(longestWord);
let originalString = 'The quick brown fox jumped and jumped';
let strArr = originalString.split(' ');
let longestWordLength = 0;
let longedsWordArray = [];
for (let word of strArr) {
if (word.length > longestWordLength) {
longestWordLength = word.length;
longedsWordArray = [word];
} else if (word.length == longestWordLength) {
longedsWordArray.push(word);
}
}
console.log(longedsWordArray);
Find Missing Number in Array
Iterate through the expected integer range (1 to n) and check if each value exists in the input array. Any missing integer is appended to the result array.
function findMissingNumber(arr) {
const n = arr.length + 1;
let missingElements = [];
for (let i = 1; i <= n; i++) {
if (!arr.includes(i)) {
missingElements.push(i);
}
}
return missingElements;
}
console.log(findMissingNumber([1, 2, 4, 5, 7]));
Data Transformation: Flatten Nested Objects & Group by Value
Common interview data transformations: recursively traversing tree structures to extract string values, grouping object keys by their values into arrays, and aggregating properties using Array.reduce().
let group = [
{ name: 'a', children: [{ name: 'b', children: [{ name: 'c' }] }] },
];
//O/p: a b c
var valueArray = [];
getValues(group);
function getValues(value) {
if (typeof value == 'string') {
valueArray.push(value);
} else if (Array.isArray(value)) {
value.forEach((element) => {
getValues(element);
});
} else if (typeof value == 'object') {
const values = Object.values(value);
getValues(values);
}
}
console.log(valueArray);
const newObj = {};
for (const person of people) {
for (const skill of person.skills) {
if (!newObj[skill]) {
newObj[skill] = [];
}
newObj[skill].push(person.name);
}
}
console.log(newObj);
Essential revision notes covering Event Loop phases (Microtasks vs Macrotasks), Execution Context with Call Stack & Web APIs, Map/Set/WeakMap/WeakSet differences, and takeUntil RxJS memory management.
Arrow functions do not bind their own `this` or prototype; regular functions have prototype for inheritance.
Microtask Queue (Promises, process.nextTick) has higher execution priority over Macrotask Queue (setTimeout, setInterval).
Execution Context contains Variable Environment and Lexical Environment connected to the Call Stack.
WeakMap and WeakSet hold weak references to objects, allowing garbage collection when no other references exist.
Tree-shaking in Angular removes unused code paths during Ahead-Of-Time (AOT) production compilation.
function Teacher(name) {
this.teacherName = name;
}
// Works with regular function:
Teacher.prototype.teach = function (subjectName) {
console.log(`${this.teacherName} teaches ${subjectName}`);
};
// Will NOT work as expected with arrow function because 'this' is lexically bound:
// Teacher.prototype.teach = (subjectName) => { console.log(this.teacherName); };