Javascript Interview Questions – Set 4

1. What is the Event Loop in JavaScript, and why is it important in handling asynchronous operations?

Answer:
The Event Loop is a fundamental part of JavaScript’s concurrency model, responsible for managing the execution of code and handling asynchronous operations. It plays a critical role in ensuring that JavaScript remains non-blocking and responsive to user interactions in web applications.

In essence, the Event Loop constantly checks the message queue for tasks and executes them one at a time. When an asynchronous task is completed (e.g., a timer expires, an HTTP request receives a response), a message representing that task is placed in the message queue. The Event Loop picks up these messages and executes the corresponding tasks.

Example:

JavaScript
   console.log("Start");

   setTimeout(function() {
     console.log("Timeout");
   }, 1000);

   console.log("End");
   // Output: Start, End (waits for 1 second), Timeout

In this example, the setTimeout function schedules the "Timeout" message to be added to the message queue after 1000 milliseconds. While waiting for that time, the Event Loop continues executing other code, such as "Start" and "End", without blocking.

2. How do you handle errors in JavaScript, and what are the different types of errors?

Answer:
In JavaScript, errors can be handled using try...catch blocks. There are several types of errors, including:

  • SyntaxError: Occurs when there’s a syntax mistake in the code, preventing it from being parsed.
  • ReferenceError: Occurs when a variable or function is used before it’s declared.
  • TypeError: Occurs when an operation is performed on an inappropriate data type.
  • RangeError: Occurs when a numeric value is outside the allowable range.
  • Custom Errors: Developers can create custom error objects using the Error constructor to provide meaningful error messages for their applications. Example:
JavaScript
   try {
     // Some code that may throw an error
     const result = 10 / "abc"; // This will throw a TypeError
   } catch (error) {
     console.error("An error occurred:", error.message);
   }

In this example, a try...catch block is used to handle a TypeError that occurs when trying to divide a number by a non-numeric value.

3. Explain the concept of promises in JavaScript. How do they differ from callbacks?

Answer:
Promises are objects representing the eventual completion or failure of an asynchronous operation. They provide a more structured and readable way to handle asynchronous code compared to callbacks. Promises have three states: pending, fulfilled, or rejected.

Promises differ from callbacks in several ways:

  • Chaining: Promises can be easily chained together using .then() and .catch(), making code more sequential and readable.
  • Error Handling: Promises have built-in error handling with .catch(), whereas in callback-based code, error handling often involves checking for error parameters.
  • Avoiding Callback Hell: Promises help avoid the “callback hell” problem, where deeply nested callbacks can become hard to manage. Example:
JavaScript
   function fetchData() {
     return new Promise((resolve, reject) => {
       setTimeout(() => {
         const data = "Data fetched successfully";
         resolve(data); // Resolve the promise
       }, 1000);
     });
   }

   fetchData()
     .then((data) => {
       console.log(data);
     })
     .catch((error) => {
       console.error(error);
     });

In this example, fetchData returns a promise that resolves after 1000 milliseconds. We use .then() to handle the resolved value (data) and .catch() to handle any errors.

4. What is the difference between null, undefined, and NaN in JavaScript?

Answer:

  • null represents the intentional absence of any object value. It is often used to indicate that a variable or object property should have no value.
  • undefined signifies the absence of a value due to the absence of a variable or the lack of assignment. It is the default value for uninitialized variables and function parameters.
  • NaN stands for “Not-a-Number” and is a special value representing the result of an invalid mathematical operation. It’s often the result of attempting to perform a math operation on non-numeric values. Examples:
JavaScript
   let x = null; // x intentionally has no value
   let y; // y is undefined by default

   console.log(x); // null
   console.log(y); // undefined

   let result = "abc" / 2; // This results in NaN
   console.log(result); // NaN

5. How does JavaScript handle variable scope? Explain the concepts of global scope, function scope, and block scope.

Answer:
JavaScript handles variable scope through the following concepts:

  • Global Scope: Variables declared outside of any function are in the global scope and can be accessed from anywhere in the code. They have the widest scope.
  • Function Scope: Variables declared within a function are in the function’s scope and can only be accessed within that function.
  • Block Scope: Variables declared using let and const within blocks like loops and conditional statements have block scope, limiting their visibility to that block. Examples:
JavaScript
   // Global Scope
   const globalVar = 10;

   function foo() {
     // Function Scope
     const localVar = 20;
     console.log(globalVar); // Accessible
     console.log(localVar); // Accessible
   }

   foo();
   console.log(globalVar); // Accessible
   console.log(localVar); // Throws an error (localVar is not defined)

6. What is the purpose of the async and await keywords in JavaScript, and how do they work?

Answer:
The async and await keywords are used for working with asynchronous code in a more synchronous-like manner. Here’s how they work:

  • async is used to declare that a function returns a promise. It allows you to use await inside the function.
  • await is used inside an async function to pause its execution until a promise is resolved. It allows you to write asynchronous code that appears sequential

.

Example:

JavaScript
   async function fetchData() {
     try {
       const response = await fetch("https://api.example.com/data");
       const data = await response.json();
       return data;
     } catch (error) {
       console.error("Error fetching data:", error);
       throw error;
     }
   }

   fetchData()
     .then((data) => {
       console.log(data);
     })
     .catch((error) => {
       console.error("Error in fetchData:", error);
     });

In this example, fetchData is an async function that uses await to make an HTTP request and handle JSON parsing. It provides a more readable way to handle asynchronous operations.

7. Explain the concept of hoisting in JavaScript.

Answer:
Hoisting is a JavaScript behavior where variable and function declarations are moved to the top of their containing scope during the compilation phase. However, only the declarations are hoisted, not their initializations.

This means that you can use a variable or function before it is declared in your code, but it will be treated as if it was declared at the top of its containing scope.

Examples:

JavaScript
   console.log(x); // undefined (declaration is hoisted)
   var x = 10;

   // Function declaration hoisting
   sayHello(); // "Hello!"
   function sayHello() {
     console.log("Hello!");
   }

In the first example, the var variable x is hoisted, but its initialization (= 10) is not. In the second example, the function sayHello is hoisted along with its implementation.

8. How can you clone an object in JavaScript?

Answer:
Cloning an object in JavaScript can be achieved using various methods:

  • Object.assign(): Copies the values of all enumerable properties from one or more source objects to a target object.
JavaScript
   const original = { name: "John" };
   const clone = Object.assign({}, original);
  • Spread Operator ({...}): Creates a shallow copy of an object.
JavaScript
   const original = { name: "John" };
   const clone = { ...original };
  • JSON.parse() and JSON.stringify(): Converts an object to a JSON string and then parses it to create a deep clone.
JavaScript
   const original = { name: "John" };
   const clone = JSON.parse(JSON.stringify(original));
  • Using Libraries: Libraries like lodash provide functions like clone() and cloneDeep() for object cloning.

9. Describe the differences between the == and === operators in JavaScript.

Answer:

  • == (loose equality) compares values after type coercion, meaning it converts operands to a common type before comparison. It can lead to unexpected results.
  • === (strict equality) compares values without type coercion, requiring both value and type to be the same for equality to be true. Examples:
JavaScript
   10 == "10"; // true (loose equality converts the string to a number)
   10 === "10"; // false (strict equality considers different types)

10. Explain the concept of currying in JavaScript.

Answer:
Currying is a technique in functional programming where a function that takes multiple arguments is transformed into a sequence of functions, each taking a single argument. It allows you to create more specialized and reusable functions.

Example:

JavaScript
   function curryAdd(a) {
     return function(b) {
       return a + b;
     };
   }

   const add5 = curryAdd(5);
   console.log(add5(10)); // 15

In this example, curryAdd takes an argument a and returns a function that takes another argument b. This allows us to create a specialized add5 function that always adds 5 to its argument.

11. What is the purpose of the this keyword in JavaScript, and how does it behave in different contexts?

Answer:
The this keyword in JavaScript refers to the current execution context or the object that the function is operating on. Its value is determined by how a function is called, and it behaves differently in various contexts:

  • Global Scope: In the global scope, this refers to the global object (e.g., window in a browser).
  • Function Scope: Inside a regular function, this is undefined in strict mode or refers to the global object in non-strict mode.
  • Method Scope: In the context of an object method, this refers to the object itself.
  • Event Handlers: In event handlers, this usually refers to the element that triggered the event.
  • Constructor Functions: In constructor functions, this refers to the newly created object. Examples:
JavaScript
   console.log(this === window); // true (global scope)

   function sayHello() {
     console.log(this); // undefined (strict mode) or window (non-strict mode)
   }

   const person = {
     name: "John",
     greet: function() {
       console.log(`Hello, ${this.name}`); // "Hello, John" (method scope)
     }
   };

   document.getElementById("myButton").addEventListener("click", function() {
     console.log(this); // The button element (event handler)
   });

12. What are the differences between the localStorage, sessionStorage, and cookies in web storage?

Answer:

  • localStorage and sessionStorage are part of the Web Storage API, allowing web applications to store key-value pairs in the user’s browser.
  • localStorage data persists across sessions and tabs, while sessionStorage data is cleared when the session ends (e.g., when the browser is closed).
  • Cookies are also used for storing data but have additional features like expiration dates, domain restrictions, and the ability to store small amounts of data on the server. Examples:
JavaScript
   // Storing data in localStorage
   localStorage.setItem("username", "John");

   // Storing data in sessionStorage
   sessionStorage.setItem("sessionToken", "abc123");

   // Using cookies (with JavaScript document.cookie)
   document.cookie = "user=John; expires=Thu, 01 Jan 2025 00:00:00 UTC; path=/";

13. Explain the concept of debouncing and throttling in JavaScript.

Answer:
Debouncing and throttling are techniques used to limit the frequency of function calls in response to events to optimize performance.

  • Debouncing: Debouncing ensures that a function is called only after a certain period of inactivity following the last event. It helps prevent multiple rapid invocations of the same function, which can be resource-intensive.
  • Throttling: Throttling limits the rate at which a function can be called. It ensures that a function is called at most once every specified time interval. This prevents excessive function calls. Examples:
JavaScript
   // Debouncing
   function debounce(fn, delay) {
     let timer;
     return function() {
       clearTimeout(timer);
       timer = setTimeout(() => fn.apply(this, arguments), delay);
     };
   }

   // Throttling
   function throttle(fn, interval) {
     let lastCall = 0;
     return function() {
       const now = Date.now();
       if (now - lastCall >= interval) {
         fn.apply(this, arguments);
         lastCall = now;
       }
     };
   }

These functions can be used to wrap other functions that need debouncing or throttling.

14. How do you handle cross-origin requests (CORS) in JavaScript?

Answer:
Cross-origin requests can be handled using HTTP headers on the server-side and client-side techniques in JavaScript:

  • Server-Side: Configure the server to include appropriate CORS headers like Access-Control-Allow-Origin to specify which domains are allowed to access resources. You can also set other headers like Access-Control-Allow-Methods and Access-Control-Allow-Headers.
  • Client-Side: In JavaScript, you can use the Fetch API or XMLHttpRequest to make cross-origin requests. The server’s CORS headers must permit the request from the client’s origin. Example (Client-Side):
JavaScript
   fetch("https://api.example.com/data")
     .then((response) => response.json())
     .then((data) => {
       console.log(data);
     })
     .catch((error) => {
       console.error("Error:", error);
     });

15. What is the purpose of the JavaScript bind() method, and how does it work? Provide an example.

Answer:
The bind() method is used to create a new function with a specific this value and optional arguments, without calling the function immediately. It allows you to control the context in which a function is executed.

Example:

JavaScript
   const person = {
     name: "John",
     greet: function() {
       console.log(`Hello, ${this.name}`);
     }
   };

   const greetJohn = person.greet.bind(person);
   greetJohn(); // "Hello, John"

In this example, we use bind() to create a new function greetJohn that is bound to the person object. When greetJohn() is called, it still has access to this.name within the person object, even though it’s called in a different context.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top