Learning Partner

JavaScript Questions

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.

function outer() {
  let count = 0;
  function inner() {
    count++;
    console.log(count);
  }
  return inner;
}
const counter = outer();
counter(); // Output: 1
counter(); // Output: 2
counter(); // Output: 3

How Closures Work Internally

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.

  • Local Variables
  • Scope Chain (Lexical Environment)
  • Function Reference (if returned)
function createCounter() {
  let count = 0;
  return {
    increment: function () {
      count++;
      console.log(count);
    },
    decrement: function () {
      count--;
      console.log(count);
    },
  };
}
const counter = createCounter();
const counter2 = createCounter();
counter.increment(); // 1
counter.increment(); // 2
counter.decrement(); // 1
counter2.increment(); //1

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!');
});
(function () {
  console.log('This runs immediately!');
})();

What is a Named Function Expression?

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)
const greet = function (name) {
  return `Hello, ${name}!`;
};
console.log(greet('Alice'));
function process(callback) {
  console.log('Processing...');
  callback();
}
process(function () {
  console.log('Callback executed!');
});
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);
console.log('Start');

setTimeout(() => {
  console.log('Executed after 2 seconds');
}, 2000);

console.log('End');

Self-Executing Function

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
var addTwoNo = (num1) => (num2) => num1 + num2;
console.log(addTwoNo(4)(7)); //11
function printLog(type) {
  return function (message) {
    console.log(`${type} - ${message}`);
  };
}
const infoLog = printLog('Info');
infoLog('User Logged'); //Info - User Logged
infoLog('Profile Fetched'); //Info - Profile Fetched
const errorLog = printLog('Error');
errorLog('API Error'); //Error - API Error
const log = (type) => (message) => console.log(`${type} - ${message}`);
const infoLog = log('info');
const errorLog = log('error');
infoLog('User logged in'); // INFO - User logged in
errorLog('Server crashed'); // ERROR - Server crashed

Generator function

A generator is a special type of function that can be paused and resumed.

  • Declared using function* syntax
  • Uses yield keyword to pause execution
  • Returns a special object , with .next() method
  • return finishes the generator
function* printNum() {
  console.log(1);
  yield 1;
  console.log(2);
  yield 2;
  console.log(3);
}
const genFun = printNum();
console.log(genFun.next()); //{value: 1, done: false}
console.log(genFun.next()); //{value: 2, done: false}
console.log(genFun.next()); //{value: undefined, done: true}
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()

  • Object.keys() => get array of keys from Object
  • Object.values() => get array of value from Object
  • Object.entries() => get array of key value pair
var student = {
  name: 'Chetan',
  id: 312,
  city: 'Nagpur',
};
console.log(Object.keys(student));
// ['name', 'id', 'city']
var student = {
  name: 'Chetan',
  id: 312,
  city: 'Nagpur',
};
console.log(Object.values(student));
// ['Chetan', 312, 'Nagpur']
var student = {
  name: 'Chetan',
  id: 312,
  city: 'Nagpur',
};
console.log(Object.entries(student));
//0 (2) ['name', 'Chetan']
//1 (2) ['id', 312]
//2 (2) ['city', 'Nagpur']

Object.defineProperty

You can define meta-properties like: writable,enumerable,configurable

const empObj = {};
Object.defineProperty(empObj, 'empId', {
  value: 123,
  writable: false,
  enumerable: false,
  configurable: true,
});
empObj.empId = 23;
console.log(empObj.empId); //123
const empObj = {};
Object.defineProperty(empObj, 'empId', {
  value: 123,
  writable: true,
  enumerable: false,
  configurable: true,
});
empObj.empId = 23;
console.log(empObj.empId); //23

Getters and Setters

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
  • but if you want to update you need set method too
const employee = {
  name: 'Chetan',
  empId: 231,
  code: 'ER',
  get emp_id() {
    return `${this.code}-${this.empId}`;
  },
  set emp_id(param) {
    [this.code, this.empId] = param.split(' ');
  },
};

console.log(employee.emp_id); //ER-231
employee.emp_id = 'ER 432';
console.log(employee.emp_id); //ER-432
const product = {
  basePrice: 100,
  taxRate: 0.18,
  get finalPrice() {
    return this.basePrice + this.basePrice * this.taxRate;
  },
  set finalPrice(newPriceWithTax) {
    this.basePrice = newPriceWithTax / (1 + this.taxRate);
  },
};
console.log(product.finalPrice); // 118
product.finalPrice = 236;
console.log(product.basePrice); // 200

Object.create

When you want prototype-based inheritance without class. For creating lightweight objects that share behavior.

  • Object.freeze(): Completely immutable (no additions, modifications, or deletions)
  • Object.seal(): Existing properties can be modified, but no additions or deletions
  • Object.preventExtensions(): No new properties can be added, but deletions and modifications are allowed
const player = {
  name: 'sachin',
  plays: 'Cricket',
};

const player2 = Object.create(player);

console.log(player2.name); //sachin
const baseForm = {
  isValid: false,
  validate() {
    debugger;
    console.log('Validating form...');
  },
};

const loginForm = Object.create(baseForm);
loginForm.fields = ['email', 'password'];
loginForm.submit = function () {
  this.validate();
  console.log('Submitting login form...');
};

loginForm.submit();

Object.freeze, Object.seal, Object.preventExtensions

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.
const employeeData = {
  name: 'chetan',
};
Object.freeze(employeeData);

employeeData.name = 'Rahul';
console.log(employeeData); //{name: 'chetan'}
delete employeeData.name;
console.log(employeeData); //{name: 'chetan'}
employeeData.city = 'Pune';
console.log(employeeData); //{name: 'chetan'}

Object.defineProperty(employeeData, 'email', {
  configurable: false,
  value: 'chetan@gmail.com',
  writable: true,
}); //Cannot define property email

console.log(employeeData); //{name: 'chetan'}
const studentData = {
  name: 'Ankit',
};

Object.seal(studentData);

studentData.name = 'Rahul';
console.log(studentData.name); //Rahul
studentData.city = 'Pune';
console.log(studentData); //{name: 'Rahul'}
delete studentData.name;
console.log(studentData); //{name: 'Rahul'}
const productData = {
  name: 'Mobile',
  price: 1200,
};
Object.preventExtensions(productData);
productData.name = 'Camera';
console.log(productData); //{name: 'Camera',price:1200}
delete productData.price;
console.log(productData); //{name: 'Camera'}
productData.discount = 14500;
console.log(productData); //{name: 'Camera'}

What is Prototype in JavaScript?

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.

console.log(sahil instanceof teacher); // true
console.log(sahil instanceof person); // true (Inherited)
console.log(person.prototype.isPrototypeOf(sahil)); // true

How to Add Custom Methods to Existing Prototypes

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.
class Student {
  constructor(surname, email) {
    this.surName = surname;
    this.emailId = email;
  }
  printDetail() {
    console.log(this.surName + ' ' + this.emailId);
  }
}
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);
class ProductPrice {
  constructor(name, price) {
    this.name = name;
    this.price = price;
  }

  get getTaxablePrice() {
    const tax = 12.5;
    return (this.price * tax) / 100;
  }
}
const mobile2 = new ProductPrice('Camred', 1200);
console.log(mobile2.getTaxablePrice);

static properties in classes

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.
let newPromiseResult = new Promise((resolve, reject) => {
  const isActive = false;
  if (isActive) {
    resolve(true);
  } else {
    reject(false);
  }
});
newPromiseResult
  .then((success) => {
    console.log('Success', success);
  })
  .catch((error) => {
    console.log('error', error);
  })
  .finally(() => {
    console.log('Finally Block');
  });
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().
new Promise((success, error) => {
  setTimeout(() => success(5), 2000);
})
  .then((res) => {
    return res * 2;
  })
  .then((res) => {
    return res * 3;
  })
  .then((res) => {
    console.log('res', res);
  });
function login(uName, pwd) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (uName == 'admin' && pwd == '1122') {
        resolve({ userId: 121 });
      } else {
        reject('Wrong Credential');
      }
    }, 2000);
  });
}
function getUserProfile(userId) {
  return new Promise((resolve, reject) => {
    setTimeout(() =>
      resolve({ uName: 'admin', city: 'Pune', email: 'admin@gmail.com' }),
    );
  });
}
login('admin', '1122')
  .then((result) => {
    getUserProfile(result).then((profile) => {
      console.log('User Found', profile);
    });
  })
  .catch((error) => {
    console.log(error);
  });

Parallel Execution (Promise.all())

When multiple async tasks can be executed simultaneously, use Promise.all().

  • All promises resolve together → Faster execution.
  • If any promise fails, the whole Promise.all() fails!
const promise1 = new Promise((result) => setTimeout(() => result(3)));
const promise2 = new Promise((result) => setTimeout(() => result(5)));
const promise3 = new Promise((result) => setTimeout(() => result(8)));

Promise.all([promise1, promise2, promise3]).then((res) => {
  console.log(res[0]);
  console.log(res[1]);
  console.log(res[2]);
});

Promise.allSettled()

Even if one promise fails, Promise.allSettled() ensures all results are received.

const prs1 = new Promise((result) => setTimeout(() => result(3)));
const prs2 = new Promise((result, reject) => setTimeout(() => reject('Error')));
const prs3 = new Promise((result) => setTimeout(() => result(8)));

Promise.allSettled([prs1, prs2, prs3]).then((res) => {
  console.log(res);
});
0: { status: 'fulfilled'; value: 3 };
1: { status: 'rejected'; reason: 'Error' };
2: { status: 'fulfilled'; value: 8 };
}

Promise.race()

Promise.race() resolves or rejects as soon as the first promise in the iterable settles (either resolves or rejects).

const timer1 = new Promise((res) => setTimeout(() => res(5000), 5000));
const timer2 = new Promise((res) => setTimeout(() => res(3000), 3000));
const timer3 = new Promise((res) => setTimeout(() => res(6000), 6000));
Promise.race([timer1, timer2, timer3]).then((res) => {
  console.log(res);
});
3000;

Cancels request Just Like switchMap from rxjs

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);
function getApi() {
  return fetch('https://jsonplaceholder.typicode.com/users').then((res) =>
    res.json(),
  );
}
getApi().then((res) => {
  console.log('API Result' + res.length);
});

What happens if a Promise is neither resolved nor rejected?

If a Promise is neither resolved nor rejected, it stays in the pending state forever.

const stuckPromise = new Promise((resolve, reject) => {});
stuckPromise
  .then((res) => {
    console.log('success');
  })
  .catch((error) => {
    console.log('error');
  });

Var - Let - Const

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
  • Basically you can't use = operator again.
const productName = 'Mobile';
productName = 'asas';
console.log(productName);
//Assignment to constant variable.
const product = {
  productName: 'Mobile',
};
product.productName = 'Camera'; //Allowed
product = {
  productName: 'Camera',
};
//Assignment to constant variable.
const cityList = ['Pune', 'Nagpur'];
cityList.push('Mumbai'); //allowed
cityList = ['Jaipur', 'Jabalpur'];
//Assignment to constant variable.

Arrow Functions

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
  • Doesn't support hoisting
const person = {
  name: 'Alice',
  sayHello: function () {
    console.log('Hello from', this.name);
  },
  sayHelloArrow: () => {
    console.log('Hello from', this.name);
  },
};
person.sayHello(); //Alice
person.sayHelloArrow(); //undefined
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'];
function addThreeNo(n1, n2, n3) {
  return n1 + n2 + n3;
}
const noList = [2, 2, 3];
console.log(addThreeNo(...noList));
const student = {
  name: 'Chetan',
  github: 'voidChetan',
};
const studAddress = {
  city: 'Nagpur',
  state: 'MAHA',
};
const copyStud = { ...student };

const studDetails = { ...student, ...studAddress };
const studFullDetails = { ...student, studId: 232, ...studAddress };
var strArray = [...myStr];
console.log(strArray);
//['C', 'h', 'e', 't', 'a', 'n']
var strObj2 = { ...myStr };
console.log(strObj2);
//{0: 'C', 1: 'h', 2: 'e', 3: 't', 4: 'a', 5: 'n'}

difference between shallow copy and deep copy

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 shallowCopy = { ...original };
shallowCopy.address.city = 'Mumbai';
console.log(original.address.city); // 👉 'Mumbai' (original is affected)
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));
function printDetails(firstParam, ...restParam) {
  console.log(firstParam); //AAA'
  console.log(restParam); //['BBB','CCC','DDD']
}
printDetails('AAA', 'BBB', 'CCC', 'DDD');
const [firstState, ...restState] = ['MAHA', 'Goa', 'Punjab', 'Gujrat'];

console.log(firstState); //MAHA
console.log(restState); //['Goa', 'Punjab', 'Gujrat']
const { empId, ...restObj } = {
  name: 'chetan',
  empId: 'ER-223',
  tech: 'React',
  email: 'che23@gmail.com',
};
console.log(empId); //ER-223
console.log(restObj); //{name: 'chetan', tech: 'React', email: 'che23@gmail.com'}

Object Destructuring

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
const empList = [
  { empId: 121, name: 'QQQQ' },
  { empId: 122, name: 'WWWW' },
];
const [{ empId: employee_id }] = empList;
console.log(employee_id); //121

JavaScript Execution Context

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)
console.log('start');
setTimeout(() => {
  console.log('setTimeout');
}, 0);
Promise.resolve().then(() => {
  console.log('Promise');
});
console.log('end');
start;
end;
Promise;
setTimeout;
console.log('start');
setTimeout(() => {
  console.log('setTimeout');
  Promise.resolve().then(() => {
    console.log('Inside Promise');
  });
}, 0);
Promise.resolve().then(() => {
  console.log('Outside Promise');
});
console.log('end');
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)
  • 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
  • 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
  • Machine Learning & AI: TensorFlow.js,Brain.js, D3.js
  • Automation & Scripting : Puppeteer,Selenium (with JavaScript) ,

What is ECMAScript

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

const arr = [1, 2, 3, 2, 4, 1];
const newArr = arr.filter((item, index) => arr.indexOf(item) === index);
console.log(newArr);
const arr = [1, 2, 3, 2, 4, 1];
const unique = [...new Set(arr)];
console.log(unique);
const arr = [1, 2, 3, 2, 4, 1, 3, 6, 7, 8, 6];
for (let i = 0; i <= arr.length; i++) {
  for (let j = i + 1; j < arr.length; j++) {
    if (arr[i] == arr[j]) {
      arr.splice(j, 1);
    }
  }
}
console.log(arr);
//[1, 2, 3, 4, 6, 7, 8]
const arr = [1, 2, 3, 2, 4, 1, 3, 6, 7, 8, 6];

const newArr = arr.filter((item) => arr.indexOf(item) == arr.lastIndexOf(item));

console.log(newArr);
const arr = [1, 2, 3, 2, 4, 1, 3, 6, 7, 8, 6];
const eleCountObj = {};
arr.forEach((element) => {
  eleCountObj[element] = (eleCountObj[element] || 0) + 1;
});
const newArray = arr.filter((item) => eleCountObj[item] == 1);
console.log(newArray);
var list = [
  { name: 'chetan', id: 111, city: 'Mumbai' },
  { name: 'Rahul', id: 112, city: 'pune' },
  { name: 'chetan', id: 113, city: 'Mumbai' },
  { name: 'Aniket', id: 114, city: 'Nagpur' },
];
var newArr = list.filter(
  (item, index) => list.findIndex((m) => m.name == item.name) == index,
);
or;
var newArr = list.filter((item, index) => {
  return list.findIndex((m) => m.name == item.name) == index;
});

console.log(newArr);
var list = [
  { name: 'chetan', id: 111, city: 'Mumbai' },
  { name: 'Rahul', id: 112, city: 'pune' },
  { name: 'chetan', id: 113, city: 'Mumbai' },
  { name: 'Aniket', id: 114, city: 'Nagpur' },
];

var uniqueFields = ['name', 'city'];

const newArr = list.filter((item, index, self) => {
  return (
    index ===
    self.findIndex((otherItem) => {
      return uniqueFields.every((field) => item[field] === otherItem[field]);
    })
  );
});

console.log(newArr);

Get Frequency Count of Array Elements

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.

const arr1 = [1, 2, 3, 2, 4, 1];
const obj = {};
for (let index = 0; index < arr1.length; index++) {
  obj[arr1[index]] = (obj[arr1[index]] || 0) + 1;
}
console.log(obj);
const arr1 = [1, 2, 3, 2, 4, 1];
const obj = {};
arr1.forEach((element) => {
  obj[element] = (obj[element] || 0) + 1;
});
console.log(obj);

Check if a String is a Palindrome

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.

const originalStr = 'naman';
const strArra = originalStr.split('');
const revArr = strArra.reverse();
const reveStr = revArr.join('');
console.log(reveStr);
const newStr = originalStr.split('').reverse().join('');
console.log(newStr);

Find the First Non-Repeating Character

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.

const str = 'aabccdb';
const dupliCountObj = {};
str.split('').forEach((element) => {
  dupliCountObj[element] = (dupliCountObj[element] || 0) + 1;
});
let firstEle = '';
Object.keys(dupliCountObj).forEach((ele) => {
  if (dupliCountObj[ele] == 1 && firstEle == '') {
    firstEle = ele;
  }
});
console.log(firstEle);
const str = 'aabccdb';
const dupliCountObj = {};
str.split('').forEach((element) => {
  dupliCountObj[element] = (dupliCountObj[element] || 0) + 1;
});
let firstEle = '';

for (let char of str) {
  debugger;
  if (dupliCountObj[char] == 1) {
    firstEle = char;
    break;
  }
}
console.log(firstEle);

Check if Two Strings are Anagrams (Permutations)

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);
let obj = { a: 10, b: 10, c: 30, d: 40 };
//{
// 10: ['a', 'b'],
// 30: ['c'],
// 40: ['d']
// }
const values = Object.values(obj);
const keys = Object.keys(obj);
const newObj = {};
values.forEach((value) => {
  keys.forEach((key) => {
    if (obj[key] == value) {
      if (newObj[value] == undefined) {
        newObj[value] = [key];
      } else {
        const isKeyExist = newObj[value].find((m) => m == key);
        if (isKeyExist == undefined) {
          newObj[value].push(key);
        }
      }
    }
  });
});
console.log(newObj);
let arr = [1, 3, 2, 4, 5];
const sum = arr.reduce((acc, cVal) => acc + cVal, 0);
console.log(sum);

const productList = [
  { name: '', quantity: 3 },
  { name: '', quantity: 3 },
  { name: '', quantity: 3 },
  { name: '', quantity: 3 },
];
const sum2 = productList.reduce((acc, item) => acc + item.quantity, 0);
console.log(sum2);

Reverse Index Mapping: Group People by Skill

Inverts a list of users with skills into a reverse dictionary where each unique skill is a key mapped to an array of all people possessing that skill.

const people = [ { name: 'john', skills: ['js', 'c#', 'html'] }, { name: 'doe',
skills: ['js'] }, { name: 'dennis', skills: ['js', 'c++', 'html'] } ]; Output
Shoud be { js: [ 'john', 'doe', 'dennis' ], 'c#': [ 'john' ], html: [ 'john',
'dennis' ], 'c++': [ 'dennis' ] }
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);
const valueList = [];

people.forEach((item) => {
  item.skills.forEach((skill) => {
    const isSkillPresent = valueList.find((m) => m == skill);
    if (isSkillPresent == undefined) {
      valueList.push(skill);
    }
  });
});
const newObj = {};
valueList.forEach((value) => {
  const peopleWithSkill = people.filter((m) => m.skills.includes(value));
  if (peopleWithSkill.length != 0) {
    if (!newObj[value]) {
      newObj[value] = peopleWithSkill.map((item) => item.name);
    }
  }
});
console.log(valueList);
console.log(newObj);

JavaScript Key Concepts: Event Loop, Polyfills, WeakMaps & Prototypes

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); };