Javascript Interview Questions and Answers

Find 100+ JavaScript interview questions and answers to assess candidates' skills in ES6+, DOM manipulation, asynchronous programming, event handling, and front-end development.
By
WeCP Team

As the backbone of interactive web applications, JavaScript remains essential for building dynamic, responsive, and scalable front-end experiences—and increasingly for server-side development through Node.js. Recruiters need developers who can write clean, efficient, and modern JavaScript code while leveraging the latest ECMAScript (ES6+) features.

This resource, "100+ JavaScript Interview Questions and Answers," is tailored for recruiters to streamline the evaluation process. It covers topics from core language fundamentals to advanced concepts, ensuring you can accurately assess a candidate’s capabilities.

Whether hiring for Front-End Developers, Full-Stack Engineers, or JavaScript Specialists, this guide enables you to assess a candidate’s:

Core JavaScript Knowledge

  • Language Basics: Data types, variables, operators, and control structures.
  • Functions & Scope: Function declarations, expressions, arrow functions, closures, and lexical scope.
  • DOM Manipulation: Querying and updating the DOM, event handling, and the event bubbling/capturing model.
  • ES6+ Features: let/const, template literals, destructuring, spread/rest operators, and modules.

Advanced Skills

  • Asynchronous Programming: Callbacks, Promises, async/await, and the Event Loop.
  • Object-Oriented & Functional Patterns: Prototypes, classes, inheritance, and higher-order functions.
  • Performance & Optimization: Debouncing/throttling, memory management, and minimizing reflows.
  • Tooling & Build Processes: Webpack, Babel, and package management (npm/yarn).
  • Security & Best Practices: XSS prevention, content security policy (CSP), and secure coding.

Real-World Proficiency

  • Building single-page applications (SPAs) using vanilla JS or frameworks like React, Vue, or Angular.
  • Integrating REST APIs and handling JSON data.
  • Writing modular, testable code with tools like Jest or Mocha.
  • Debugging and profiling using browser developer tools.

For a streamlined assessment process, consider platforms like WeCP, which allow you to:

Create customized JavaScript assessments tailored to your project’s stack and complexity.
Include hands-on coding tasks, such as implementing algorithms, manipulating the DOM, or building small applications.
Proctor assessments remotely with AI-driven monitoring for test integrity.
Leverage automated grading to evaluate code correctness, efficiency, and adherence to ES6+ best practices.

Save time, ensure technical excellence, and confidently hire JavaScript developers who can deliver interactive, high-performance web applications from day one.

Javascript Interview Questions

Beginner (40 Questions)

  1. What is JavaScript?
  2. How is JavaScript different from Java?
  3. What are the data types available in JavaScript?
  4. What is a variable in JavaScript? What are the ways to declare variables?
  5. What is the difference between var, let, and const?
  6. Explain undefined and null in JavaScript.
  7. What is a function in JavaScript? How do you define one?
  8. What is a parameter and an argument in a JavaScript function?
  9. What is the difference between == and ===?
  10. How do you create an object in JavaScript?
  11. What is a JavaScript array and how do you create one?
  12. What are the different ways to iterate over an array in JavaScript?
  13. What are JavaScript events? Can you give an example?
  14. What is a callback function in JavaScript?
  15. What is an anonymous function in JavaScript?
  16. Explain the concept of scope in JavaScript.
  17. What is the global object in JavaScript?
  18. What is the purpose of this keyword in JavaScript?
  19. What is the difference between synchronous and asynchronous JavaScript?
  20. What is a promise in JavaScript?
  21. What is the event loop in JavaScript?
  22. What is the use of setTimeout() and setInterval()?
  23. What are template literals in JavaScript?
  24. What is a closure in JavaScript?
  25. What are higher-order functions in JavaScript?
  26. What is the typeof operator used for in JavaScript?
  27. What is the difference between a function expression and a function declaration?
  28. What is a default parameter in a JavaScript function?
  29. What are arrow functions in JavaScript?
  30. What is the difference between let and const?
  31. What are JavaScript modules?
  32. What is DOM (Document Object Model) in JavaScript?
  33. What is the window object in JavaScript?
  34. What is event delegation in JavaScript?
  35. How do you handle errors in JavaScript?
  36. What are try-catch blocks in JavaScript?
  37. What is the Array.prototype.map() method in JavaScript?
  38. How can you create a shallow copy of an array?
  39. What is the difference between localStorage and sessionStorage?
  40. How do you define a class in JavaScript?

Intermediate (40 Questions)

  1. What is hoisting in JavaScript?
  2. Explain the bind(), call(), and apply() methods in JavaScript.
  3. What is the difference between null and undefined?
  4. What is the difference between function scope and block scope?
  5. What are the different ways to clone an object in JavaScript?
  6. What is a promise chain and how does it work?
  7. What are the advantages and disadvantages of using promises over callbacks?
  8. What are the different ways to handle asynchronous code in JavaScript?
  9. What is a JavaScript closure and how do you use it?
  10. Explain the concept of the "this" keyword in different contexts.
  11. What is the purpose of Object.create() in JavaScript?
  12. What is a JavaScript singleton?
  13. What are JavaScript modules and how do you use them?
  14. What is destructuring in JavaScript?
  15. What is the spread operator (...) in JavaScript?
  16. What is the rest parameter (...) in JavaScript?
  17. Explain the concept of deep copy vs. shallow copy.
  18. How do you handle errors asynchronously in JavaScript?
  19. What is the EventEmitter class in Node.js?
  20. How does the setInterval() function work in JavaScript?
  21. What are template literals and how are they useful in JavaScript?
  22. Explain the difference between == and === in JavaScript.
  23. What are closures used for in JavaScript?
  24. How do you create a promise in JavaScript?
  25. What is the Array.prototype.reduce() method in JavaScript?
  26. What is event bubbling and event capturing in JavaScript?
  27. What is the difference between synchronous and asynchronous execution in JavaScript?
  28. What is async/await and how does it simplify handling asynchronous code?
  29. What is the purpose of setImmediate() in Node.js?
  30. How do you prevent default behavior of an event in JavaScript?
  31. How do you bind an event handler to an element in JavaScript?
  32. What is a JavaScript proxy and how is it used?
  33. Explain the concept of call stack in JavaScript.
  34. What is the difference between bind() and call()?
  35. What are JavaScript generators and how do they work?
  36. What is memoization and how is it implemented in JavaScript?
  37. What are the main differences between ES5 and ES6?
  38. How do you create a method for an object in JavaScript?
  39. What is the difference between Object.freeze() and Object.seal()?
  40. Explain the importance of immutability in JavaScript.

Experienced (40 Questions)

  1. What is the event loop in JavaScript and how does it work?
  2. What is a JavaScript decorator, and how would you implement one?
  3. How would you optimize the performance of a JavaScript application?
  4. What is the difference between a stack and a queue in JavaScript?
  5. How would you implement memoization in JavaScript for a performance-critical application?
  6. What are JavaScript WeakMaps and WeakSets?
  7. What is the purpose of Symbol in JavaScript?
  8. How do you handle large-scale asynchronous code in JavaScript?
  9. What is the role of a JavaScript polyfill, and when should you use one?
  10. What is event delegation, and why is it important in large applications?
  11. What is the significance of JavaScript’s setImmediate() vs. setTimeout()?
  12. How would you explain prototypal inheritance in JavaScript?
  13. What are the major differences between ES6 classes and the traditional prototype-based inheritance?
  14. How would you handle a memory leak in a JavaScript application?
  15. How does the async/await syntax work under the hood?
  16. How would you implement a custom event system in JavaScript?
  17. What is the difference between shallow copy and deep copy for objects and arrays?
  18. Explain JavaScript’s call stack, event loop, and the microtask queue.
  19. What is the difference between let and var in terms of scoping and hoisting?
  20. How does JavaScript’s garbage collection work?
  21. What are JavaScript modules (ES6) and how do they differ from CommonJS modules?
  22. What is an IIFE (Immediately Invoked Function Expression) and when would you use it?
  23. What are closures used for in advanced JavaScript applications?
  24. What is the difference between Object.create() and new keyword in JavaScript?
  25. How would you handle deep cloning in JavaScript for complex objects?
  26. What are some strategies for improving JavaScript application security?
  27. Explain the concept of concurrency vs. parallelism in JavaScript.
  28. What is the role of the service worker in JavaScript applications?
  29. How would you implement a JavaScript debounce and throttle function?
  30. What is a JavaScript singleton pattern, and when would you use it?
  31. What are generators in JavaScript, and how would you use them for complex async tasks?
  32. How does the bind() method affect the execution context in JavaScript?
  33. What are the main challenges when working with JavaScript in large applications, and how do you overcome them?
  34. How would you implement a custom JavaScript iterator?
  35. What are JavaScript Promises and how do they differ from callbacks?
  36. Explain the difference between Object.seal(), Object.freeze(), and Object.preventExtensions().
  37. How would you go about managing state in a complex JavaScript application?
  38. How do JavaScript Proxy and Reflect work, and what are their use cases?
  39. What are the different types of Web APIs in JavaScript, and how do you use them?
  40. How would you implement server-side rendering (SSR) using JavaScript frameworks like React?

Javascript Interview Questions and Answers

Beginners (Q&A)

1. What is JavaScript?

JavaScript is a high-level, interpreted programming language that was initially created to add interactivity and dynamic functionality to web pages. It was developed by Brendan Eich at Netscape in 1995 under the name "LiveScript," and later renamed JavaScript to align with the growing popularity of Java. Despite the name similarity, JavaScript and Java are distinct languages with different use cases and characteristics.

JavaScript is a multi-paradigm language that supports event-driven, functional, and imperative programming styles. It's primarily used on the client side in web browsers to manipulate the Document Object Model (DOM), allowing developers to change the content and structure of web pages dynamically without requiring a full page reload.

With the advent of environments like Node.js, JavaScript is now also used on the server side for building web applications, APIs, and server services. This has led to a unified development stack, often referred to as the "JavaScript stack" (with technologies like Express, MongoDB, Angular/React/Vue, and Node.js—commonly abbreviated as MEAN or MERN). JavaScript is fundamental to the "full stack" development world and has become one of the most popular and widely-used programming languages in the world.

2. How is JavaScript different from Java?

Despite the similar names, Java and JavaScript have significant differences in terms of design, use cases, syntax, and paradigms:

  • Purpose and Use:
    • Java is a general-purpose, object-oriented programming language designed for building large, complex, and high-performance applications. It is commonly used in enterprise-level systems, Android app development, desktop applications, and backend services.
    • JavaScript, on the other hand, was specifically created for web development to enhance the interactivity of web pages by interacting with the HTML and CSS content dynamically. Although originally used on the client-side, JavaScript now also has extensive usage on the server-side with environments like Node.js.
  • Typing and Compilation:
    • Java is statically typed, meaning that types (e.g., int, String, boolean) must be explicitly declared, and the code is compiled before execution. The compiled Java bytecode is then run on the Java Virtual Machine (JVM), making it platform-independent.
    • JavaScript is dynamically typed, meaning that variables do not require a predefined type and can change type during runtime (e.g., a variable could be a string one moment, and an integer the next). JavaScript code is interpreted by the web browser (or a runtime like Node.js) at runtime, without a compilation step.
  • Execution Environment:
    • Java applications typically run on the JVM, which ensures portability across different operating systems and environments (i.e., "write once, run anywhere").
    • JavaScript runs primarily in web browsers as part of the client-side code, although with environments like Node.js, it can also run on the server side.
  • Concurrency:
    • Java supports multi-threading, where different parts of a program can run in parallel threads, which can improve performance for complex, resource-heavy applications.
    • JavaScript uses an event-driven, single-threaded model with non-blocking asynchronous execution (e.g., with the use of callbacks, Promises, and async/await). This makes it ideal for handling multiple user interactions in web applications without blocking the UI.
  • Syntax:
    • Java has a rigid syntax that requires the definition of classes and strict rules for variable declarations. Everything in Java is object-oriented, and a class is a blueprint for creating objects.
    • JavaScript has a more flexible syntax, supports both object-oriented and functional programming paradigms, and allows for more dynamic behavior like closures, anonymous functions, and higher-order functions.

3. What are the data types available in JavaScript?

In JavaScript, data types are divided into two categories: primitive types and objects. Here's a deeper look at each:

Primitive Data Types:

  1. Number: JavaScript uses a single type, Number, to represent both integers and floating-point numbers. Examples: 10, 3.14, -5. JavaScript uses double-precision floating-point format for all number types, which can lead to some precision issues when working with very large or very small numbers.
  2. String: A string is a sequence of characters used for textual data. Strings can be enclosed in either single quotes ('), double quotes ("), or backticks (`) for template literals. For example: "Hello, World!" or 'JavaScript'. Template literals allow for interpolation and multi-line strings.
  3. Boolean: This type has only two values: true or false. It is used to represent binary states, such as true/false conditions or flags. Example: let isValid = true;.
  4. Undefined: This type is assigned to variables that have been declared but have not yet been assigned a value. Example: let x; console.log(x); will output undefined. It indicates that a variable exists but has no value assigned.
  5. Null: Null is a special keyword that represents the intentional absence of any value or object. It is a primitive value that is often used to indicate that a variable or object is empty or undefined. Example: let obj = null;.
  6. Symbol: A Symbol is a unique and immutable primitive value used as the key for object properties. Symbols are often used to create private properties or methods in objects. Example: const mySymbol = Symbol('description');.
  7. BigInt: Introduced in ES2020, BigInt allows you to work with arbitrarily large integers that exceed the limits of the Number type. Example: const bigNumber = 1234567890123456789012345678901234567890n;.

Objects:

Objects are more complex data structures that can hold multiple values and data types. They are used for storing collections of data and more complex entities. Examples of objects include arrays, functions, and plain JavaScript objects created using curly braces.

4. What is a variable in JavaScript? What are the ways to declare variables?

A variable in JavaScript is a symbolic name for a value stored in memory. It is a container that holds data, and this data can be accessed and modified through the variable's name. Variables in JavaScript are dynamic, meaning they can change types over time (e.g., a variable can hold a number at one point and a string at another).

There are three main ways to declare variables in JavaScript:

  1. var:
    • var is the oldest keyword for declaring variables, and it was introduced in JavaScript ES5. Variables declared with var have function scope, meaning they are accessible throughout the entire function in which they are declared (if not declared inside a function, they are globally scoped).
    • var declarations are hoisted, meaning that the declaration is moved to the top of its scope during execution, but not the initialization.
    • var can lead to unexpected behavior due to its lack of block-level scoping.
  2. let:
    • let was introduced in ES6 (ECMAScript 2015) and is block-scoped, meaning it is confined to the block (e.g., loops, if-statements) in which it is declared.
    • Unlike var, let does not allow redeclaration within the same scope, reducing potential bugs.
    • let allows you to assign new values to the variable, and it is the most commonly used way to declare variables in modern JavaScript.
  3. const:
    • const also was introduced in ES6 and is used to declare constants, or variables that cannot be reassigned after initialization.
    • Like let, const is block-scoped.
    • It is used when you know the variable should not change. However, note that for objects and arrays declared with const, the contents (or values) of the object or array can still be modified, but the reference to the variable itself cannot be reassigned.

In general, modern JavaScript development recommends using let and const due to their block-scoping behavior, and var is considered outdated for most use cases.

5. What is the difference between var, let, and const?

The differences between var, let, and const mainly relate to scoping rules, hoisting behavior, and mutability:

  1. var:
    • Scope: Function-scoped. This means var is only available within the function where it is declared. If declared outside of a function, it becomes globally scoped.
    • Hoisting: var declarations are hoisted to the top of their scope, but the value assignment remains in place. This can lead to bugs if you try to access a variable before it is assigned.
    • Re-declaration: Variables declared with var can be re-declared in the same scope without causing an error, which can lead to accidental overwrites.
  2. let:
    • Scope: Block-scoped, meaning it is only accessible within the block of code (e.g., loops, if statements) where it is declared.
    • Hoisting: let is also hoisted to the top of its block, but unlike var, it does not get initialized until the line of declaration. Accessing a let variable before its declaration will result in a ReferenceError (called a "temporal dead zone").
    • Re-declaration: let does not allow re-declaring the same variable in the same block scope.
  3. const:
    • Scope: Like let, const is block-scoped.
    • Hoisting: const behaves similarly to let in terms of hoisting, where it is hoisted but not initialized until the line of declaration.
    • Re-declaration: const does not allow re-declaration or reassignment. Once a variable is assigned a value, it cannot be reassigned. However, for objects or arrays, the properties or elements can still be modified.

In modern JavaScript, let and const are preferred over var due to their clearer and more predictable scoping rules.

6. Explain undefined and null in JavaScript.

In JavaScript, both undefined and null are primitive values that represent the absence of a value, but they are used in different contexts and have distinct characteristics:

undefined:

  • Definition: undefined is the default value assigned to a variable that has been declared but not assigned a value. It is a built-in global variable that indicates that a variable is "empty" or "not yet defined."
  • Use Case: It typically occurs in the following situations:

A variable is declared but not initialized:

Total width = width + left padding + right padding + left border + right border + left margin + right margin
Total height = height + top padding + bottom padding + top border + bottom border + top margin + bottom margin

To avoid this "extra" space being included in the element's dimensions, you can use the box-sizing: border-box; property. This makes padding and borders part of the element's specified width and height.

5. What is the difference between inline, block, and inline-block elements?

The display property in CSS determines how an element behaves in the layout of a page. The three most common display types are:

  1. Block-level elements:
    • Block-level elements take up the full width of their container (default behavior), and they start on a new line, stacking vertically.
    • Examples: <div>, <p>, <section>, <header>.
    • These elements can have set width, height, margin, and padding.

Example:

div {
  display: block;
  width: 100%;
}
  1. Inline elements:
    • Inline elements only take up as much width as necessary, and they do not cause line breaks. They flow along with the content in the same line.
    • Examples: <span>, <a>, <strong>.
    • These elements cannot have set width and height but can have padding and margin on the left and right sides.

Example:

span {
  display: inline;
}
  1. Inline-block elements:
    • Inline-block elements combine characteristics of both block and inline elements. Like inline elements, they do not cause line breaks. However, like block elements, they can have set width and height.
    • Examples: <button>, <input>, and other custom elements.
    • These elements are often used for creating flexible and responsive layouts.

Example:

.box {
  display: inline-block;
  width: 100px;
  height: 100px;
}

6. What is the purpose of the z-index property in CSS?

The z-index property in CSS controls the stacking order of positioned elements (those with position: relative, position: absolute, or position: fixed). It determines which elements appear in front of or behind other elements when they overlap. Elements with higher z-index values will appear on top of elements with lower z-index values.

  • Usage: To manipulate the visibility of overlapping elements, for example, placing modal windows or dropdown menus on top of other content.

Example:

.modal {
  position: absolute;
  z-index: 100;
}

.overlay {
  position: absolute;
  z-index: 50;
}

Here, the .modal will be displayed above the .overlay because its z-index is higher.

Note that z-index only works with elements that have a positioning context (i.e., elements with position set to something other than static).

7. What does the float property do in CSS?

The float property in CSS is used to position elements (usually images or text) to the left or right of their container, allowing content to wrap around them. It's primarily used for creating layouts, especially in older designs, but it has largely been replaced by more modern layout techniques like Flexbox and Grid.

  • float: left; — Moves the element to the left of its container.
  • float: right; — Moves the element to the right of its container.
  • float: none; — Removes the float (default value).

Example:

img {
  float: left;
  margin-right: 10px;
}

Function Expression: A function can also be defined as an expression and assigned to a variable. This is an anonymous function (it doesn’t have a name) or a named function expression.

const add = function(x, y) {
  return x + y;
};
console.log(add(2, 3));  // 5

Arrow Function (ES6): Introduced in ECMAScript 6, arrow functions provide a more concise syntax and are especially useful for writing short functions. Arrow functions also have different scoping behavior for the this keyword, which makes them ideal for some use cases like event handling.

const multiply = (a, b) => a * b;
console.log(multiply(4, 5));  // 20

Anonymous Function: Functions that don’t have a name, often used in callbacks or higher-order functions:

setTimeout(function() {
  console.log("This will run after 1 second.");
}, 1000);

Function Parameters and Return Values:

  • Functions can accept parameters (inputs) and return a value (output). Parameters are the variables you define within the parentheses of the function declaration.

You can call a function by passing arguments—the actual values that correspond to the parameters.

function subtract(a, b) {
  return a - b;
}
console.log(subtract(10, 5));  // 5

JavaScript functions are first-class citizens, meaning they can be passed around as arguments, returned from other functions, and assigned to variables. This is a powerful feature of JavaScript.

8. What is a parameter and an argument in a JavaScript function?

In the context of JavaScript functions:

Parameter: A parameter is a variable that is used in the function definition to accept values when the function is called. Parameters act as placeholders for the actual values that will be passed to the function. Parameters are declared in the parentheses of a function declaration or function expression. Example:

function greet(name) {  // "name" is a parameter
  console.log("Hello, " + name);
}

Argument: An argument is the actual value or expression that is passed into the function when it is invoked (called). Arguments are passed to the function in place of the parameters defined in the function's signature. The number and order of arguments must match the parameters. Example:

greet("Alice");  // "Alice" is an argument passed to the "name" parameter

Key Differences:

  • Parameters are like placeholders in the function's definition, while arguments are the actual values you provide when you call the function.
  • JavaScript functions are flexible in that you can pass more arguments than the number of parameters defined in the function. In this case, the extra arguments are ignored (unless you use arguments object or rest parameters).

Example of default parameters:

function multiply(a, b = 2) {  // Default value for b is 2
  return a * b;
}
console.log(multiply(3));  // 6, since b defaults to 2

9. What is the difference between == and ===?

In JavaScript, both == (loose equality) and === (strict equality) are comparison operators used to check if two values are equal, but they differ significantly in how they perform comparisons:

== (Loose Equality):

  • Type Coercion: The == operator performs type coercion before making the comparison. This means it will convert the operands to the same type if they are of different types, and then compare their values.

Example:

console.log(5 == "5");  // true, because "5" is coerced into the number 5
console.log(null == undefined);  // true, because null and undefined are considered equal

Pitfall: Because == converts values to the same type, it can sometimes lead to unexpected results.

console.log(0 == false);  // true, because both are considered falsy values
console.log('' == false);  // true, because an empty string is coerced to false

=== (Strict Equality):

  • No Type Coercion: The === operator, known as strict equality, checks both the value and the type of the operands. It does not perform type coercion.

Example:

console.log(5 === "5");  // false, because one is a number and the other is a string
console.log(null === undefined);  // false, because they are of different types

  • Recommendation: It is generally recommended to use === (strict equality) in JavaScript, as it avoids the potential pitfalls of type coercion and makes the comparison more predictable and reliable.

10. How do you create an object in JavaScript?

In JavaScript, objects are used to store collections of data and more complex entities. Objects consist of properties (key-value pairs) and can also have methods (functions that belong to the object).

Ways to Create Objects:

Object Literal: The most common and simple way to create an object is by using object literals. This is done by enclosing a list of properties and methods inside curly braces {}.

const person = {
  firstName: "John",
  lastName: "Doe",
  age: 30,
  greet: function() {
    console.log("Hello, " + this.firstName);
  }
};
person.greet();  // "Hello, John"

Using the new Object() Syntax: You can also create an object using the new Object() syntax, though this is less common and often seen as more verbose than using literals.

const person = new Object();
person.firstName = "John";
person.lastName = "Doe";
person.age = 30;
person.greet = function() {
  console.log("Hello, " + this.firstName);
};

Using a Constructor Function: Another way to create an object is by defining a constructor function. Constructor functions allow you to create multiple instances of similar objects.

function Person(firstName, lastName, age) {
  this.firstName = firstName;
  this.lastName = lastName;
  this.age = age;
  this.greet = function() {
    console.log("Hello, " + this.firstName);
  };
}
const person1 = new Person("Alice", "Smith", 25);
const person2 = new Person("Bob", "Johnson", 28);
person1.greet();  // "Hello, Alice"

Using Object.create(): The Object.create() method creates a new object, using an existing object as the prototype.

const personProto = {
  greet: function() {
    console.log("Hello, " + this.firstName);
  }
};
const person = Object.create(personProto);
person.firstName = "Eve";
person.lastName = "Green";
person.greet();  // "Hello, Eve"

Key Characteristics of JavaScript Objects:

  • Objects in JavaScript are dynamic, meaning properties can be added, modified, or deleted at runtime.
  • Properties of an object are accessed using either dot notation (person.firstName) or bracket notation (person['firstName']).
  • JavaScript objects can also contain methods (functions) that act on the object's data.

11. What is a JavaScript array and how do you create one?

A JavaScript array is a data structure used to store a collection of elements, typically of the same type (though JavaScript arrays are flexible and can hold mixed data types). Arrays are ordered, meaning the items are indexed and can be accessed by their position (index) in the array. JavaScript arrays can store any type of data, including numbers, strings, objects, and even other arrays (i.e., multidimensional arrays).

Creating an Array:

Array Literal: The most common and easiest way to create an array is using the array literal syntax, where you define the elements inside square brackets [].

let fruits = ['apple', 'banana', 'cherry'];
console.log(fruits);  // ["apple", "banana", "cherry"]
  1. Using the new Array() Constructor: You can also create an array using the Array constructor, though this method is less common and can lead to unexpected results if not used correctly.

When passing a single numeric argument, it creates an array with that many empty slots:

let emptyArray = new Array(5);  // Array of 5 empty slots
console.log(emptyArray);  // [ <5 empty items> ]

When passing multiple arguments, it creates an array with those elements:

let colors = new Array('red', 'green', 'blue');
console.log(colors);  // ["red", "green", "blue"]

Using the Array.of() Method: This method is used to create an array from a list of elements, regardless of the type or quantity of arguments.

let numbers = Array.of(1, 2, 3);
console.log(numbers);  // [1, 2, 3]

Using the Array.from() Method: Array.from() creates a new array from an array-like or iterable object, such as a string or the arguments object.

let str = "hello";
let arr = Array.from(str);
console.log(arr);  // ["h", "e", "l", "l", "o"]

Accessing Array Elements:

Array elements can be accessed using zero-based indexing:

console.log(fruits[0]);  // "apple"
console.log(fruits[2]);  // "cherry"

12. What are the different ways to iterate over an array in JavaScript?

JavaScript provides multiple ways to iterate over arrays, each suited to different use cases:

Using a for Loop: A traditional for loop is one of the most common ways to iterate over an array when you need control over the loop index.

let fruits = ['apple', 'banana', 'cherry'];
for (let i = 0; i < fruits.length; i++) {
  console.log(fruits[i]);  // "apple", "banana", "cherry"
}

Using forEach(): The forEach() method is an array method that executes a provided function once for each element in the array. It is a cleaner alternative to a for loop.

fruits.forEach(function(fruit) {
  console.log(fruit);  // "apple", "banana", "cherry"
});

Using map(): The map() method creates a new array populated with the results of calling a provided function on every element in the array.

let uppercaseFruits = fruits.map(function(fruit) {
  return fruit.toUpperCase();
});
console.log(uppercaseFruits);  // ["APPLE", "BANANA", "CHERRY"]

Using for...of Loop: The for...of loop is a more modern and concise way to iterate over arrays (and other iterable objects like strings or sets).

for (let fruit of fruits) {
  console.log(fruit);  // "apple", "banana", "cherry"
}

Using for...in Loop: The for...in loop iterates over the enumerable properties (indices) of an object, including arrays. However, it’s generally not recommended for arrays due to potential issues with inherited properties.

for (let index in fruits) {
  console.log(fruits[index]);  // "apple", "banana", "cherry"
}

Using reduce(): The reduce() method applies a function against an accumulator and each element in the array (from left to right) to reduce it to a single value.

let sum = [1, 2, 3].reduce(function(acc, num) {
  return acc + num;
}, 0);
console.log(sum);  // 6

13. What are JavaScript events? Can you give an example?

In JavaScript, an event is an action or occurrence that happens in the browser, such as a user interaction (click, hover, keypress), or a change in the environment (like a page load). JavaScript allows you to listen for these events and respond to them with code.

Example:

<button id="clickMe">Click Me</button>
<script>
  // Attach an event listener to the button
  document.getElementById('clickMe').addEventListener('click', function() {
    alert("Button was clicked!");
  });
</script>

  • In this example, an event listener is added to the button element to listen for the click event. When the button is clicked, the function displays an alert.

Common Event Types:

  • click: Triggered when an element is clicked.
  • mouseover: Triggered when the mouse pointer hovers over an element.
  • keydown: Triggered when a key is pressed down.
  • load: Triggered when the page or an element has finished loading.

14. What is a callback function in JavaScript?

A callback function is a function passed as an argument to another function and is executed after the completion of that function’s operation. This allows for asynchronous programming and is commonly used in handling events, asynchronous operations, and handling results after a task is completed.

Example:

function greetUser(name, callback) {
  console.log("Hello, " + name);
  callback();
}

function showMessage() {
  console.log("Welcome to JavaScript!");
}

greetUser("Alice", showMessage);
// Output:
// Hello, Alice
// Welcome to JavaScript!

In this example, showMessage is a callback function passed to greetUser. Once greetUser finishes executing, it calls the showMessage function.

Asynchronous Callbacks:

Callbacks are often used with asynchronous code, such as setTimeout, event listeners, and network requests:

setTimeout(function() {
  console.log("This message appears after 2 seconds.");
}, 2000);

15. What is an anonymous function in JavaScript?

An anonymous function is a function that does not have a name. It’s typically used when you need a short-lived function for a one-time use, such as in callbacks, event handlers, or higher-order functions.

Example:

setTimeout(function() {
  console.log("This is an anonymous function!");
}, 1000);

In this example, the function inside setTimeout is an anonymous function because it doesn’t have a name. It’s directly passed as an argument.

Arrow Function (ES6):

Arrow functions are a concise syntax for writing anonymous functions:

setTimeout(() => {
  console.log("This is an arrow function!");
}, 1000);

16. Explain the concept of scope in JavaScript.

Scope in JavaScript refers to the accessibility of variables and functions in different parts of the code. There are several types of scope in JavaScript:

  1. Global Scope:
    • A variable declared outside any function is in the global scope and is accessible from anywhere in the program.
let globalVar = "I am global!";
function showVar() {
  console.log(globalVar);  // "I am global!"
}
showVar();
  1. Function Scope:
    • A variable declared inside a function is locally scoped to that function. It is only accessible within that function.
function example() {
  let localVar = "I am local!";
  console.log(localVar);  // "I am local!"
}
example();
console.log(localVar);  // ReferenceError: localVar is not defined
  1. Block Scope (ES6 let and const):
    • Variables declared with let and const are block-scoped, meaning they are only accessible within the block (e.g., inside loops, conditionals) where they are defined.
if (true) {
  let blockVar = "I am block-scoped";
  console.log(blockVar);  // "I am block-scoped"
}
console.log(blockVar);  // ReferenceError: blockVar is not defined

Lexical Scope:

JavaScript uses lexical scoping, meaning the scope is determined by the location where a function is defined, not where it is called.

17. What is the global object in JavaScript?

The global object is the highest-level object in the JavaScript environment. It serves as a container for all globally accessible variables and functions. Its properties and methods are available globally in your JavaScript code.

In browsers, the global object is window. Any variable declared in the global scope becomes a property of the window object.

let globalVar = "I am global!";
console.log(window.globalVar);  // "I am global!"

In Node.js, the global object is global. In Node.js, variables declared in the global scope are accessible via the global object.

globalVar = "I am global!";
console.log(global.globalVar);  // "I am global!"

18. What is the purpose of the this keyword in JavaScript?

The this keyword in JavaScript refers to the current execution context of a function. It refers to the object that is executing the current piece of code. The value of this depends on how a function is called:

In a regular function: this refers to the global object (in browsers, it's the window object), unless in strict mode, where it would be undefined.

function myFunction() {
  console.log(this);  // In non-strict mode, 'this' is the global object (window in browsers)
}
myFunction();

In an object method: this refers to the object that owns the method.

const person = {
  name: "Alice",
  greet: function() {
    console.log("Hello, " + this.name);
  }
};
person.greet();  // "Hello, Alice"

In a constructor function: this refers to the newly created object.

function Person(name) {
  this.name = name;
}
const john = new Person("John");
console.log(john.name);  // "John"

In arrow functions: this does not refer to the object that invokes the function, but instead is lexically bound, meaning it inherits the value of this from its surrounding scope.

const obj = {
  name: "Alice",
  greet: () => {
    console.log(this.name);  // 'this' refers to the global object (window in browsers)
  }
};
obj.greet();  // undefined (in browsers, 'this' is the global object)

19. What is the difference between synchronous and asynchronous JavaScript?

In synchronous programming, operations are executed one after the other. Each operation must complete before the next one begins, blocking further execution until the current operation finishes.

Synchronous Example:

console.log("Start");
console.log("End");
// Output:
// Start
// End

In asynchronous programming, operations can run independently of the main program flow. When an operation is asynchronous, the program doesn’t wait for it to complete; instead, it moves on to the next task. Once the asynchronous task completes, a callback or promise is used to handle the result.

Asynchronous Example:

console.log("Start");
setTimeout(() => {
  console.log("Middle");
}, 1000);
console.log("End");
// Output:
// Start
// End
// Middle (after 1 second)

20. What is a promise in JavaScript?

A Promise in JavaScript is an object that represents the eventual completion (or failure) of an asynchronous operation and its resulting value. It allows you to write asynchronous code in a more readable and manageable way, avoiding "callback hell."

States of a Promise:

  1. Pending: The promise is still in progress.
  2. Fulfilled: The promise has been completed successfully, and a result is available.
  3. Rejected: The promise has failed, and an error is returned.

Creating and Using a Promise:

let myPromise = new Promise((resolve, reject) => {
  let success = true;
  if (success) {
    resolve("Task completed successfully!");
  } else {
    reject("Task failed.");
  }
});

myPromise
  .then((result) => {
    console.log(result);  // "Task completed successfully!"
  })
  .catch((error) => {
    console.log(error);   // If the promise is rejected
  });

Promises are commonly used in asynchronous operations like handling network requests, reading files, or performing timeouts, and they can be chained together for better readability.

21. What is the event loop in JavaScript?

The event loop is a fundamental concept in JavaScript, especially in the context of asynchronous programming. It allows JavaScript to perform non-blocking operations by handling multiple tasks concurrently, even though JavaScript itself is single-threaded.

How the event loop works:

  1. Call Stack: This is where JavaScript functions are executed. When a function is invoked, it is placed on the call stack.
  2. Web APIs: For asynchronous operations like setTimeout(), fetch(), or event handlers, JavaScript delegates these tasks to the browser's Web APIs.
  3. Callback Queue: Once a Web API completes its task (e.g., a timer expires or a network request finishes), its callback function is placed in the callback queue (or message queue).
  4. Event Loop: The event loop continuously checks if the call stack is empty. If the stack is empty, it pushes the first callback from the callback queue to the call stack for execution.

Example:

console.log("Start");

setTimeout(() => {
  console.log("This is from setTimeout");
}, 0);

console.log("End");

// Output:
// Start
// End
// This is from setTimeout

In this example, the setTimeout callback is placed in the callback queue even though it's set to 0 milliseconds. The event loop only executes it after the main call stack is empty (after "Start" and "End" are logged).

22. What is the use of setTimeout() and setInterval()?

Both setTimeout() and setInterval() are functions in JavaScript that handle timing operations, often used in asynchronous tasks.

  1. setTimeout():
    • Executes a function after a specified delay (in milliseconds).
    • It runs the code once after the delay, not repeatedly.

Syntax:

setTimeout(callback, delay);
  • callback: The function to execute.
  • delay: The time (in milliseconds) to wait before executing the callback.

Example:

setTimeout(() => {
  console.log("This message appears after 2 seconds.");
}, 2000);
  1. setInterval():
    • Executes a function repeatedly at a specified interval (in milliseconds), until clearIn

Example:

let count = 0;
let intervalId = setInterval(() => {
  console.log("This message appears every 2 seconds.");
  count++;
  if (count >= 3) clearInterval(intervalId);  // Stop after 3 times
}, 2000);

23. What are template literals in JavaScript?

Template literals (introduced in ES6) are a way to embed expressions inside string literals, making string concatenation easier and more readable. They are enclosed by backticks (`) rather than single (') or double quotes (").

Features of Template Literals:

Expression Interpolation: You can embed variables or expressions inside a string using ${} syntax.Example:

let name = "Alice";
let age = 25;
let message = `Hello, my name is ${name} and I am ${age} years old.`;
console.log(message);  // "Hello, my name is Alice and I am 25 years old."

Multiline Strings: Template literals can span multiple lines without the need for concatenation or escape characters.Example:

let greeting = `Hello,
How are you today?`;
console.log(greeting);

Tagged Templates: A more advanced feature where you can define a function to manipulate the template literal.Example:

function tag(strings, ...values) {
  console.log(strings);  // Array of string literals
  console.log(values);   // Array of interpolated values
}
let name = "Bob";
let age = 30;
tag`Hello, my name is ${name} and I am ${age} years old.`;

24. What is a closure in JavaScript?

A closure is a function that "remembers" its lexical scope (the variables from its surrounding context) even when the function is executed outside of that scope. In other words, closures allow functions to retain access to variables in their defining scope after the outer function has finished execution.

Key Characteristics:

  • Closures enable private data.
  • They allow for data encapsulation and function factories.

Example:

function outerFunction() {
  let counter = 0;  // `counter` is enclosed by the closure
  return function innerFunction() {
    counter++;
    console.log(counter);
  };
}

const increment = outerFunction();  // `increment` is a closure
increment();  // 1
increment();  // 2

In this example, innerFunction retains access to the counter variable even after outerFunction has finished execution. This is because innerFunction is a closure.

25. What are higher-order functions in JavaScript?

A higher-order function is a function that:

  1. Takes one or more functions as arguments.
  2. Returns a function as its result.

Higher-order functions are a powerful concept in functional programming and are commonly used for operations like map, filter, and reduce.

Example:

function applyOperation(a, b, operation) {
  return operation(a, b);
}

function add(x, y) {
  return x + y;
}

console.log(applyOperation(5, 3, add));  // 8

In this example, applyOperation is a higher-order function because it takes a function (add) as an argument and uses it inside its body.

26. What is the typeof operator used for in JavaScript?

The typeof operator in JavaScript is used to determine the type of a given operand. It returns a string that indicates the type of the variable or expression.

Example:

console.log(typeof "Hello");  // "string"
console.log(typeof 42);       // "number"
console.log(typeof true);     // "boolean"
console.log(typeof {});       // "object"
console.log(typeof []);       // "object" (arrays are objects in JavaScript)
console.log(typeof null);     // "object" (this is a known quirk in JavaScript)
console.log(typeof undefined); // "undefined"
console.log(typeof function() {}); // "function"

The typeof operator helps to check the type of a variable, but be cautious with special cases like null, which returns "object".

27. What is the difference between a function expression and a function declaration?

Both function declarations and function expressions are ways to define functions, but they differ in how and when they are hoisted and used.

Function Declaration: A function declaration defines a named function. It is hoisted, meaning the function definition is available throughout the entire scope, even before the function is declared in the code.Example:

console.log(add(2, 3));  // 5
function add(a, b) {
  return a + b;
}

Function Expression: A function expression involves creating a function and assigning it to a variable. Function expressions are not hoisted, meaning you cannot call the function before it is defined.Example:

let add = function(a, b) {
  return a + b;
};
console.log(add(2, 3));  // 5

Key Differences:

  • Hoisting: Function declarations are hoisted, function expressions are not.
  • Anonymous Functions: Function expressions can be anonymous (i.e., they don’t need a name).
  • Flexibility: Function expressions can be passed as arguments, returned from other functions, and assigned to variables.

28. What is a default parameter in a JavaScript function?

A default parameter allows a function to specify a default value for one or more of its parameters if no argument is provided. This feature was introduced in ES6.

Example:

function greet(name = "Guest") {
  console.log("Hello, " + name);
}

greet();          // "Hello, Guest"
greet("Alice");   // "Hello, Alice"

In this example, if the greet() function is called without an argument, it uses the default value "Guest".

Default Parameters with Expressions:

You can also use expressions as default values:

function multiply(a, b = 2) {
  return a * b;
}

console.log(multiply(5));  // 10 (uses default value for `b`)
console.log(multiply(5, 3));  // 15

29. What are arrow functions in JavaScript?

Arrow functions (introduced in ES6) provide a concise syntax for writing functions. They are particularly useful for anonymous functions and are often used in higher-order functions like map, filter, or reduce. Arrow functions have different behavior for the this keyword, making them easier to use in certain contexts.

const add = (a, b) => a + b;
console.log(add(2, 3));  // 5

Key Features:

  1. Concise Syntax: Arrow functions can omit return if the function body contains a single expression.
  2. Lexical this: Arrow functions do not have their own this binding; they inherit this from the surrounding context.

Example:

const person = {
  name: "Alice",
  greet: function() {
    setTimeout(() => {
      console.log(`Hello, ${this.name}`);
    }, 1000);
  }
};

person.greet();  // "Hello, Alice"

In this case, the arrow function inside setTimeout inherits the this value from the greet method, ensuring that it refers to the person object.

30. What is the difference between let and const?

Both let and const are used to declare variables in JavaScript and are part of the block scope (introduced in ES6), but they differ in their behavior:

  1. let:
    • Allows variable reassignment.
    • Variables declared with let are mutable.
    • It is block-scoped, meaning the variable is only accessible within the block where it’s defined.

Example:

let x = 5;
x = 10;  // Reassignable
console.log(x);  // 10

  1. const:
    • Does not allow reassignment. Once a value is assigned to a const variable, it cannot be changed.
    • It is block-scoped, like let.
    • const does not make the value immutable; it only makes the binding (the reference to the value) immutable. If the value is an object or array, its contents can still be modified.

Example:

const y = 5;
// y = 10;  // Error: Assignment to constant variable.

const obj = { name: "Alice" };
obj.name = "Bob";  // Works, since the object itself is mutable
console.log(obj);  // { name: "Bob" }

31. What are JavaScript modules?

JavaScript modules are a way to break up large pieces of JavaScript code into smaller, reusable units. Each module can encapsulate functionality, making your codebase more maintainable, readable, and modular. Modules help manage dependencies and scope, improving the structure and organization of your application.

Key Features of JavaScript Modules:

  1. Import and Export: JavaScript modules use the export and import keywords to share and consume functionality across different files.
  2. Encapsulation: A module has its own scope, so variables, functions, or classes declared inside a module are not globally accessible unless explicitly exported.
  3. Dynamic Imports: Modules can be imported dynamically, meaning that they can be loaded asynchronously when needed, improving performance for large applications.

Example:

Exporting from a module (in math.js):

// math.js
export function add(a, b) {
  return a + b;
}

export const PI = 3.14159;

Importing in another file (in app.js):

// app.js
import { add, PI } from './math.js';

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

Modern JavaScript (ES6) allows modules to be used with the import/export syntax, and older versions of JavaScript used CommonJS or AMD module formats. In browsers, you can use the <script type="module"> tag to load JavaScript modules.

32. What is DOM (Document Object Model) in JavaScript?

The DOM (Document Object Model) is an interface provided by browsers that represents the structure of an HTML or XML document as a tree of nodes. Each node corresponds to a part of the document (e.g., an element, an attribute, or text content). JavaScript can interact with the DOM to manipulate the content, structure, and style of a web page dynamically.

Key Concepts:

  1. DOM Tree: The document is represented as a tree structure, where each element, attribute, and piece of text is a node.
  2. Manipulating the DOM: JavaScript allows you to create, modify, and remove HTML elements, attributes, and text through the DOM API.

Example:

// Accessing the DOM elements
let heading = document.getElementById("myHeading");
heading.textContent = "New Heading Text";  // Change the text of the heading

// Creating new elements and appending them
let newDiv = document.createElement("div");
newDiv.textContent = "Hello, this is a new div!";
document.body.appendChild(newDiv);  // Add the new div to the page

33. What is the window object in JavaScript?

The window object is the global object in a browser environment. It represents the browser's window and provides access to various properties and methods that control the browser's behavior, the DOM, and the environment where JavaScript runs.

Key Features of the window Object:

  1. Global Scope: Variables and functions declared in the global scope are properties of the window object (e.g., window.alert() or window.location).
  2. DOM Access: The window object is where the DOM is accessible (e.g., window.document).
  3. Browser Controls: You can use the window object to interact with browser features such as navigating between pages, setting timeouts, or opening new tabs.

Example:

// Accessing properties of the window object
console.log(window.innerWidth);  // Get the width of the window
window.alert("Hello, world!");  // Show an alert box

// Window location
console.log(window.location.href);  // Get current URL

34. What is event delegation in JavaScript?

Event delegation is a technique in JavaScript where you attach a single event listener to a parent element instead of attaching individual event listeners to each child element. When an event occurs on a child element, it bubbles up to the parent, where it is handled. This allows for more efficient event handling, especially for dynamic content.

How it works:

  • Instead of adding an event listener to each child element (e.g., each button), you add one event listener to the parent, and use the event's target property to determine which child triggered the event.
  • This is particularly useful when dealing with dynamically added elements.

Example:

// Event delegation example
document.getElementById("parent").addEventListener("click", function(event) {
  if (event.target && event.target.matches("button")) {
    console.log("Button clicked:", event.target.textContent);
  }
});

// HTML structure
// <div id="parent">
//   <button>Click Me</button>
//   <button>Click Me Too</button>
// </div>

In this example, the event listener on the parent element listens for click events and checks if the clicked target is a button.

35. How do you handle errors in JavaScript?

In JavaScript, errors can be handled using try-catch blocks, or by using error-handling mechanisms such as promises or event listeners.

Key Approaches:

try-catch block: Used for synchronous error handling. The code that may throw an error is placed inside the try block, and if an error occurs, it is caught in the catch block.Example:

try {
  let result = riskyFunction();  // Function that might throw an error
  console.log(result);
} catch (error) {
  console.error("Error occurred:", error);
}

finally block: The finally block will execute whether or not an error occurred in the try block.Example:

try {
  let result = riskyFunction();
} catch (error) {
  console.error("Error:", error);
} finally {
  console.log("Cleanup code runs regardless of success or failure.");
}

Promises: When working with asynchronous code, promises can be used with .catch() to handle errors.Example:

fetch("someapi.com")
  .then(response => response.json())
  .catch(error => console.error("Fetch error:", error));

36. What are try-catch blocks in JavaScript?

The try-catch statement is used to handle exceptions (runtime errors) in JavaScript. It allows you to attempt to execute a block of code (try), and if an error occurs, it will be caught in the catch block, preventing the script from crashing.

Syntax:

try {
  // Code that may throw an error
} catch (error) {
  // Code to handle the error
}

Example:

try {
  let number = 10;
  let result = number / 0;  // This will not throw an error, but just return Infinity
  console.log(result);
} catch (error) {
  console.log("An error occurred:", error);
}

The catch block provides access to the error object, which can be used for debugging purposes or to provide a custom error message.

37. What is the Array.prototype.map() method in JavaScript?

The map() method in JavaScript is used to create a new array populated with the results of calling a provided function on every element in the calling array. The original array remains unchanged. This is part of array iteration methods that allow functional-style programming in JavaScript.

Syntax:

let newArray = array.map(function(element, index, array) {
  // return transformed element
});

Example:

let numbers = [1, 2, 3, 4, 5];
let squaredNumbers = numbers.map(num => num * num);
console.log(squaredNumbers);  // [1, 4, 9, 16, 25]

In this example, map() creates a new array with each number squared, without modifying the original numbers array.

38. How can you create a shallow copy of an array?

In JavaScript, creating a shallow copy of an array means creating a new array where the elements of the original array are copied, but the subarrays (if any) or objects inside the array are still references to the original ones. A shallow copy is different from a deep copy, where the entire structure (including nested objects) is copied recursively.

Methods to create a shallow copy:

Using the slice() method:

let arr = [1, 2, 3];
let copy = arr.slice();
console.log(copy);  // [1, 2, 3]

Using the spread operator (...):

let arr = [1, 2, 3];
let copy = [...arr];
console.log(copy);  // [1, 2, 3]

Using Array.from():

let arr = [1, 2, 3];
let copy = Array.from(arr);
console.log(copy);  // [1, 2, 3]

In each case, a shallow copy is created, and changes to the original array or its elements will not affect the shallow copy (unless they are objects or arrays themselves).

39. What is the difference between localStorage and sessionStorage?

Both localStorage and sessionStorage are part of the Web Storage API and allow storing data on the client's browser, but they differ in their lifespan and scope.

  1. localStorage:
    • Stores data persistently across browser sessions.
    • Data does not expire unless explicitly deleted.
    • Accessible across tabs and windows in the same domain.

Example:

localStorage.setItem('username', 'Alice');
let name = localStorage.getItem('username');
console.log(name);  // "Alice"
  1. sessionStorage:
    • Stores data only for the duration of the page session (i.e., as long as the browser tab is open).
    • Data is cleared when the tab or browser is closed.
    • Only accessible within the same tab/window.

Example:

sessionStorage.setItem('username', 'Bob');
let name = sessionStorage.getItem('username');
console.log(name);  // "Bob"

40. How do you define a class in JavaScript?

In JavaScript, classes are a template for creating objects with shared properties and methods. Classes were introduced in ES6 as syntactic sugar over the existing prototype-based inheritance model.

Syntax:

class ClassName {
  constructor(parameter1, parameter2) {
    this.property1 = parameter1;
    this.property2 = parameter2;
  }

  method1() {
    console.log(this.property1);
  }

  static staticMethod() {
    console.log("This is a static method");
  }
}

Example:

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  greet() {
    console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
  }

  static species() {
    console.log("Humans");
  }
}

let person1 = new Person("Alice", 30);
person1.greet();  // "Hello, my name is Alice and I am 30 years old."

Person.species();  // "Humans" (static method)

Classes in JavaScript support inheritance using the extends keyword, allowing one class to inherit properties and methods from another class.

Intermediate (Q&A)

1. What is hoisting in JavaScript?

Hoisting is a JavaScript mechanism where variable and function declarations are moved to the top of their containing scope during the compilation phase, before the code is executed. This means that functions and variables can be used before they are formally declared in the code.

How hoisting works:

  1. Function Declarations: Hoisted to the top and can be called before their declaration in the code.
  2. Var Declarations: Variables declared with var are hoisted, but their initialization remains at the point of declaration.
  3. Let and Const Declarations: These variables are also hoisted, but they remain in a "temporal dead zone" from the start of the block until the point they are declared.

Examples:

Function Hoisting:

hoistedFunction();  // Works because function declarations are hoisted

function hoistedFunction() {
  console.log("I am hoisted!");
}

Variable Hoisting:

console.log(myVar);  // undefined, not ReferenceError, because `myVar` is hoisted but not initialized
var myVar = 5;
console.log(myVar);  // 5

Let and Const Hoisting:

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

In summary, hoisting allows variables and functions to be used before their actual declaration, but with subtle differences depending on how they are declared (var, let, const, function).

2. Explain the bind(), call(), and apply() methods in JavaScript.

These three methods are used to explicitly set the value of the this keyword within a function. They allow you to control the context in which the function is executed.

  1. bind():
    • The bind() method creates a new function that, when called, has its this value set to the provided value.
    • The original function is not immediately executed; instead, a new function is returned.

Syntax:

const boundFunction = originalFunction.bind(thisArg);

Example:

function greet() {
  console.log(`Hello, ${this.name}`);
}

const person = { name: 'Alice' };
const greetAlice = greet.bind(person);
greetAlice();  // Hello, Alice
  1. call():
    • The call() method calls a function immediately and explicitly sets its this value.
    • You can also pass arguments to the function directly.

Syntax:

originalFunction.call(thisArg, arg1, arg2, ...);

Example:

function greet(age) {
  console.log(`Hello, ${this.name}. You are ${age} years old.`);
}

const person = { name: 'Bob' };
greet.call(person, 25);  // Hello, Bob. You are 25 years old.
  1. apply():
    • The apply() method is similar to call(), but the arguments are passed as an array (or array-like object).
    • It immediately invokes the function.

Syntax:

originalFunction.apply(thisArg, [argsArray]);

Example:

function greet(age, occupation) {
  console.log(`Hello, ${this.name}. You are ${age} years old and work as a ${occupation}.`);
}

const person = { name: 'Charlie' };
greet.apply(person, [30, 'developer']);  // Hello, Charlie. You are 30 years old and work as a developer.

3. What is the difference between null and undefined?

null and undefined are both primitive values in JavaScript, but they represent different concepts:

  1. undefined:
    • A variable that has been declared but not assigned a value is automatically assigned the value undefined.
    • It also represents the return value of functions that don't explicitly return a value.

Example:

let x;
console.log(x);  // undefined (x is declared but not initialized)

function noReturn() {}
console.log(noReturn());  // undefined (no return value)

  1. null:
    • Represents an intentional absence of any object value.
    • It is explicitly assigned by the programmer to indicate that a variable should have no value or object.

Example:

let person = null;  // null is explicitly assigned
console.log(person);  // null

Key Difference:

  • undefined is a type itself (undefined), whereas null is an object type (although it is a primitive).
  • undefined usually implies a lack of assignment, while null is used to indicate intentional absence or emptiness.

4. What is the difference between function scope and block scope?

In JavaScript, the scope defines the accessibility of variables, functions, and objects in different parts of the code. JavaScript has two types of scoping:

  1. Function Scope:
    • Variables declared with var are function-scoped, meaning they are only accessible within the function where they are declared.
    • If declared outside of any function, they have global scope.

Example:

function example() {
  var a = 5;  // function-scoped
  console.log(a);  // Works inside the function
}

console.log(a);  // Error: a is not defined (if accessed outside)
  1. Block Scope:
    • Variables declared with let and const are block-scoped, meaning they are only accessible within the block (e.g., within a loop or an if statement) in which they are declared.

Example:

if (true) {
  let x = 10;  // block-scoped
  const y = 20; // block-scoped
  console.log(x, y);  // 10, 20
}

console.log(x, y);  // Error: x is not defined (or y is not defined

5. What are the different ways to clone an object in JavaScript?

There are several ways to clone an object in JavaScript. These methods vary based on whether you want to create a shallow copy or a deep copy.

  1. Shallow Copy:
    • Using Object.assign(): Creates a shallow copy, meaning nested objects are still referenced.
let original = { name: "Alice", address: { city: "Wonderland" } };
let clone = Object.assign({}, original);
console.log(clone);  // { name: "Alice", address: { city: "Wonderland" } }


    • Using the Spread Operator (...):
let original = { name: "Bob", address: { city: "Gotham" } };
let clone = { ...original };
console.log(clone);  // { name: "Bob", address: { city: "Gotham" } }
  1. Deep Copy:
    • Using JSON.parse() and JSON.stringify(): This is a simple way to create a deep copy, but it only works with objects that can be serialized (i.e., no functions or circular references).
let original = { name: "Charlie", address: { city: "Metropolis" } };
let deepClone = JSON.parse(JSON.stringify(original));
console.log(deepClone);  // { name: "Charlie", address: { city: "Metropolis" } }
  • Using a Custom Function or Libraries: For complex cases (e.g., with functions or special objects), you can use libraries like lodash or write a custom deep cloning function.

6. What is a promise chain and how does it work?

A promise chain is a sequence of .then() or .catch() methods that are chained together to handle multiple asynchronous operations in sequence. Each .then() method returns a new promise, allowing you to chain further operations.

How it works:

  1. Each .then() handles the result of the previous promise and returns a new promise.
  2. .catch() is used to handle any errors that occur in the chain.
  3. Promise chains help avoid "callback hell" and improve readability.

Example:

fetch('https://api.example.com')
  .then(response => response.json())  // First .then - handles response
  .then(data => {
    console.log(data);  // Second .then - handles the data
  })
  .catch(error => {
    console.log('Error:', error);  // .catch - handles any error
  });

7. What are the advantages and disadvantages of using promises over callbacks?

Advantages of Promises:

  1. Avoid Callback Hell: Promises allow chaining, which helps avoid deeply nested callbacks.
  2. Error Handling: Promises provide a cleaner and more consistent way to handle errors using .catch().
  3. Better Composition: Promises support chaining and returning new promises, allowing for better composition of asynchronous operations.

Disadvantages of Promises:

  1. Complexity in Error Handling: While promises provide better error handling than callbacks, nested promises can still become complex.
  2. Not Always Needed: For very simple asynchronous tasks, promises may introduce unnecessary complexity compared to callbacks.

8. What are the different ways to handle asynchronous code in JavaScript?

  1. Callbacks:
    • Functions that are passed as arguments to other functions and executed later (e.g., after an asynchronous task completes).
    • Drawback: Can lead to "callback hell" (deeply nested callbacks).
  2. Promises:
    • Used to handle asynchronous operations in a cleaner way, allowing chaining with .then(), .catch(), and .finally().
  3. Async/Await:
    • Syntactic sugar built on top of promises. Makes asynchronous code look and behave like synchronous code.

Example:

async function fetchData() {
  let response = await fetch('https://api.example.com');
  let data = await response.json();
  console.log(data);
}
fetchData();

9. What is a JavaScript closure and how do you use it?

A closure is a function that "remembers" its lexical environment, even when it's executed outside of its original scope. This means that a closure can access variables from its containing function even after the containing function has finished execution.

Example:

function outer() {
  let count = 0;
  
  return function inner() {
    count++;
    console.log(count);
  }
}

const counter = outer();
counter();  // 1
counter();  // 2

In this example, the inner function is a closure that has access to the count variable from the outer function, even though outer() has already returned.

10. Explain the concept of the this keyword in different contexts.

In JavaScript, the this keyword refers to the context in which a function is called. It behaves differently in various scenarios:

Global Context: In the global execution context (outside any function), this refers to the global object (window in browsers).

console.log(this);  // window (in a browser)

Object Method: When a function is called as a method of an object, this refers to the object itself.

const person = {
  name: 'Alice',
  greet: function() {
    console.log(this.name);  // this refers to the person object
  }
};
person.greet();  // Alice

Function Context: In a regular function call, this refers to the global object in non-strict mode (window in browsers), and undefined in strict mode.

function show() {
  console.log(this);  // window (in non-strict mode)
}
show();

Arrow Functions: Arrow functions do not have their own this; they inherit this from their surrounding context.

const person = {
  name: 'Bob',
  greet: function() {
    setTimeout(() => {
      console.log(this.name);  // this refers to the person object, because of lexical scoping
    }, 1000);
  }
};
person.greet();  // Bob

In summary, this refers to the context in which a function is executed, and its behavior depends on whether the function is in the global scope, part of an object, or inside a constructor or an arrow function.

11. What is the purpose of Object.create() in JavaScript?

The Object.create() method is used to create a new object with a specified prototype object and optional properties. It's a powerful tool for working with inheritance in JavaScript, as it allows you to set up an object's prototype chain explicitly.

Syntax:

Object.create(proto, propertiesObject);

  • proto: The prototype object (or null) to which the new object will be linked.
  • propertiesObject: Optional. An object whose properties are added to the new object.

Use Cases:

Creating an object with a custom prototype:

const animal = {
  speak: function() {
    console.log('Animal sound');
  }
};

const dog = Object.create(animal);
dog.speak();  // "Animal sound" (dog inherits speak from animal)

  1. Avoiding inheritance pitfalls: Object.create() allows you to explicitly define the prototype and avoid the problems that arise when using constructor functions or new syntax.

12. What is a JavaScript singleton?

A singleton is a design pattern that ensures a class or object has only one instance, while providing a global access point to that instance. This pattern is often used when you want to manage a resource, like a configuration or a connection pool, and ensure there is only one instance managing that resource.

Example:

const Singleton = (function() {
  let instance;

  function createInstance() {
    return { value: 'I am the only instance' };
  }

  return {
    getInstance: function() {
      if (!instance) {
        instance = createInstance();
      }
      return instance;
    }
  };
})();

const singleton1 = Singleton.getInstance();
const singleton2 = Singleton.getInstance();

console.log(singleton1 === singleton2);  // true

In this example, Singleton.getInstance() ensures that only one instance of the object is created, regardless of how many times it is called.

13. What are JavaScript modules and how do you use them?

JavaScript modules allow you to break up your code into smaller, reusable pieces, making it easier to manage and maintain. Modules provide a way to export and import functionality between different files.

How to use JavaScript modules:

Exporting: You export functions, objects, or variables to make them available for import in other files.

// math.js
export function add(a, b) {
  return a + b;
}

export const pi = 3.14;

Importing: You import the exported entities from other modules.

// app.js
import { add, pi } from './math.js';

console.log(add(2, 3));  // 5
console.log(pi);  // 3.14

Default Export: You can export a single value as the default export of a module.

// logger.js
export default function log(message) {
  console.log(message);
}

// app.js
import log from './logger.js';
log("This is a log message");

JavaScript modules were introduced in ES6 and are now supported in modern browsers and Node.js. They allow for better code organization and modularity.

14. What is destructuring in JavaScript?

Destructuring is a syntax feature in JavaScript that allows you to unpack values from arrays or properties from objects into distinct variables. It's a concise and readable way to assign variables from complex data structures.

Array Destructuring:

const arr = [1, 2, 3, 4];
const [first, second, third] = arr;
console.log(first, second, third);  // 1, 2, 3

Object Destructuring:

const person = { name: 'Alice', age: 25 };
const { name, age } = person;
console.log(name, age);  // Alice, 25

You can also use default values, rest syntax, and rename variables when destructuring.

Example with default values and renaming:

const person = { name: 'Bob' };
const { name, age = 30 } = person;
console.log(name, age);  // Bob, 30

15. What is the spread operator (...) in JavaScript?

The spread operator (...) allows an iterable (like an array or object) to be expanded into individual elements or properties. It's often used to clone objects or arrays, combine multiple arrays, or pass individual arguments to functions.

Examples:

Expanding an array:

const numbers = [1, 2, 3];
const newNumbers = [...numbers, 4, 5];
console.log(newNumbers);  // [1, 2, 3, 4, 5]

Merging arrays:

const arr1 = [1, 2];
const arr2 = [3, 4];
const mergedArr = [...arr1, ...arr2];
console.log(mergedArr);  // [1, 2, 3, 4]

Cloning an object:

const person = { name: 'Alice', age: 25 };
const clonedPerson = { ...person };
console.log(clonedPerson);  // { name: 'Alice', age: 25 }

Using with function arguments:

function greet(name, age) {
  console.log(`Hello ${name}, you are ${age} years old.`);
}
const args = ['Alice', 25];
greet(...args);  // Hello Alice, you are 25 years old.

16. What is the rest parameter (...) in JavaScript?

The rest parameter (...) allows you to collect multiple arguments into a single array parameter. It is used in function definitions to handle variable numbers of arguments.

Example:

function sum(...numbers) {
  return numbers.reduce((total, num) => total + num, 0);
}

console.log(sum(1, 2, 3));  // 6
console.log(sum(4, 5, 6, 7));  // 22

In this case, numbers is an array containing all the arguments passed to the function. The rest parameter can only be the last parameter in a function.

17. Explain the concept of deep copy vs. shallow copy.

A shallow copy creates a new object or array, but the elements inside it (if they are references to objects or arrays) are still pointing to the same memory locations as the original object. Changes to the nested objects will reflect in both the original and the copied object.

A deep copy creates a new object or array and recursively copies all nested objects or arrays, ensuring that the copied object is completely independent of the original.

Shallow Copy Example:

let original = { name: 'Alice', details: { age: 25 } };
let shallowCopy = { ...original };
shallowCopy.details.age = 26;

console.log(original.details.age);  // 26 (shallow copy affects original)

Deep Copy Example:

let original = { name: 'Alice', details: { age: 25 } };
let deepCopy = JSON.parse(JSON.stringify(original));
deepCopy.details.age = 26;

console.log(original.details.age);  // 25 (deep copy does not affect original)

18. How do you handle errors asynchronously in JavaScript?

In JavaScript, errors in asynchronous code can be handled using callbacks, promises, or async/await:

Using Callbacks:

function fetchData(callback) {
  setTimeout(() => {
    try {
      throw new Error('Data fetch error');
    } catch (error) {
      callback(error, null);
    }
  }, 1000);
}

fetchData((error, data) => {
  if (error) {
    console.log('Error:', error);
  } else {
    console.log('Data:', data);
  }
});

Using Promises:

function fetchData() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(new Error('Data fetch error'));
    }, 1000);
  });
}

fetchData()
  .then(data => console.log(data))
  .catch(error => console.log('Error:', error));

Using Async/Await:

async function fetchData() {
  try {
    throw new Error('Data fetch error');
  } catch (error) {
    console.log('Error:', error);
  }
}

fetchData();

Asynchronous error handling allows you to handle failures gracefully in asynchronous code and ensures that your program doesn't crash unexpectedly.

19. What is the EventEmitter class in Node.js?

The EventEmitter class in Node.js is part of the events module and is used to handle and manage events in an application. It allows you to create custom events and register listeners that are triggered when the event is emitted.

Usage:

Creating an EventEmitter instance:

const EventEmitter = require('events');
const myEmitter = new EventEmitter();

Emitting an event:

myEmitter.emit('event', 'some data');

Listening to an event:

myEmitter.on('event', (data) => {
  console.log('An event occurred with data:', data);
});

Example:

const EventEmitter = require('events');
const myEmitter = new EventEmitter();

myEmitter.on('greet', (name) => {
  console.log(`Hello, ${name}!`);
});

myEmitter.emit('greet', 'Alice');  // Hello, Alice!

This pattern is widely used in Node.js for event-driven programming, especially when dealing with streams, I/O operations, and server requests.

20. How does the setInterval() function work in JavaScript?

The setInterval() function is used to repeatedly execute a function at specified intervals (in milliseconds). It returns a unique identifier that can be used to cancel the interval using clearInterval().

Syntax:

let intervalId = setInterval(function, delay);

  • function: The function to execute.
  • delay: The time interval (in milliseconds) between each execution.

Example:

let count = 0;
let intervalId = setInterval(() => {
  count++;
  console.log(count);
  if (count === 5) {
    clearInterval(intervalId);  // Stop the interval after 5 counts
  }
}, 1000);  // Executes every 1000ms (1 second)

In this example, the function logs the value of count every second, and it stops after logging the number 5. clearInterval() is used to stop the interval.

21. What are template literals and how are they useful in JavaScript?

Template literals are a feature introduced in ES6 that allow for string interpolation, multi-line strings, and embedded expressions within string literals. They are defined using backticks (`\) instead of single or double quotes.

Advantages of Template Literals:

String Interpolation: You can easily embed variables and expressions inside strings.

const name = 'Alice';
const greeting = `Hello, ${name}!`;  // Interpolation
console.log(greeting);  // Hello, Alice!

Multi-line Strings: Template literals preserve line breaks and indentation.

const multiLineString = `
  This is line 1
  This is line 2
  This is line 3
`;
console.log(multiLineString);

Expression Embedding: You can embed any valid JavaScript expression inside ${}.

const a = 5, b = 10;
const result = `Sum of a and b is: ${a + b}`;  // Expression inside template literal
console.log(result);  // Sum of a and b is: 15

Template literals make string handling more concise, readable, and flexible compared to traditional string concatenation.

22. Explain the difference between == and === in JavaScript.

In JavaScript, == and === are comparison operators, but they differ in how they compare values:

  1. == (Loose Equality / Abstract Equality):
    • It compares values only, but performs type coercion if the values are of different types.

Example:

console.log(5 == '5');  // true (type coercion, '5' is converted to a number)
console.log(true == 1);  // true (true is coerced to 1)
  1. === (Strict Equality):
    • It compares both value and type, without any type coercion.

Example:

console.log(5 === '5');  // false (different types, number vs. string)
console.log(true === 1);  // false (boolean vs. number)

In general, it's recommended to use === to avoid unexpected behavior due to type coercion.

23. What are closures used for in JavaScript?

A closure is a function that has access to its own scope, the scope of the outer function, and the global scope. Closures are a powerful feature in JavaScript and are primarily used in the following cases:

  1. Data Encapsulation:
    • Closures allow for private variables. You can hide variables and only expose specific functionality.
function counter() {
  let count = 0;
  return {
    increment: function() {
      count++;
      return count;
    },
    decrement: function() {
      count--;
      return count;
    },
    getCount: function() {
      return count;
    }
  };
}

const myCounter = counter();
console.log(myCounter.increment());  // 1
console.log(myCounter.getCount());   // 1
  1. Event Handlers and Callbacks:
    • Closures are useful when you need to maintain state in asynchronous callbacks or event handlers.
function makeCounter() {
  let count = 0;
  return function() {
    count++;
    console.log(count);
  };
}

const counter1 = makeCounter();
counter1();  // 1
counter1();  // 2

  1. Function Factories:
    • You can use closures to create functions dynamically based on parameters.
function multiplier(factor) {
  return function(number) {
    return number * factor;
  };
}

const double = multiplier(2);
console.log(double(5));  // 10

Closures allow for powerful patterns such as private data and function factories, making code more modular and maintainable.

24. How do you create a promise in JavaScript?

A promise in JavaScript represents the eventual completion (or failure) of an asynchronous operation and its resulting value. You can create a promise using the new Promise() constructor.

Syntax:

const myPromise = new Promise((resolve, reject) => {
  // Asynchronous operation
  let success = true;
  if (success) {
    resolve('Operation successful!');
  } else {
    reject('Operation failed!');
  }
});

Example:

const myPromise = new Promise((resolve, reject) => {
  let success = true;
  if (success) {
    resolve('Task completed!');
  } else {
    reject('Task failed!');
  }
});

myPromise
  .then((message) => console.log(message))   // Success handler
  .catch((error) => console.log(error));     // Error handler

In this example, the promise is resolved (i.e., successful) if the success variable is true, and it is rejected otherwise.

25. What is the Array.prototype.reduce() method in JavaScript?

The reduce() method is used to reduce an array to a single value by executing a provided function on each element, accumulating the result.

Syntax:

arr.reduce(callback(accumulator, currentValue[, index, array]), initialValue);
  • callback: A function that is called on each element of the array. It takes four arguments: accumulator, currentValue, index, and array.
  • initialValue (optional): The initial value to start the accumulation (if not provided, it defaults to the first element of the array).

Example:

const numbers = [1, 2, 3, 4];
const sum = numbers.reduce((acc, num) => acc + num, 0);
console.log(sum);  // 10 (1 + 2 + 3 + 4)

In this example, reduce() accumulates the sum of the numbers in the array.

26. What is event bubbling and event capturing in JavaScript?

In JavaScript, event propagation occurs in two phases: bubbling and capturing (also known as trickling).

  1. Event Bubbling:
    • The event starts at the target element and bubbles up to the root (document).
    • This is the default behavior of event propagation.

Example:

document.getElementById('parent').addEventListener('click', () => {
  console.log('Parent clicked');
});

document.getElementById('child').addEventListener('click', () => {
  console.log('Child clicked');
});

// When clicking the child element, the log will show:
// "Child clicked"
// "Parent clicked" (event bubbles from child to parent)

  1. Event Capturing (Trickling):
    • The event starts from the root element and trickles down to the target element.
    • Capturing is less commonly used than bubbling.

Example (set capturing to true):

document.getElementById('parent').addEventListener('click', () => {
  console.log('Parent clicked');
}, true);  // true enables capturing

document.getElementById('child').addEventListener('click', () => {
  console.log('Child clicked');
});

// When clicking the child element, the log will show:
// "Parent clicked"
// "Child clicked" (event captures from parent to child)

27. What is the difference between synchronous and asynchronous execution in JavaScript?

  • Synchronous Execution:
    • In synchronous execution, each task is executed one after the other, and the next task waits for the current task to complete.
    • This can lead to blocking and a less responsive user experience, especially in I/O-bound tasks (e.g., network requests, file reading).
console.log('First');
console.log('Second');  // This will be executed only after the first log

  • Asynchronous Execution:
    • In asynchronous execution, tasks can be initiated and executed concurrently, without blocking the main thread. The results are usually handled using callbacks, promises, or async/await.
    • This allows JavaScript to handle non-blocking operations like I/O without freezing the main execution.
setTimeout(() => {
  console.log('Second');  // This will be executed after 1000ms
}, 1000);
console.log('First');

28. What is async/await and how does it simplify handling asynchronous code?

async and await are syntactic sugar built on top of promises that make handling asynchronous code look and behave more like synchronous code, making it easier to read and manage.

  • async: Defines a function as asynchronous, which means it will always return a promise.
  • await: Pauses the execution of the async function until the promise is resolved or rejected.

Example:

async function fetchData() {
  const response = await fetch('https://jsonplaceholder.typicode.com/posts');
  const data = await response.json();
  console.log(data);
}

fetchData();

In this example, await pauses the function until the fetch request resolves, making the code more readable than using then() with promises.

29. What is the purpose of setImmediate() in Node.js?

The setImmediate() function in Node.js is used to schedule a callback to be executed in the next iteration of the event loop (after the current event loop cycle completes). It’s typically used for deferring execution of a function to ensure it runs after the I/O events and timers.

Example:

console.log('Start');
setImmediate(() => {
  console.log('Immediate');
});
console.log('End');

Output:

Start
End
Immediate

In this example, setImmediate() runs the callback after the current event loop cycle, which is why "Immediate" is logged after "End".

30. How do you prevent default behavior of an event in JavaScript?

You can prevent the default behavior of an event using the preventDefault() method of the event object. This is commonly used when handling form submissions, anchor tags, or other events where you want to prevent the browser's default action.

Example:

document.getElementById('myForm').addEventListener('submit', function(event) {
  event.preventDefault();
  console.log('Form submission prevented!');
});

In this case, preventDefault() stops the form from being submitted when the user clicks the submit button.

31. How do you bind an event handler to an element in JavaScript?

In JavaScript, you can bind an event handler to an element in several ways. The most common methods are:

  1. Using addEventListener():
    • This is the preferred way to bind event handlers because it allows you to attach multiple event listeners to the same event and provides more flexibility.
const button = document.getElementById('myButton');
button.addEventListener('click', function(event) {
  alert('Button clicked!');
});

  1. In this example, we add a click event listener to the button element, which triggers the provided function when the button is clicked.
  2. Using onclick property:
    • This method can only attach one event handler at a time and will override any previous handler attached to the same event.
const button = document.getElementById('myButton');
button.onclick = function() {
  alert('Button clicked!');
};

  1. The onclick property is useful for simple scenarios but is less flexible compared to addEventListener().

32. What is a JavaScript proxy and how is it used?

A JavaScript Proxy is a special object that allows you to define custom behavior for fundamental operations (like property lookup, assignment, enumeration, etc.) on an object. It can intercept and redefine operations for getting, setting, deleting properties, and more.

Creating a Proxy:

You create a proxy using the Proxy constructor, which takes two arguments:

  1. The target object (the object you want to wrap).
  2. The handler object (defines traps for intercepting operations).

Example:

const target = {
  message: 'Hello, world!',
};

const handler = {
  get: function(target, prop) {
    if (prop in target) {
      return target[prop];
    } else {
      return `Property ${prop} not found!`;
    }
  }
};

const proxy = new Proxy(target, handler);
console.log(proxy.message);  // "Hello, world!"
console.log(proxy.nonExistent);  // "Property nonExistent not found!"

Uses of Proxy:

  • Data validation
  • Debugging (logging accesses to properties)
  • Trapping function calls
  • Implementing access control and privacy features

33. Explain the concept of call stack in JavaScript.

The call stack is a mechanism in JavaScript that keeps track of function calls. It follows the Last In, First Out (LIFO) principle, meaning that the most recently called function is executed first, and once it completes, it is popped off the stack.

How it works:

  1. Each time a function is invoked, a new frame is added to the stack.
  2. The JavaScript engine executes the function on top of the stack.
  3. When the function execution is finished, it is removed from the stack, and the next function (if any) is executed.

Example:

function first() {
  second();
}

function second() {
  third();
}

function third() {
  console.log('End of stack');
}

first();  // Starts the chain of calls

// Call stack:
// third() -> second() -> first()

In this example, the function calls first(), which calls second(), which in turn calls third(). Once third() completes, it’s removed from the stack, then second(), and finally first().

34. What is the difference between bind() and call()?

Both bind() and call() are methods in JavaScript used to invoke functions with a specific this value, but they differ in their behavior:

  1. bind():
    • Returns a new function that, when called, has the specified this value and any provided arguments.
    • Does not invoke the function immediately. Instead, it returns a new function that can be invoked later.
function greet() {
  console.log(`Hello, ${this.name}`);
}

const person = { name: 'Alice' };
const boundGreet = greet.bind(person);
boundGreet();  // "Hello, Alice"

  1. call():
    • Immediately invokes the function with the specified this value and arguments provided as individual values (not an array).
function greet(city, country) {
  console.log(`Hello, ${this.name} from ${city}, ${country}`);
}

const person = { name: 'Alice' };
greet.call(person, 'Paris', 'France');  // "Hello, Alice from Paris, France"

Key Differences:

  • bind() returns a function, while call() immediately invokes the function.
  • call() requires the arguments to be passed individually, while bind() passes them later when the returned function is called.

35. What are JavaScript generators and how do they work?

A JavaScript generator is a function that can be paused and resumed, allowing you to produce a sequence of values on demand. They are defined using the function* syntax and use the yield keyword to pause execution.

Syntax:

function* myGenerator() {
  yield 1;
  yield 2;
  yield 3;
}

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

In this example:

  • The generator function is paused at each yield statement.
  • Each call to gen.next() resumes execution and returns the next value.

Generators are useful for implementing iterators, managing asynchronous code with yield (in the case of async/await), and working with large datasets lazily.

36. What is memoization and how is it implemented in JavaScript?

Memoization is an optimization technique where you store the results of expensive function calls and reuse them when the same inputs occur again. This can significantly improve performance, especially for functions with repetitive calculations (e.g., recursive algorithms).

Example:

function memoize(fn) {
  const cache = {};
  return function(...args) {
    const key = JSON.stringify(args);
    if (key in cache) {
      return cache[key];
    } else {
      const result = fn(...args);
      cache[key] = result;
      return result;
    }
  };
}

const factorial = memoize(function(n) {
  if (n === 0) return 1;
  return n * factorial(n - 1);
});

console.log(factorial(5));  // 120 (computed)
console.log(factorial(5));  // 120 (retrieved from cache)

In this example, the factorial function is memoized, so subsequent calls with the same argument return the cached result rather than recalculating it.

37. What are the main differences between ES5 and ES6?

ES5 (ECMAScript 5) and ES6 (ECMAScript 6, also known as ECMAScript 2015) introduced significant changes and improvements to JavaScript. Key differences include:

  1. Variable Declarations:
    • ES5: var is the only way to declare variables.
    • ES6: let and const introduced, with block-level scoping.
  2. Arrow Functions:
    • ES5: Functions are declared using the function keyword.
    • ES6: Arrow functions provide a shorter syntax and lexical this.
// ES6
const add = (a, b) => a + b;

  1. Classes:
    • ES5: Classes are implemented using constructor functions and prototypes.
    • ES6: Classes are introduced as a syntactic sugar over prototype-based inheritance.
  2. Template Literals:
    • ES6: Introduced template literals for string interpolation and multi-line strings.
const name = 'Alice';
const greeting = `Hello, ${name}!`;

  1. Modules:
    • ES5: No native support for modules; developers use libraries like CommonJS or AMD.
    • ES6: Native support for modules using import and export.

38. How do you create a method for an object in JavaScript?

In JavaScript, you can create methods for an object by assigning functions as properties of the object.

Example 1: Method inside an object literal

const person = {
  name: 'Alice',
  greet: function() {
    console.log(`Hello, my name is ${this.name}`);
  }
};

person.greet();  // "Hello, my name is Alice"

Example 2: Using ES6 shorthand for methods

const person = {
  name: 'Alice',
  greet() {
    console.log(`Hello, my name is ${this.name}`);
  }
};

person.greet();  // "Hello, my name is Alice"

In both examples, greet is a method attached to the person object.

39. What is the difference between Object.freeze() and Object.seal()?

  • Object.freeze():
    • Prevents any changes to the object's properties.
    • Cannot add, delete, or modify the values of the object's properties (shallow immutability).
const obj = { name: 'Alice' };
Object.freeze(obj);
obj.name = 'Bob';  // Does nothing, name remains 'Alice'

  • Object.seal():
    • Prevents adding or deleting properties, but allows modification of existing property values.
const obj = { name: 'Alice' };
Object.seal(obj);
obj.name = 'Bob';  // Allowed, name is now 'Bob'
delete obj.name;   // Does nothing, property cannot be deleted

40. Explain the importance of immutability in JavaScript.

Immutability refers to the concept that once a value is assigned to a variable or object, it cannot be changed. This is an important concept in JavaScript for several reasons:

  1. Predictability: Immutable data makes the code more predictable and easier to debug since values cannot change unexpectedly.
  2. Performance: With immutable data, certain optimizations, like memoization or shallow comparisons, are possible because the object won't change during execution.
  3. Concurrency: Immutable objects avoid issues related to shared state and data corruption in asynchronous or multi-threaded environments.
  4. Functional Programming: Immutability is a core concept in functional programming, encouraging the use of pure functions.

Example:

const obj = Object.freeze({ name: 'Alice' });
obj.name = 'Bob';  // Throws an error or does nothing, depending on the environment

Using immutability, changes can be tracked more easily, and the program becomes less prone to bugs related to unexpected changes in state.

Experienced (Q&A)

1. What is the event loop in JavaScript and how does it work?

The event loop is a fundamental concept in JavaScript's concurrency model. JavaScript is single-threaded, meaning only one task can be executed at a time. The event loop ensures that non-blocking I/O operations (like timers, HTTP requests, or user interactions) are processed without freezing the application.

How it works:

  1. Call Stack: JavaScript executes code from the call stack. Each function call is placed onto the stack, and once a function completes, it's popped off the stack.
  2. Callback Queue: When an asynchronous task (e.g., setTimeout(), network request, or event) completes, its callback is placed in the callback queue.
  3. Event Loop: The event loop constantly monitors the call stack and the callback queue. If the call stack is empty, it pushes the first task in the callback queue to the call stack for execution.

Example:

console.log('Start');

setTimeout(function() {
  console.log('Timeout');
}, 0);

console.log('End');

// Output:
// Start
// End
// Timeout

In this example, even though setTimeout is set to 0 milliseconds, the callback is pushed to the callback queue after the synchronous code completes, so "End" is logged before "Timeout".

2. What is a JavaScript decorator, and how would you implement one?

A decorator in JavaScript is a function that can be used to modify or extend the behavior of another function or method. Decorators are often used to add functionality to an object, class, or method without directly altering their code.

Implementation (Method Decorator Example):

JavaScript doesn't have built-in support for decorators, but they can be implemented using higher-order functions. Decorators are common in frameworks like Angular.

Example:

// Basic decorator function
function logExecution(target, name, descriptor) {
  const originalMethod = descriptor.value;

  descriptor.value = function(...args) {
    console.log(`Calling ${name} with arguments: ${args}`);
    const result = originalMethod.apply(this, args);
    console.log(`${name} returned: ${result}`);
    return result;
  };

  return descriptor;
}

class MyClass {
  @logExecution
  add(a, b) {
    return a + b;
  }
}

const myClass = new MyClass();
myClass.add(2, 3);  // Logs the execution details

In this example, the logExecution decorator logs the arguments and result of the method it decorates.

3. How would you optimize the performance of a JavaScript application?

Optimizing JavaScript performance can involve various strategies:

  1. Reduce DOM Manipulation: Minimizing the number of direct DOM manipulations can drastically improve performance, especially in complex applications. Use document fragments, event delegation, or frameworks like React that optimize rendering.
  2. Lazy Loading: Load resources only when they are needed, especially for large libraries, images, or videos, to reduce the initial load time.
  3. Asynchronous Programming: Use asynchronous techniques like Promises, async/await, or Web Workers to avoid blocking the main thread with long-running tasks.
  4. Debouncing and Throttling: These techniques are useful when handling events like resize, scroll, or keypress to prevent excessive function calls.
    • Debouncing delays the execution of a function until a certain time has passed since the last event.
    • Throttling limits the number of times a function can be called over a period.
  5. Minification and Bundling: Minify JavaScript files and bundle them into a single file to reduce the number of network requests and the size of the files.
  6. Memoization: Cache results of expensive function calls to avoid recalculating the same result multiple times (especially in recursive functions).
  7. Web Workers: Offload CPU-intensive tasks to separate threads using Web Workers, preventing the main thread from becoming blocked.
  8. Use of Efficient Algorithms: Optimize algorithms (e.g., avoiding nested loops) and data structures for speed, using tools like map(), filter(), or reduce() for functional operations instead of traditional loops.

4. What is the difference between a stack and a queue in JavaScript?

A stack and a queue are both data structures, but they differ in how elements are added and removed:

  1. Stack: Follows the Last In, First Out (LIFO) principle. The last element added is the first one to be removed.
    • Common methods: push(), pop()

Example:

const stack = [];
stack.push(1);
stack.push(2);
console.log(stack.pop());  // 2
console.log(stack.pop());  // 1

  1. Queue: Follows the First In, First Out (FIFO) principle. The first element added is the first one to be removed.
    • Common methods: enqueue() (or push() in JavaScript), dequeue() (or shift() in JavaScript)

Example:

const queue = [];
queue.push(1);
queue.push(2);
console.log(queue.shift());  // 1
console.log(queue.shift());  // 2

The choice between stack and queue depends on how you need to process the data — stack for last-in-first-out scenarios (e.g., undo operations) and queue for first-in-first-out scenarios (e.g., task scheduling).

5. How would you implement memoization in JavaScript for a performance-critical application?

Memoization is a technique used to optimize performance by caching the results of expensive function calls and returning the cached result when the same inputs occur again. This can be implemented using a JavaScript object or Map.

Example:

function memoize(fn) {
  const cache = new Map();
  return function(...args) {
    const key = JSON.stringify(args);
    if (cache.has(key)) {
      return cache.get(key);  // Return cached result
    }
    const result = fn(...args);
    cache.set(key, result);  // Cache the result
    return result;
  };
}

// Expensive function
function factorial(n) {
  if (n === 0) return 1;
  return n * factorial(n - 1);
}

const memoizedFactorial = memoize(factorial);

console.log(memoizedFactorial(5));  // First time, computes and caches
console.log(memoizedFactorial(5));  // Second time, retrieves from cache

In this example, the memoize function caches the result of factorial() to avoid redundant calculations.

6. What are JavaScript WeakMaps and WeakSets?

  1. WeakMap:
    • A WeakMap is a collection of key-value pairs where the keys are objects, and the values can be any value. The key-value pairs in a WeakMap are weakly referenced, meaning the garbage collector can remove the key-value pair if the object key is no longer in use.

Example:

const weakMap = new WeakMap();
let obj = {};
weakMap.set(obj, 'value');
console.log(weakMap.get(obj));  // 'value'
obj = null;  // Now the object is eligible for garbage collection

  1. Use case: WeakMap is useful for storing private data or metadata for objects without preventing garbage collection.
  2. WeakSet:
    • A WeakSet is similar to a Set, but it only allows objects as elements, and the references to the objects are weak. When an object is no longer referenced elsewhere, it can be garbage collected even if it’s in the WeakSet.

Example:

const weakSet = new WeakSet();
let obj = {};
weakSet.add(obj);
console.log(weakSet.has(obj));  // true
obj = null;  // The object is eligible for garbage collection

  1. Use case: WeakSet is useful for tracking objects without preventing their garbage collection.

7. What is the purpose of Symbol in JavaScript?

A Symbol is a primitive data type in JavaScript introduced in ES6. It represents a unique and immutable identifier. Symbols are often used to add unique property keys to objects to avoid name clashes, especially in large applications or libraries.

Example:

const sym = Symbol('description');
const obj = {};
obj[sym] = 'value';
console.log(obj[sym]);  // 'value'

Symbols are not enumerable by default, meaning they do not show up in for...in loops or Object.keys(). They are also used for defining well-known symbols like Symbol.iterator for custom iteration behavior.

8. How do you handle large-scale asynchronous code in JavaScript?

Handling large-scale asynchronous code in JavaScript requires strategies to manage complexity, improve readability, and prevent callback hell. Some techniques include:

Promises: Use Promises for managing asynchronous operations and chaining multiple async operations in a readable manner.

fetchData()
  .then(data => processData(data))
  .then(result => displayResult(result))
  .catch(error => handleError(error));

Async/Await: Use async/await syntax to write asynchronous code that looks synchronous, improving readability and reducing nested .then() chains.

async function fetchData() {
  try {
    const data = await fetchDataFromAPI();
    const processedData = await processData(data);
    displayResult(processedData);
  } catch (error) {
    handleError(error);
  }
}

  1. Modularization: Break down large asynchronous operations into smaller functions or modules to keep the code maintainable.
  2. Error Handling: Always ensure that asynchronous code has proper error handling using try/catch with async/await or .catch() with Promises.
  3. Parallelism and Concurrency: Use Promise.all() for running multiple asynchronous tasks in parallel.

9. What is the role of a JavaScript polyfill, and when should you use one?

A polyfill is a piece of code (usually a library) that provides support for features not available in certain environments (like older browsers). Polyfills allow developers to use modern JavaScript features, such as Array.prototype.includes, Promise, or fetch(), even in environments that do not natively support them.

Example (Polyfill for Array.prototype.includes):

if (!Array.prototype.includes) {
  Array.prototype.includes = function(element) {
    return this.indexOf(element) !== -1;
  };
}

You should use polyfills when:

  • You need to support older browsers or environments.
  • You’re using newer ECMAScript features that may not be supported by all users.

10. What is event delegation, and why is it important in large applications?

Event delegation is a technique in JavaScript where you attach a single event listener to a parent element instead of multiple listeners to individual child elements. This is efficient because it takes advantage of event bubbling to capture events on child elements.

Example:

document.querySelector('#parent').addEventListener('click', function(event) {
  if (event.target && event.target.matches('button.classname')) {
    console.log('Button clicked!');
  }
});

In this example, only one event listener is attached to the parent element #parent, and it handles clicks on any button.classname inside it.

Why it's important:

  • Reduces memory usage by minimizing the number of event listeners.
  • Makes it easier to manage dynamically added elements, as new child elements automatically inherit the parent's event delegation.

11. What is the significance of JavaScript’s setImmediate() vs. setTimeout()?

Both setImmediate() and setTimeout() are used to schedule asynchronous tasks in JavaScript, but they differ in when the tasks are executed:

  1. setTimeout():
    • Executes the callback function after a specified delay (in milliseconds).
    • The callback is queued in the event loop’s task queue after the specified delay, which means it will run after the current execution context and any other already scheduled tasks.

Example:

setTimeout(() => console.log('Timeout'), 0);
console.log('Immediate');
// Output: "Immediate" first, then "Timeout"

  1. setImmediate() (Node.js specific):
    • Executes the callback function in the next iteration of the event loop, after I/O events but before timers and other task queue tasks.
    • It’s similar to setTimeout(fn, 0) but guarantees to run after I/O events and before timers.

Example:

setImmediate(() => console.log('Immediate'));
console.log('Timeout');
// Output: "Timeout" first, then "Immediate"

In summary, setImmediate() is specific to Node.js and gives a higher priority to I/O tasks over timers, whereas setTimeout() schedules a task to be run after a delay, and its callback is pushed into the task queue.

12. How would you explain prototypal inheritance in JavaScript?

Prototypal inheritance is a feature in JavaScript where objects can inherit properties and methods from other objects. Every object in JavaScript has a prototype, which is another object that it inherits from. This allows for shared properties and methods among multiple instances of objects, enabling code reuse.

Example:

const animal = {
  makeSound() {
    console.log('Animal sound');
  }
};

const dog = Object.create(animal);  // dog inherits from animal
dog.makeSound();  // 'Animal sound'

In this example, dog does not have its own makeSound method, but it inherits it from the animal object’s prototype. This is the essence of prototypal inheritance.

  • When a property or method is called on an object, JavaScript first looks for it in the object itself. If it's not found, JavaScript looks up the prototype chain until it finds the property or method (or reaches the end of the chain, which is Object.prototype).

13. What are the major differences between ES6 classes and the traditional prototype-based inheritance?

While ES6 classes introduce a more familiar and syntactically cleaner way to define objects and inheritance, JavaScript still uses prototype-based inheritance under the hood. The major differences are:

  1. Syntax:
    • ES6 Classes: Use the class and constructor keywords to define the object structure and inheritance.
    • Prototype-based: Involves defining a constructor function and directly manipulating prototypes using prototype property.

Example (ES6 Class):

class Animal {
  constructor(name) {
    this.name = name;
  }
  speak() {
    console.log(`${this.name} makes a sound.`);
  }
}
const dog = new Animal('Dog');
dog.speak();  // "Dog makes a sound."

Example (Prototype-based):

function Animal(name) {
  this.name = name;
}
Animal.prototype.speak = function() {
  console.log(`${this.name} makes a sound.`);
};
const dog = new Animal('Dog');
dog.speak();  // "Dog makes a sound."

  1. Inheritance:
    • ES6 Classes: Use the extends keyword to establish inheritance, making subclassing more explicit.
    • Prototype-based: Inheritance is achieved by setting the prototype of a child constructor to an instance of the parent constructor.

Example (Inheritance with ES6):

class Dog extends Animal {
  speak() {
    console.log(`${this.name} barks.`);
  }
}
const dog = new Dog('Rex');
dog.speak();  // "Rex barks."

  1. Conclusion: ES6 classes provide syntactical sugar and are easier to work with compared to traditional prototype-based inheritance. However, both use prototypes under the hood.

14. How would you handle a memory leak in a JavaScript application?

A memory leak in JavaScript occurs when the application consumes more and more memory over time without releasing it. This can degrade performance and cause the application to crash. Some strategies to handle memory leaks:

  1. Avoid global variables: Minimize the use of global variables to avoid unintentionally holding references to objects.

Clear timeouts and intervals: Always clear setTimeout() and setInterval() when they are no longer needed.

const timerId = setInterval(() => {
  // Some code
}, 1000);
clearInterval(timerId);

Event listeners: Always remove event listeners when no longer needed, especially for DOM elements that might get removed.

element.removeEventListener('click', handleClick);

  1. Circular references: Be cautious with circular references (e.g., when two objects reference each other), as this can prevent garbage collection.
  2. Use tools: Use browser developer tools (e.g., Chrome DevTools) to detect memory leaks and analyze heap snapshots.
  3. Weak references: Use WeakMap or WeakSet to hold objects when you don’t want them to prevent garbage collection.

15. How does the async/await syntax work under the hood?

The async/await syntax is built on Promises and allows asynchronous code to be written in a synchronous-looking style. Here's how it works:

  1. async function: An async function always returns a Promise. If the function returns a value, that value is wrapped in a Promise.
  2. await keyword: The await keyword can only be used inside an async function and pauses the execution of the function until the Promise resolves or rejects.

Under the hood:

  • When the function encounters an await, it pauses execution and hands control back to the event loop. The execution is resumed once the Promise resolves, with the resolved value available at that point.
  • Essentially, async/await is syntactic sugar that simplifies the use of Promise chaining and provides cleaner, more readable code.

Example:

async function fetchData() {
  const response = await fetch('https://api.example.com');
  const data = await response.json();
  return data;
}

fetchData().then(data => console.log(data));

Under the hood, this is equivalent to:

function fetchData() {
  return fetch('https://api.example.com')
    .then(response => response.json())
    .then(data => data);
}

16. How would you implement a custom event system in JavaScript?

To implement a custom event system, you can use a pub/sub (publish/subscribe) pattern, where event listeners subscribe to certain events, and the event emitter triggers those events when needed.

Example:

class EventEmitter {
  constructor() {
    this.events = {};
  }

  on(event, listener) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(listener);
  }

  emit(event, ...args) {
    if (this.events[event]) {
      this.events[event].forEach(listener => listener(...args));
    }
  }

  off(event, listener) {
    if (this.events[event]) {
      this.events[event] = this.events[event].filter(l => l !== listener);
    }
  }
}

// Usage:
const emitter = new EventEmitter();

function onUserLogin(user) {
  console.log(`${user} logged in`);
}

emitter.on('userLogin', onUserLogin);
emitter.emit('userLogin', 'Alice');  // Logs: Alice logged in

emitter.off('userLogin', onUserLogin);

This basic EventEmitter class allows for adding listeners (on), emitting events (emit), and removing listeners (off).

17. What is the difference between shallow copy and deep copy for objects and arrays?

  1. Shallow Copy:
    • A shallow copy of an object or array is a new object/array where the top-level properties are copied, but nested objects or arrays still refer to the original values (i.e., references are shared).

Example (Shallow copy):

const obj1 = { a: 1, b: { c: 2 } };
const obj2 = { ...obj1 };  // Shallow copy
obj2.b.c = 3;
console.log(obj1.b.c);  // 3 (obj1's nested object is affected)

  1. Deep Copy:
    • A deep copy involves creating a new object or array with completely independent values, including nested objects or arrays. This means all levels of the object/array are copied recursively.

Example (Deep copy using JSON.parse and JSON.stringify):

const obj1 = { a: 1, b: { c: 2 } };
const obj2 = JSON.parse(JSON.stringify(obj1));  // Deep copy
obj2.b.c = 3;
console.log(obj1.b.c);  // 2 (obj1 remains unaffected)

18. Explain JavaScript’s call stack, event loop, and the microtask queue.

  1. Call Stack: The call stack is a stack data structure that keeps track of function calls in JavaScript. When a function is called, it is pushed onto the stack, and when it finishes, it is popped off the stack.
  2. Event Loop: The event loop continually checks if the call stack is empty. If it is, it pushes the first event from the event queue to the call stack for execution. It allows asynchronous code (like timers and I/O operations) to be processed after synchronous code.
  3. Microtask Queue: The microtask queue contains tasks like Promise resolutions or process.nextTick() in Node.js. The microtask queue has a higher priority than the event queue, meaning it is processed before regular events.

Example:

console.log('Start');
setTimeout(() => console.log('Timeout'), 0);
Promise.resolve().then(() => console.log('Promise'));
console.log('End');

// Output:
// Start
// End
// Promise
// Timeout

19. What is the difference between let and var in terms of scoping and hoisting?

  1. Scoping:
    • var is function-scoped, meaning it’s available throughout the entire function, regardless of where it’s declared.
    • let is block-scoped, meaning it’s available only within the block where it’s defined (e.g., inside a loop or an if statement).
  2. Hoisting:
    • var declarations are hoisted to the top of their function, and they are initialized with undefined.
    • let is also hoisted, but it remains in a "temporal dead zone" from the start of the block until the declaration is encountered.

20. How does JavaScript’s garbage collection work?

JavaScript uses automatic garbage collection to manage memory. It identifies objects that are no longer needed (unreachable) and frees up memory.

  1. Mark-and-sweep: JavaScript uses a mark-and-sweep algorithm:
    • Mark: It marks all objects that are still reachable (e.g., those still referenced).
    • Sweep: It frees memory used by objects that are no longer reachable.
  2. Reachability: An object is reachable if it is referenced by another object or by the global scope. If no object references it, it becomes eligible for garbage collection.

Garbage collection helps to prevent memory leaks and ensures the application does not consume excessive memory over time.

21. What are JavaScript modules (ES6) and how do they differ from CommonJS modules?

ES6 Modules (also called ECMAScript modules) provide a standardized way to organize and export JavaScript code. They use the import and export syntax to share code between files.

Example of ES6 Module:

math.js (module file):

export function add(a, b) {
  return a + b;
}
export function subtract(a, b) {
  return a - b;
}

app.js (importing the module):

import { add, subtract } from './math.js';

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

Key Features of ES6 Modules:

  • Static structure: Imports and exports are determined at compile time, leading to better optimization and tree-shaking (unused code removal).
  • import and export syntax are used.
  • Modules are lexically scoped (i.e., scoped to the module itself).
  • They are asynchronous when loaded in a browser environment.

CommonJS modules, on the other hand, are primarily used in Node.js applications. They use require() to import modules and module.exports to export them.

Example of CommonJS Module:

math.js:

function add(a, b) {
  return a + b;
}
function subtract(a, b) {
  return a - b;
}

module.exports = { add, subtract };

app.js:

const { add, subtract } = require('./math');

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

Key Features of CommonJS:

  • Synchronous loading: Modules are loaded and executed synchronously.
  • CommonJS is not supported natively in the browser without bundlers like Webpack or Browserify.
  • Dynamic: Exports and imports can be modified at runtime.

22. What is an IIFE (Immediately Invoked Function Expression) and when would you use it?

An IIFE (Immediately Invoked Function Expression) is a function that is defined and executed immediately after its creation. It is commonly used to create a new scope, avoid polluting the global namespace, and isolate variables.

Syntax:

(function() {
  // code here
})(); // IIFE

Alternatively, with an arrow function:

(() => {
  // code here
})();

Use Cases:

  • Encapsulation: Prevents variables from leaking into the global scope.
  • Module pattern: Implements private variables and methods.
  • Self-contained logic: Execute code that doesn't need to be reused.

Example:

(function() {
  const privateVar = 'I am private';
  console.log(privateVar);  // 'I am private'
})();
console.log(privateVar);  // Error: privateVar is not defined

23. What are closures used for in advanced JavaScript applications?

A closure is a function that has access to its own scope, the scope in which it was created, and the global scope. Closures are powerful and are often used for:

  1. Data privacy: Variables inside closures are not accessible outside, making them ideal for implementing private data.
    • Example: Module Pattern in JavaScript:
function counter() {
  let count = 0;
  return {
    increment: () => count++,
    getCount: () => count
  };
}
const counterInstance = counter();
console.log(counterInstance.getCount());  // 0
counterInstance.increment();
console.log(counterInstance.getCount());  // 1
  1. Callback functions and event handlers: Closures are used in event handling, where the inner function can access variables from the outer function even after it has finished executing.
  2. Function factories: Create functions dynamically with customized behavior.
    • Example: Creating a function that multiplies any number by a given factor:
function multiplier(factor) {
  return function(number) {
    return number * factor;
  };
}
const doubler = multiplier(2);
console.log(doubler(5));  // 10

  1. Partial function application: A closure can be used to pre-fill arguments of a function for later execution.

24. What is the difference between Object.create() and new keyword in JavaScript?

Both Object.create() and the new keyword are used to create new objects, but they differ in how they establish the object's prototype chain and constructor behavior.

  1. Object.create():
    • Creates a new object with the specified prototype object and optionally properties.
    • It does not invoke a constructor function.

Example:

const proto = { greet() { console.log('Hello!'); } };
const obj = Object.create(proto);
obj.greet();  // "Hello!"
  1. new keyword:
    • Creates a new object and sets its prototype to the prototype of the constructor function.
    • It also invokes the constructor function, allowing you to initialize properties on the object.

Example:

function Person(name) {
  this.name = name;
}
const person = new Person('Alice');
console.log(person.name);  // 'Alice'

Key Differences:

  • Object.create() creates an object with the specified prototype and does not invoke a constructor function.
  • new creates an object and invokes the constructor function to initialize properties.

25. How would you handle deep cloning in JavaScript for complex objects?

Deep cloning involves creating a new object with an identical structure and values, including nested objects, without maintaining references to the original object's properties.

Methods to Deep Clone:

  1. Using JSON.parse() and JSON.stringify():
    • This method serializes the object to a string and then deserializes it back into an object. It works for plain objects and arrays but doesn't handle functions, undefined, or special objects like Date, RegExp, Map, etc.

Example:

const original = { a: 1, b: { c: 2 } };
const clone = JSON.parse(JSON.stringify(original));
clone.b.c = 3;
console.log(original.b.c);  // 2
  1. Using a custom recursive function:
    • For more control and to handle various data types like Date, RegExp, and functions, a custom deep cloning function may be necessary.

Example (Recursive Clone):

function deepClone(obj) {
  if (obj === null || typeof obj !== 'object') return obj;
  if (Array.isArray(obj)) return obj.map(item => deepClone(item));
  const clonedObj = {};
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      clonedObj[key] = deepClone(obj[key]);
    }
  }
  return clonedObj;
}

26. What are some strategies for improving JavaScript application security?

To improve the security of JavaScript applications, several strategies should be employed:

  1. Input validation and sanitization:
    • Always validate and sanitize user inputs to prevent XSS (Cross-Site Scripting) and other injection attacks.
    • Use libraries like DOMPurify to sanitize user-generated content before injecting it into the DOM.
  2. Use HTTPS:
    • Ensure all requests to the server use HTTPS to prevent Man-in-the-Middle (MITM) attacks.
  3. Avoid eval():
    • Never use eval(), setTimeout() with string arguments, or setInterval() with string arguments, as they can lead to code injection vulnerabilities.
  4. Use Content Security Policy (CSP):
    • Implement a CSP to restrict the sources from which scripts, styles, and other resources can be loaded.
  5. Cross-Origin Resource Sharing (CORS):
    • Properly configure CORS headers to control which origins can access your resources.
  6. Session management:
    • Ensure secure session management practices, like secure cookies, token expiration, and same-origin policy enforcement.
  7. Keep libraries and dependencies updated:
    • Regularly update libraries to patch known security vulnerabilities.

27. Explain the concept of concurrency vs. parallelism in JavaScript.

  • Concurrency: Involves executing multiple tasks in a way that they appear to run simultaneously, but in reality, they are being executed in small time slices (in a single-threaded environment like JavaScript). It’s about managing multiple tasks that may be waiting on other tasks (I/O-bound).
  • Parallelism: Refers to executing multiple tasks at exactly the same time, typically in multiple threads or processes. This is used to perform CPU-bound tasks.

In JavaScript, concurrency is achieved via the event loop, where tasks are handled asynchronously, but there's only a single thread of execution. However, parallelism can be achieved using Web Workers in the browser or using multi-threading in environments like Node.js.

28. What is the role of the service worker in JavaScript applications?

A service worker is a special JavaScript file that runs in the background and enables features like offline access, background sync, and push notifications. It acts as a proxy between the web application and the network, allowing you to intercept network requests and cache responses.

Key Features:

  • Offline capabilities: Cache assets and serve them when the user is offline.
  • Background sync: Sync data between the client and server in the background when the network is available.
  • Push notifications: Receive and display push notifications even when the app is not open.

Service workers are essential for building Progressive Web Apps (PWAs).

Example:

// In service-worker.js
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open('my-cache').then((cache) => {
      return cache.addAll(['/index.html', '/style.css']);
    })
  );
});

29. How would you implement a JavaScript debounce and throttle function?

  • Debounce ensures that a function is only called after a specified amount of idle time, useful for events like resizing or typing in search fields.
  • Throttle ensures that a function is called at most once every specified time period, useful for events like scrolling or resizing.

Debounce:

function debounce(func, delay) {
  let timeout;
  return function(...args) {
    clearTimeout(timeout);
    timeout = setTimeout(() => func(...args), delay);
  };
}

Throttle:

function throttle(func, limit) {
  let lastCall = 0;
  return function(...args) {
    const now = new Date().getTime();
    if (now - lastCall >= limit) {
      func(...args);
      lastCall = now;
    }
  };
}

30. What is a JavaScript singleton pattern, and when would you use it?

The Singleton Pattern ensures that a class has only one instance and provides a global point of access to that instance.

Example:

const Singleton = (function() {
  let instance;
  
  function createInstance() {
    return { message: "I am the only instance" };
  }

  return {
    getInstance: function() {
      if (!instance) {
        instance = createInstance();
      }
      return instance;
    }
  };
})();

const instance1 = Singleton.getInstance();
const instance2 = Singleton.getInstance();

console.log(instance1 === instance2);  // true

Use Cases:

  • Managing global state in an application.
  • When you need a shared resource like a logging service, configuration manager, or database connection.

31. What are generators in JavaScript, and how would you use them for complex async tasks?

Generators are special functions in JavaScript that can be paused and resumed, allowing them to maintain their state between function calls. A generator function is defined using the function* syntax, and it yields values one at a time using the yield keyword.

Example of a 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

Using Generators for Asynchronous Tasks:

Generators can be useful for complex async workflows when combined with a control flow mechanism, such as co (a library) or custom handling, to yield promises and resume once the promises are resolved.

Example: Using Generators with Async Tasks

function* fetchData() {
  const user = yield fetch('https://api.example.com/user');
  const posts = yield fetch(`https://api.example.com/posts/${user.id}`);
  return posts;
}

function runGenerator(gen) {
  const iterator = gen();

  function handleResult(result) {
    if (result.done) return Promise.resolve(result.value);

    return Promise.resolve(result.value).then(res => handleResult(iterator.next(res)));
  }

  return handleResult(iterator.next());
}

runGenerator(fetchData)
  .then(data => console.log('Fetched data:', data))
  .catch(err => console.error(err));

Generators provide a way to simplify asynchronous code flow while avoiding "callback hell." However, async/await is generally preferred nowadays for simpler syntax.

32. How does the bind() method affect the execution context in JavaScript?

The bind() method creates a new function that, when called, has its this value set to a specific object, and any provided arguments are prepended to the arguments provided to the new function when called.

Key Points:

  • bind() does not execute the function immediately; it returns a new function with a specific this context.
  • this is permanently bound to the object you provide, regardless of where the function is called.

Example:

const obj = { name: 'Alice' };

function greet() {
  console.log(`Hello, ${this.name}!`);
}

const greetAlice = greet.bind(obj);
greetAlice();  // Output: "Hello, Alice!"

In this case, bind() ensures that the greet() function always uses obj as its execution context, even if it's called in a different context.

WeCP Team
Team @WeCP
WeCP is a leading talent assessment platform that helps companies streamline their recruitment and L&D process by evaluating candidates' skills through tailored assessments