JavaScript Concepts

1. New features in ES6 version:

1. let and const

let allows re-assignment, const does not.


let count = 10; 
count = 20; // ✅ allowed

const pi = 3.14;
// pi = 3.1416 ❌ error
    

Output: count = 20, pi = 3.14

2. Arrow Functions


const numbers = [1, 2, 3];
const squares = numbers.map(n => n * n);
    

Squares: 1,4,9

3. Template Literals


let name = 'Arpit';
let msg = `Hello ${name}, Welcome to Angular 17!`;
    

Message: Hello Arpit, Welcome to Angular 17!

4. Default Parameters


const greet = (name = 'Guest') => `Hello, ${name}`;
greet(); // Hello, Guest
greet('Arpit'); // Hello, Arpit
    

Output:Hello, Guest, Hello, Arpit

5. Destructuring


const student = { id: 1, username: 'John' };
const { username } = student;
    

Message: John is learning Angular

6. Spread & Rest


const arr1 = [1, 2];
const arr2 = [...arr1, 3, 4]; // Spread

const sum = (...nums) => nums.reduce((a,b) => a+b, 0); // Rest
    

Spread Result: 1, 2, 3, 4

Rest Sum: 30

2. static Vs dynamic

1. Dynamically Typed (JavaScript)

JavaScript variables do not have fixed types. They can change type at runtime.


let dynamicVar = 42;        // number
dynamicVar = "Hello";       // string
dynamicVar = true;          // boolean
    

Output:

dynamicVar = 42 (number)
dynamicVar = "Hello" (string)
dynamicVar = true (boolean)

2. Statically Typed (TypeScript)

TypeScript variables have a type declared and cannot be assigned a different type.


let staticNum: number = 10;
staticNum = 20;     // ✅ allowed
staticNum = "Hello"; // ❌ Error: Type 'string' not assignable to type 'number'
    

Output:

staticNum = 10 (number)

3. Scope in JavaScript

1. Global Scope

Variables declared outside any function are global and accessible everywhere.


          let globalVar = "I am global";

          function test() {
            console.log(globalVar); // ✅ Accessible
          }

          test();
          console.log(globalVar); // ✅ Accessible
              

Output:

I am global | I am global

2. Function Scope

Variables declared inside a function are local to that function and not accessible outside.


          function test() {
            let localVar = "I am local";
            console.log(localVar); // ✅ Accessible
          }

          test();
          console.log(localVar); // ❌ ReferenceError
              

Output:

I am local | ReferenceError: localVar is not accessible outside function

3. Block Scope

let and const are block-scoped, while var is function-scoped.


          {
            let blockVar = "I am block scoped";
            const constBlock = "Me too";
            console.log(blockVar); // ✅
          }

          console.log(blockVar); // ❌ ReferenceError
              

Output:

I am block scoped | Me too | ReferenceError: blockVar is not accessible outside block

4. Lexical Scope

Functions remember the scope in which they were defined (not where they are called).


          function outer() {
            let outerVar = "Outer";
            function inner() {
              console.log(outerVar); // ✅ Accesses parent scope
            }
            return inner;
          }

          let myFunc = outer();
          myFunc(); // "Outer"
              

Output:

Outer

5. Scope Chain

JavaScript looks for variables in the current scope, then outer scopes, up to the global scope.


          let a = 10;
          function outer() {
            let b = 20;
            function inner() {
              let c = 30;
              console.log(a, b, c); // ✅ 10, 20, 30
            }
            inner();
          }
          outer();
              

Output:

10, 20, 30

4. Differences between var, let & const

1. var

var is function-scoped, allows redeclaration & reassignment, and is hoisted (initialized with undefined).


function testVar() {
  if (true) {
    var x = 10;
  }
  console.log(x); // ✅ 10 (function scoped)
}

testVar();
console.log(y); // undefined (hoisted)
var y = 5;
    

Output:

Inside function: 10 | Before declaration y: undefined | After assignment y: 5

2. let

let is block-scoped, does not allow redeclaration in the same scope, but reassignment is allowed.


function testLet() {
  if (true) {
    let x = 20;
    console.log(x); // ✅ 20
  }
  console.log(x); // ❌ ReferenceError (block scoped)
}

let z = 30;
// let z = 40; // ❌ Error (re-declaration not allowed)
    

Output:

Inside block: 20 | Outside block: ReferenceError (x not accessible) | Initial z: 30 | After reassignment z: 40

3. const

const is block-scoped, cannot be redeclared or reassigned, but object/array contents can still be mutated.


const pi = 3.14;
// pi = 3.1416; // ❌ Error (cannot reassign)

const user = { name: "Arpit" };
user.name = "John"; // ✅ Allowed (object mutation)
    

Output:

pi: 3.14 | Reassign pi (not allowed) | User after mutation: {"name":"John"}

5. Hoisting in JavaScript

1. Function Hoisting

Function declarations are hoisted, so they can be called before they are defined.


test(); // ✅ Works due to hoisting

function test() {
  return "I am hoisted!";
}
    

Output:

I am hoisted!

2. Var Hoisting

var is hoisted but initialized with undefined.


console.log(a); // ✅ undefined (hoisted)
var a = 10;
console.log(a); // 10
    

Output:

Before declaration: undefined | After initialization: 10

3. Let & Const Hoisting

let and const are hoisted but stay in a "temporal dead zone" until they are initialized. Accessing them before declaration throws a ReferenceError.


try {
  console.log(b); // ❌ ReferenceError
  let b = 20;
} catch (err) {
  console.log(err.message);
}

try {
  console.log(c); // ❌ ReferenceError
  const c = 30;
} catch (err) {
  console.log(err.message);
}
    

Output:

Let before declaration: Cannot access 'o' before initialization || Const before declaration: Cannot access 'o' before initialization

6. Temporal Dead Zone (TDZ)

1. Temporal Dead Zone

Variables declared with let and const are in a "temporal dead zone" from the start of the block until the declaration is evaluated. Accessing them before declaration throws a ReferenceError.


console.log(x); // ❌ ReferenceError: Cannot access 'x' before initialization
let x = 10;

try {
  console.log(y); // ❌ ReferenceError: Cannot access 'y' before initialization
  const y = 20;
} catch (err) {
  console.log(err.message);
}
    

Output:

7. Closure in JavaScript

1. Simple Closure

A closure is a function that remembers the scope in which it was created.


function outerFunction() {
  let count = 0;
  return function innerFunction() {
    count++;
    return count;
  };
}

const counter = outerFunction();
console.log(counter());
console.log(counter());
console.log(counter());

Output:

1 | 2 | 3

2. Closure with Parameters

Closures can also capture arguments passed to the outer function.


function greet(name) {
  return function(message) {
    return `Hello ${name}, ${message}`;
  };
}

const greetArpit = greet('Arpit');
console.log(greetArpit('Welcome!'));
console.log(greetArpit('How are you?'));

Output:

Hello Arpit, Welcome! | Hello Arpit, How are you?

8. Differences between == and ===

== vs ===

== compares **values** after type coercion, while === compares **both value and type**.

let a: any = 5;
      let b: any = '5';

      console.log(a == b);  // true, only value is compared
      console.log(a === b); // false, value + type are compared
      

Output:

true | false

9. NaN in JavaScript

1. NaN (Not-a-Number)

`NaN` represents a value that is not a valid number. It occurs when arithmetic operations fail to produce a numeric result.


          let a: any = "Hello";
          let b = Number(a); // NaN

          console.log(b);        // NaN
          console.log(isNaN(b)); // true
          console.log(Number.isNaN(b)); // true
              

Output:

b: NaN | isNaN(b): true | Number.isNaN(b): true

2. Operations resulting in NaN


          const result = "abc" / 2;
          console.log(result); // NaN
              

Output:

NaN

10. Difference between null and undefined

1. null

null represents an explicitly assigned “no value”.


let a = null;
console.log(a); // null
console.log(typeof a); // "object"
    

Output:

2. undefined

undefined means a variable has been declared but not assigned a value.


let b;
console.log(b); // undefined
console.log(typeof b); // "undefined"
    

Output:

11. BOM vs DOM in JavaScript

1. DOM (Document Object Model)

The DOM represents the HTML document structure. It allows JavaScript to manipulate elements, attributes, and content.


console.log(document.title); // Logs the page title
document.body.style.backgroundColor = "#f0f0f0"; // Changes page background
    

Output:

2. BOM (Browser Object Model)

The BOM provides objects to interact with the browser (window, navigator, screen, location, history).


console.log(window.innerWidth); // Width of the browser window
console.log(navigator.userAgent); // Browser info
    

Output:

12. Critical Rendering Path (CRP) in JavaScript

1. Build the DOM

The browser parses the HTML and creates the DOM (Document Object Model).


<h1>Hello World</h1>
<p>Welcome to CRP</p>
    

Example in JS:


console.log(document.body); // Shows the DOM structure
    

2. Build the CSSOM

The browser parses CSS and creates the CSSOM (CSS Object Model).


p {
  color: blue;
  font-size: 18px;
}
    

Effect: Styles are applied when CSSOM is combined with DOM.

3. Render Tree

The DOM and CSSOM are combined to create the Render Tree, which only includes visible elements.


// Example: Hidden elements (display:none) won't appear in Render Tree
    

4. Layout (Reflow)

The browser calculates size and position of each element.


document.querySelector("p").style.margin = "50px";
// Triggers reflow (layout recalculation)
    

5. Paint & Composite

The browser fills in pixels (colors, text, borders, images) and renders layers to the screen.


// Example: Change background (repaint)
document.body.style.backgroundColor = "lightyellow";
    

⚡ JavaScript's Role

- Blocking: Scripts without async or defer pause HTML parsing.
- Modifying: JavaScript changes DOM/CSSOM, which may trigger reflow & repaint.


<script src="app.js"></script> 
<script src="app.js" defer></script> 
    

🚀 Optimization Tips

  • Use async or defer for scripts
  • Inline critical CSS
  • Lazy-load non-essential JS/CSS
  • Minify and compress resources

13. Basic JavaScript Array Methods

1. forEach()

Executes a provided function once for each array element.


let numbers = [1, 2, 3];
numbers.forEach(n => console.log(n));
    

Output:

[1,2,3]

2. map()

Creates a new array by applying a function to each element.


let doubled = numbers.map(n => n * 2);
console.log(doubled);
    

Output:

[2,4,6]

3. filter()

Creates a new array with elements that pass a condition.


let evens = numbers.filter(n => n % 2 === 0);
console.log(evens);
    

Output:

[2]

4. reduce()

Reduces the array to a single value by applying a function.


let sum = numbers.reduce((acc, n) => acc + n, 0);
console.log(sum);
    

Output:

6

5. find()

Returns the first element that satisfies a condition.


let firstEven = numbers.find(n => n % 2 === 0);
console.log(firstEven);
    

Output:

2

6. includes()

Checks if an array contains a certain element.


let hasTwo = numbers.includes(2);
console.log(hasTwo);
    

Output:

true

14. Rest Parameter & Spread Operator

1. Rest Parameter (...args)

Allows a function to accept an indefinite number of arguments as an array.


function sumAll(...numbers) {
  return numbers.reduce((acc, n) => acc + n, 0);
}
console.log(sumAll(1, 2, 3, 4, 5));

Output:

15

2. Spread Operator (...)

Expands elements of an array (or object properties) into individual elements.


let nums = [1, 2, 3];
let moreNums = [...nums, 4, 5];
console.log(moreNums);

let obj1 = { a: 1, b: 2 };
let obj2 = { ...obj1, c: 3 };
console.log(obj2);

Output:

[1,2,3,4,5]
{"a":1,"b":2,"c":3}

15. The "this" Keyword

1. The "this" Keyword

this refers to the object that is currently executing the function. Its value depends on how the function is called.


let person = {
  name: "Alice",
  greet: function() {
    return "Hello, my name is " + this.name;
  }
};

console.log(person.greet());

// Arrow function (lexical 'this')
let person2 = {
  name: "Bob",
  greet: () => {
    return "Hello, my name is " + this.name;
  }
};

console.log(person2.greet());

Output:

Hello, my name is Alice
Hello, my name is Default Name

16. call(), apply(), and bind()

1. call()

Executes a function with a given this and arguments (comma-separated).

2. apply()

Similar to call(), but arguments are passed as an array.

3. bind()

Returns a new function with this bound, does not execute immediately.

17. JavaScript Single-Threaded & Event Loop

1. Is JavaScript Single-Threaded?

Yes, JavaScript runs on a single thread. But it can handle asynchronous operations using the event loop.

2. Event Loop Example

Shows how asynchronous callbacks are executed after the call stack is empty.


console.log("Start");

setTimeout(() => {
  console.log("Timeout callback");
}, 0);

console.log("End");

Output:

Start
End
Timeout callback

18. Callbacks, Promises, and Async/Await

1. Callback

A callback is a function passed into another function to be executed later.


function greet(name, callback) {
  callback('Hello ' + name);
}

greet('Arpit', function(message) {
  console.log(message);
});

Output:

Hello Arpit

2. Promise

A Promise represents a value that may be available now, or in the future, or never.


function fetchData() {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve('Data received!'), 1000);
  });
}

fetchData().then(console.log);

Output:

Data received!

3. Async/Await

Async/Await is syntactic sugar over Promises, allowing asynchronous code to be written like synchronous code.


function fetchData() {
  return new Promise(resolve => setTimeout(() => resolve('Data received!'), 1000));
}

async function getData() {
  const result = await fetchData();
  console.log(result);
}

getData();

Output:

Data received!

19. Callback Hell

1. What is Callback Hell?

Callback Hell happens when multiple asynchronous operations are nested, making the code unreadable and difficult to maintain.


setTimeout(() => {
  console.log("Step 1");
  setTimeout(() => {
    console.log("Step 2");
    setTimeout(() => {
      console.log("Step 3");
      setTimeout(() => {
        console.log("Step 4");
      }, 1000);
    }, 1000);
  }, 1000);
}, 1000);

Output:

Step 1
Step 2
Step 3
Step 4

20. Observables

1. What is an Observable?

An Observable is a data stream that can emit multiple values over time. Observables are lazy and require subscription to start emitting values.


import { Observable } from 'rxjs';

const myObservable = new Observable(subscriber => {
  subscriber.next('First value');
  subscriber.next('Second value');
  setTimeout(() => {
    subscriber.next('Third value (after 1 sec)');
    subscriber.complete();
  }, 1000);
});

myObservable.subscribe({
  next: value => console.log(value),
  complete: () => console.log('Observable completed')
});

Output:

First value
Second value
Third value (after 1 sec)
Observable completed

21. Promises vs Observables

Differences Between Promises and Observables

  • Promise emits a single value; Observable can emit multiple values over time.
  • Promise is eager (executes immediately); Observable is lazy (executes on subscription).
  • Observable supports operators like map, filter, debounce, etc.
  • Observable can be canceled/unsubscribed; Promise cannot be canceled once started.
  • Promise uses `.then()`/`.catch()`; Observable uses `.subscribe()`.

/* Promise Example */
const myPromise = new Promise((resolve) => {
  resolve('Promise resolved');
});

myPromise.then(console.log);

/* Observable Example */
const myObservable = new Observable(subscriber => {
  subscriber.next('Observable first value');
  subscriber.next('Observable second value');
  subscriber.complete();
});

myObservable.subscribe({
  next: console.log,
  complete: () => console.log('Observable completed')
});

Output:

Observable first value
Observable second value
Observable completed
Promise resolved

22. setTimeout vs setImmediate vs process.nextTick

Difference Between setTimeout, setImmediate, and process.nextTick

  • setTimeout(callback, 0): Executes after 0ms in the timers phase.
  • setImmediate(callback): Executes in the check phase, immediately after I/O events.
  • process.nextTick(callback): Executes before the next event loop tick, before other asynchronous callbacks.

console.log('Start');

setTimeout(() => console.log('setTimeout 0ms'), 0);

setImmediate(() => console.log('setImmediate'));

process.nextTick(() => console.log('process.nextTick'));

console.log('End');

Output:

Start
End
process.nextTick
setImmediate
setTimeout 0ms

23. Microtasks in JavaScript

What is a Microtask?

Microtasks are asynchronous tasks that run immediately after the current synchronous code, but before the next macrotask (like setTimeout). They have higher priority than macrotasks.


console.log("Start");

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

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

queueMicrotask(() => console.log("queueMicrotask (Microtask)"));

console.log("End");

Output:

Start\nEnd\nPromise.then (Microtask)\nqueueMicrotask (Microtask)\nsetTimeout (Macrotask)

24. Pure Functions in JavaScript

What is a Pure Function?

A Pure Function always produces the same output for the same input and does not cause any side effects. Functions that modify external state or depend on it are impure.


// Pure Function Example
function multiply(a: number, b: number): number {
  return a * b;
}

console.log(multiply(2, 3)); 
console.log(multiply(2, 3));

// Impure Function Example
let counter = 0;
function increment(): number {
  return ++counter;
}

console.log(increment());
console.log(increment());

Output:

multiply(2, 3) → 6
multiply(2, 3) → 6
increment() → 1
increment() → 2

25. Error Object and Types in JavaScript

What is an Error Object?

An Error object provides information about an error. It has two main properties: name (the type of error) and message (description).

Common Error Types:

  • Error – generic error
  • ReferenceError – variable not found
  • TypeError – invalid operation on a value
  • SyntaxError – incorrect code syntax
  • RangeError – invalid range (e.g., negative array length)
  • EvalError – error in eval() usage
  • URIError – invalid encodeURI/decodeURI usage

// Different JavaScript Error Objects

try {
  throw new Error("Generic Error Example");
} catch (e) {
  console.log(e.name, "-", e.message);
}

try {
  // ReferenceError
  console.log(undeclaredVar);
} catch (e) {
  console.log(e.name, "-", e.message);
}

try {
  // TypeError
  null.f();
} catch (e) {
  console.log(e.name, "-", e.message);
}

try {
  // RangeError
  let arr = new Array(-1);
} catch (e) {
  console.log(e.name, "-", e.message);
}

try {
  // URIError
  decodeURIComponent("%");
} catch (e) {
  console.log(e.name, "-", e.message);
}

Output:

Error - Generic Error Example
ReferenceError - window is not defined
TypeError - Cannot read properties of null (reading 'f')
RangeError - Invalid array length
URIError - URI malformed

26. Error Handling Statements in JavaScript

What are the various statements in error handling?

  • try → Defines code to test for errors.
  • catch → Handles the error if it occurs.
  • finally → Always runs, whether error occurs or not.
  • throw → Used to create custom errors.

// Error Handling Statements Example

try {
  console.log("Before error");

  // Throwing a custom error
  throw new Error("Something went wrong!");

  console.log("This will not run");
} catch (e) {
  console.log("Caught an error:", e.message);
} finally {
  console.log("Finally block always executes");
}

Output:

Before error
Caught an error: Something went wrong!
Finally block always executes

27. Strict Mode in JavaScript

What do you mean by strict mode in JavaScript and its characteristics?

  • Variables must be declared before use.
  • Duplicate function parameters are not allowed.
  • this inside a function is undefined (not global).
  • Reserved keywords cannot be used as identifiers.
  • Deleting variables, functions, or function parameters is not allowed.
"use strict";

  // Example 1: Using undeclared variable
try {
  num = 10; // ❌ Error in strict mode
  console.log(num);
} catch (error) {
  console.log("Error:", error.message);
}

// Example 2: 'this' inside function
function checkThis() {
  console.log("'this' in strict mode:", this);
}
checkThis();

// Example 3: Deleting variable
try {
  let x = 100;
  // delete x; // ❌ Not allowed
} catch (error) {
  console.log("Error:", error.message);
}

Output:

Error: num is not defined
'this' in strict mode: undefined
Error: Delete of an unqualified identifier in strict mode.

28. Cookie vs Local Storage vs Session Storage

What are the differences between Cookie, Local Storage, and Session Storage?

  • Cookie → Small data (4KB), sent with every HTTP request, can have expiry.
  • Local Storage → Stores large data (~5–10MB), persists even after browser close, not sent to server.
  • Session Storage → Similar to Local Storage but data clears when browser tab is closed.
// Cookie Example
document.cookie = "username=John; expires=Fri, 31 Dec 2025 23:59:59 GMT";
console.log("Cookies:", document.cookie);

// Local Storage Example
localStorage.setItem("theme", "dark");
console.log("Local Storage:", localStorage.getItem("theme"));

// Session Storage Example
sessionStorage.setItem("token", "12345");
console.log("Session Storage:", sessionStorage.getItem("token"));

Output:

Cookies: username=John
Local Storage: dark
Session Storage: 12345

29. Prototype Chaining in JavaScript

What is Prototype Chaining?

Prototype chaining is the mechanism by which objects in JavaScript inherit properties and methods from other objects through the __proto__ or prototype chain.

function Person(name) {
  this.name = name;
}

Person.prototype.sayHello = function() {
  return "Hello, my name is " + this.name;
};

function Student(name, grade) {
  Person.call(this, name); // inherit properties
  this.grade = grade;
}

// Inherit methods via prototype chaining
Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;

Student.prototype.getGrade = function() {
  return this.name + " is in grade " + this.grade;
};

const student1 = new Student("Alice", 10);

console.log(student1.sayHello()); 
console.log(student1.getGrade());

Output:

Hello, my name is Alice
Alice is in grade 10

30. Generators in JavaScript

What are Generators and their types?

Generators are special functions in JavaScript that can **pause execution** and **resume later**. They are defined using the function* syntax and use yield to produce values.

Types of Generators:

  • Simple Generator – produces sequence of values on demand.
  • Infinite Generator – can produce values indefinitely until stopped.
  • Delegated Generator – uses yield* to delegate to another generator.
// Simple Generator
function* simpleGenerator() {
  yield 1;
  yield 2;
  yield 3;
}

const gen = simpleGenerator();
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // 3

// Infinite Generator
function* infiniteGenerator() {
  let i = 0;
  while(true) {
    yield i++;
  }
}
const infGen = infiniteGenerator();
console.log(infGen.next().value); // 0
console.log(infGen.next().value); // 1

// Delegated Generator
function* generatorA() {
  yield 'A1';
  yield 'A2';
}
function* generatorB() {
  yield* generatorA(); // delegate
  yield 'B1';
}
const genB = generatorB();
console.log(genB.next().value); // A1
console.log(genB.next().value); // A2
console.log(genB.next().value); // B1

Output:

1
2
3
0
1
A1
A2
B1

31. Debouncing vs Throttling in JavaScript

What is the difference between Debouncing and Throttling?

  • Debouncing → Ensures a function is called **after a certain delay** only once, after the last event. Useful for input validation, search suggestions.
  • Throttling → Ensures a function is called **at most once every interval**. Useful for scroll, resize events.
// Debounce Function
function debounce(func: Function, delay: number) {
  let timer: any;
  return function(...args: any[]) {
    clearTimeout(timer);
    timer = setTimeout(() => func.apply(this, args), delay);
  };
}

const debouncedFunc = debounce(() => console.log("Debounced!"), 1000);
debouncedFunc();
debouncedFunc();
debouncedFunc(); // Only this call will execute after 1 sec

// Throttle Function
function throttle(func: Function, limit: number) {
  let lastFunc: any;
  let lastRan: number;
  return function(...args: any[]) {
    if (!lastRan) {
      func.apply(this, args);
      lastRan = Date.now();
    } else {
      clearTimeout(lastFunc);
      lastFunc = setTimeout(() => {
        if ((Date.now() - lastRan) >= limit) {
          func.apply(this, args);
          lastRan = Date.now();
        }
      }, limit - (Date.now() - lastRan));
    }
  };
}

const throttledFunc = throttle(() => console.log("Throttled!"), 2000);
throttledFunc();
throttledFunc();
throttledFunc(); // Executes immediately and then once every 2 sec

Output:

Debounced! (after 1 sec)
Throttled! (immediately)
Throttled! (after 2 sec if called again)