Javascript/Typescript/Node.js Interview Questions Part -2 JavaScript questions

In the second part of the Interview question series we will be looking into a little bit advanced JavaScript questions on functions and object. These questions are very commonly asked in directly or indirectly in most of the advanced Javascript Interviews

  1. What is Rest and spread operator and explain their usage?

Rest and spread operators are two useful features introduced in ECMAScript 6 (ES6) to work with arrays and objects in JavaScript. They provide concise and flexible ways to handle multiple elements or properties at once. Though they look same but functionally they are different

  1. Rest Operator (…) The rest operator is represented by three dots (…) and is used in function parameters to collect multiple arguments into a single array. It allows you to handle an arbitrary number of arguments passed to a function without explicitly defining them.

Usage of Rest Operator:

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

const result = sum(1, 2, 3, 4, 5);
console.log(result); // Output: 15

In this example, the sum() function uses the rest operator …numbers in its parameter list to collect all the arguments passed to the function into a single array called numbers. This way, the function can handle any number of arguments provided during the function call.

2. Spread Operator (…) The spread operator is also represented by three dots (…) and is used to split arrays or objects into individual elements or properties. It can be used in various contexts, such as creating a new array or object, passing array elements as function arguments, and combining arrays or objects.

Usage of Spread Operator:

a) Creating a new array by combining two arrays:

const array1 = [1, 2, 3];
const array2 = [4, 5, 6];


const combinedArray = [...array1, ...array2];
console.log(combinedArray); // Output: [1, 2, 3, 4, 5, 6]

b) Copying an array:

const array1 = [1, 2, 3];
const array2 = [4, 5, 6];


const combinedArray = [...array1, ...array2];
console.log(combinedArray); // Output: [1, 2, 3, 4, 5, 6]

c) Passing array elements as function arguments:

function printElements(a, b, c) {
  console.log(a, b, c);
}


const elements = [1, 2, 3];
printElements(...elements); // Output: 1 2 3


function printElements(a, b, c) {
  console.log(a, b, c);
}

const elements = [1, 2, 3];
printElements(...elements); // Output: 1 2 3

d) Merging objects:

const obj1 = { name: 'John' };
const obj2 = { age: 30 };


const mergedObj = { ...obj1, ...obj2 };
console.log(mergedObj); // Output: { name: 'John', age: 30 }

The spread operator allows us to easily combine, copy, or pass elements from arrays or properties from objects. It simplifies the manipulation of complex data structures and enhances code readability.

In summary, the rest operator collects function arguments into an array, enabling functions to handle an arbitrary number of parameters. On the other hand, the spread operator splits arrays or objects into individual elements or properties, making it easier to create, copy, or merge data structures. Both operators are powerful tools that improve the flexibility and readability of JavaScript code.

2. What is a higher-order function?

A higher-order function is a function that takes one or more functions as arguments or returns a function as its result. In other words, it either operates on other functions or produces functions. Higher-order functions are a powerful concept in functional programming and allow for code abstraction and composability.

Example

function mapArray(array, transformFn) {
  const result = [];
  for (const item of array) {
    result.push(transformFn(item));
  }
  return result;
}

const numbers = [1, 2, 3, 4];
const doubleNumbers = mapArray(numbers, (num) => num * 2);
console.log(doubleNumbers); // Output: [2, 4, 6, 8]

3. What are pure functions, and why are they important?

A pure function is a function that always produces the same output for the same input and has no side effects. In other words, a pure function does not modify external state or rely on external state, making it predictable and easy to reason about. Pure functions play a crucial role in functional programming paradigms as they lead to code that is easier to test, maintain, and reason about.

Example:

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

4. What is recursion, and how does it work?

Recursion is a programming technique where a function calls itself to solve a problem. It involves breaking a complex problem into smaller, similar sub-problems and solving them iteratively until the base case is reached. Recursive functions must have a base case to stop the recursion and avoid infinite loops.

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

console.log(factorial(5)); // Output: 120 (5 * 4 * 3 * 2 * 1)

In this example, the factorial function is a recursive function that calculates the factorial of a number. It breaks down the problem of computing the factorial of n into computing the factorial of n – 1, until it reaches the base case of n === 0, where it returns 1.

5. What is function currying, and how is it achieved in JavaScript?

Function currying involves breaking down a multi-argument function into a series of single-argument functions. This transformation enables you to create a chain of functions, each taking a single argument, which can be applied one after another to eventually produce the final result. This technique is particularly useful when you have a function that accepts multiple arguments but needs to be reused with varying subsets of those arguments.

Benefits of Function Currying

  1. Code Reusability: Currying allows you to create specialized functions from a more general one. This can help you avoid repetitive code and keep your codebase cleaner.
  2. Partial Function Application: With currying, you can pre-fill some arguments of a function and generate new functions that only require the remaining arguments. This can be handy for creating variants of a function with some fixed parameters.
  3. Readability: Curried functions often read more naturally and intuitively, as each function in the curry chain only deals with one argument.
  4. Modularity: Currying encourages a modular approach to programming, making it easier to reason about your code and swap out parts without affecting the rest of the logic.

Examples of Function Currying

Let’s explore some examples to better understand function currying in JavaScript:

Basic Currying

// Original function
function add(a, b, c) {
  return a + b + c;
}

// Curried version
function curriedAdd(a) {
  return function(b) {
    return function(c) {
      return a + b + c;
    };
  };
}

const add2 = curriedAdd(2);
const add5 = add2(3);
console.log(add5(5)); // Output: 10

//You can also call the Curried function
console.log(curriedAdd(2)(3)(5)) // Output 10

Dynamic Currying with ES6

const dynamicCurry = (fn, ...args) =>
  args.length >= fn.length ? fn(...args) : (...moreArgs) => dynamicCurry(fn, ...args, ...moreArgs);

function greet(firstName, lastName, message) {
  return `${message}, ${firstName} ${lastName}!`;
}

const curriedGreet = dynamicCurry(greet);
const greetMr = curriedGreet('Mr.');
const greetMrSmith = greetMr('Smith');
console.log(greetMrSmith('Hello')); // Output: Hello, Mr. Smith!

6. What is Closure in Javascript ?

Closures are a fundamental concept in JavaScript that play a crucial role in understanding how variables, functions, and scope work together. In simple terms, a closure is a function that “remembers” the environment in which it was created, including the variables from its outer function, even after that outer function has finished executing. This enables the closure to access those variables and maintain their state.

Let’s dive into how closures work with some examples:

Example 1: Basic Closure

function outerFunction() {
  const outerVariable = "I am from outer function";

  function innerFunction() {
    console.log(outerVariable); // Accessing outerVariable from the outer scope
  }

  return innerFunction;
}

const closureExample = outerFunction();
closureExample(); // Output: "I am from outer function"

In this example, innerFunction is a closure. It’s defined within the outerFunction, and even after outerFunction has completed execution, the closure still has access to outerVariable.

Example 2: Closure and Data Privacy

function createCounter() {
  let count = 0;

  return function() {
    count++;
    console.log(count);
  };
}

const counter1 = createCounter();
counter1(); // Output: 1
counter1(); // Output: 2

const counter2 = createCounter();
counter2(); // Output: 1

Here, the createCounter function returns a closure. The closure maintains its own private count variable, which can only be accessed and modified through the closure itself. This showcases how closures can be used to create private data that’s not directly accessible from outside the closure.

Example 3: Closures in Asynchronous Operations

function delayMessage(message, delay) {
  setTimeout(function() {
    console.log(message);
  }, delay);
}

delayMessage("Hello, after 2 seconds!", 2000);

In this example, the anonymous function inside the setTimeout is a closure. It retains access to the message variable from the delayMessage function’s scope, even though the delayMessage function has already completed execution.

Closures and Memory Management

Closures can have implications for memory management. If closures are still referencing variables from their outer scope, those variables won’t be garbage collected, potentially leading to memory leaks. To prevent this, it’s a good practice to make sure you release references to variables when they’re no longer needed.

6. In JavaScript, two objects may not be equal even though they appear to be similar. Why is that the case? 

In JavaScript, two objects may not be equal even if they appear to be similar because of the way object comparison works in the language. Unlike primitive data types (such as numbers and strings), objects are reference types, which means they are compared by reference rather than by their content.

When you compare two objects using the equality operator (==) or strict equality operator (===), it checks whether they reference the same memory location, not whether their properties have the same values. If two objects are created independently, even if their properties have the same key-value pairs, they will be considered unequal because they are stored in different memory locations.

Example:

const obj1 = { name: 'Daby', marks: 80 };
const obj2 = { name: 'Daby', marks: 80 };

console.log(obj1 === obj2); // Output: false
console.log(obj1 == obj2); // Output: false

In this example, obj1 and obj2 have the same key-value pairs { name: ‘Daby’, marks: 80 };, but they are two distinct objects in memory. Hence, the equality operators return false.

To compare the contents of two objects, you need to implement a custom comparison function that compares their properties one by one. You can use the JSON.stringify() method to convert the objects to JSON strings and then compare the strings.

const obj1 = { name: 'Daby', marks: 80 };
const obj2 = { name: 'Daby', marks: 80 };

const areEqual = JSON.stringify(obj1) === JSON.stringify(obj2);
console.log(areEqual); // Output: true

In this modified example, the JSON.stringify() method converts both objects into JSON strings (‘{ name: ‘Daby’, marks: 80 }’), and the comparison now returns true because the strings have the same content.

However, it’s essential to be cautious when using this approach, as it only compares the object’s own enumerable properties and not any properties inherited from its prototype chain. Additionally, it does not account for differences in the order of properties, as the order of keys in an object is not guaranteed in JavaScript.

In general, when comparing objects, it is best to use custom comparison functions or libraries specifically designed for deep object comparison to ensure accurate and reliable results. Examples of such libraries include Lodash’s isEqual() function or Underscore.js’s _.isEqual() method.

7. What is Circular Reference Error in Javascript and How to Fix it?

In JavaScript, a circular reference error occurs when two or more objects reference each other in a circular manner, creating an infinite loop during certain operations, such as serialization or traversing the object structure. This circular reference prevents the proper completion of those operations and may lead to stack overflow errors.

To better understand the circular reference error, let’s consider an example:

const obj1 = {};
const obj2 = {};

obj1.reference = obj2;
obj2.reference = obj1;

console.log(obj1); // Output: { reference: {...} }
console.log(obj2); // Output: { reference: {...} }

In this example, obj1 and obj2 reference each other, creating a circular reference. When you log either of these objects to the console, the console tries to display the object’s content, but it goes into an infinite loop because of the circular reference, causing the console to display { … } to represent the reference but not the actual contents of the objects. If you were to use JSON.stringify() on these objects, it would also result in an error.

Circular references can occur in various scenarios, such as when dealing with complex data structures like trees or linked lists, or when serializing objects containing self-references.

How to Fix Circular Reference Errors:

Remove the Circular Reference:

The most straightforward solution is to remove the circular reference in your data structure. If you have control over the objects, make sure they don’t reference each other in a circular manner. Instead, design your data structure to avoid circular dependencies.

Use Serialization Options:

If you need to serialize the objects, you can use the JSON.stringify() method with a replacer function or the replacer argument to handle circular references. The replacer function can be used to exclude or customize the serialization of certain properties. When a circular reference is detected, you can handle it by returning a placeholder value or excluding the circular property from the serialization.

const obj1 = {};
const obj2 = {};

obj1.reference = obj2;
obj2.reference = obj1;

const jsonString = JSON.stringify(obj1, (key, value) => {
  if (key === 'reference') {
    return '[Circular Reference]';
  }
  return value;
});

console.log(jsonString); // Output: {"reference":"[Circular Reference]"}

Use Libraries for Deep Cloning:

When you need to make a deep copy of objects with potential circular references, consider using libraries like Lodash’s cloneDeep() or circular-json. These libraries handle circular references during the cloning process and ensure that you get a copy of the object without circular dependencies.

const _ = require('lodash');

const obj1 = {};
const obj2 = {};

obj1.reference = obj2;
obj2.reference = obj1;

const clonedObject = _.cloneDeep(obj1);

console.log(clonedObject);

By addressing circular references in your code, you can avoid circular reference errors and ensure that your data structures behave as intended during various operations, such as serialization, deep copying, and traversal.

8. What is computed property in JavaScript ? Provide me with some examples

A computed property in JavaScript is a property whose name is computed at runtime. This means that the name of the property is not known at the time the object is created, but is instead determined by an expression that is evaluated at runtime.

Computed properties are declared using square brackets. For example, the following code declares an object with a computed property named ‘age’:

let propName ='age'
const person= {
  [propName]: 10,
};
console.log(person.age) // 10

Lets look at another example


const person ={
    firstName: 'Anupam',
    lastName: 'Roy',
    get fullName(){ return this.firstName+' '+ this.lastName}
}

console.log(person.fullName)

The fullName property is a computed property. The name of the fullName property is computed by the get fullName() getter function. The get fullName() function returns the full name of the person, which is the concatenation of the person’s firstName and lastName.

Note: Within the object you can see fullName is declared as function though while accessing it you should not use person.fullName() instead use person.fullName as fullName is a computed property of person object not a method

9. What are getters and setters in JavaScript ?

Getters and setters are special methods in JavaScript that allow you to control how properties are accessed and modified. Getters are used to read the value of a property, while setters are used to write the value of a property.

For example, the following code defines an object with a property named ‘name' and a getter and setter for that property:

const person = {
  name: "John Doe",
  get name() {
    return this.name;
  },
  set name(value) {
    this.name = value;
  },
};

The ‘name' property is a regular property, but it also has a getter and setter. The getter method, get name(), returns the value of the ‘name' property. The setter method, set name(), sets the value of the ‘name' property.

The getter and setter methods allow us to control how the ‘name‘ property is accessed and modified. For example, we can use the getter method to validate the value of the ‘name' property before it is set. We can also use the setter method to do some processing before the value of the ‘name' property is actually set.

Example:

const person = {
    fname: "John",
    lane : "Doe",
    get fullName() {
      return this.name === null ? 'Name is less than 10 characters long' :`${this.fname} ${this.lname}`
      
    },
    set firstName(name) {
      this.fname = name.length >=2 ? name : null
      
    },
    set lastName(name) {
        this.lname = name.length >=2 ? name : null
        
    },
  };

  person.firstName='Anupam'
  person.lastName = 'Roy'
  console.log(person.fullName) // Anupam Roy

Getters and setters can be used to implement a variety of different behaviors for properties. They can be used to:

  • Validate the value of a property before it is set.
  • Do some processing before the value of a property is set.
  • Expose a calculated value of a property.
  • Hide the internal implementation of a property.

Getters and setters are a powerful tool that can be used to improve the readability, maintainability, and security of JavaScript code.

10. What are iterables in Javascript ?


In JavaScript, an iterable is an object that can be iterated over. This means that it can be used with the for…of loop, which allows you to loop over the elements of an object one at a time.

To be iterable, an object must have a property named Symbol.iterator. This property is a function that returns an iterator for the object. The iterator is responsible for providing the next element in the sequence of elements that the object represents.

Some built-in objects in JavaScript are iterable, such as arrays, strings, and maps. You can also create your own iterable objects by implementing the Symbol.iterator property.

Example of built in iterables

const array = [1, 2, 3, 4, 5];

for (const element of array) {
  console.log(element);
}

This code will loop over the elements of the array and log each element to the console.


In JavaScript, an iterable is an object that can be iterated over. This means that it can be used with the for…of loop, which allows you to loop over the elements of an object one at a time.

To be iterable, an object must have a property named Symbol.iterator. This property is a function that returns an iterator for the object. The iterator is responsible for providing the next element in the sequence of elements that the object represents.

Some built-in objects in JavaScript are iterable, such as arrays, strings, and maps. You can also create your own iterable objects by implementing the Symbol.iterator property.

Here is an example of how to iterate over an array:

const array = [1, 2, 3, 4, 5];

for (const element of array) {
  console.log(element);
}

This code will loop over the elements of the array and log each element to the console.

Here is an example of how to create your own iterable object:

const myIterable = {
  [Symbol.iterator]() {
    const counter = 0;

    return {
      next() {
        if (counter < 10) {
          return {
            value: counter,
            done: false,
          };
        } else {
          return {
            value: undefined,
            done: true,
          };
        }
      },
    };
  },
};

for (const element of myIterable) {
  console.log(element);
}

This code creates an iterable object that generates the numbers from 0 to 9. The Symbol.iterator property returns an iterator that loops over the numbers one at a time. The iterator returns an object with two properties: value and done. The value property contains the current element in the sequence, and the done property is set to true when there are no more elements in the sequence.

Iterables are a powerful tool that can be used to iterate over data structures in a consistent and efficient way. They are a key part of the JavaScript language and are used in many different libraries and frameworks.

Previous post Javascript/Typescript/Node.js Interview Questions Part -1 JavaScript questions
Next post Javascript/Typescript/Node.js Interview Questions Part -3 JavaScript questions