DEV Community

Cover image for πŸ”₯ JavaScript Interview Series(9): Working with Arrays and Objects Like a Pro
jackma
jackma

Posted on

πŸ”₯ JavaScript Interview Series(9): Working with Arrays and Objects Like a Pro

1. What's the difference between == and === when comparing objects and arrays?

Key concepts: Equality, type coercion, reference vs. value.

Standard Answer:
When comparing objects or arrays, both the double equals (==) and triple equals (===) operators check for referential equality, not value equality. This means they check if the two variables point to the exact same object in memory, not if they have the same properties and values.

  • === (Strict Equality): This operator checks if the two operands are of the same type and have the same value. For objects and arrays, it returns true only if the variables reference the same object.
  • == (Abstract Equality): This operator will attempt to convert and compare operands of different types. However, when both operands are objects (which includes arrays), it behaves exactly like === and checks for reference.

Here’s a quick example:

const arr1 = [1, 2, 3];
const arr2 = [1, 2, 3];
const arr3 = arr1;

console.log(arr1 == arr2);  // false
console.log(arr1 === arr2); // false
console.log(arr1 === arr3); // true
Enter fullscreen mode Exit fullscreen mode

Even though arr1 and arr2 have identical contents, they are two separate objects in memory, so they are not considered equal.

Possible 3 Follow-up Questions: πŸ‘‰ (Want to test your skills? Try a Mock Interview β€” each question comes with real-time voice insights)

  1. How would you then check if two arrays or objects have the same values?
  2. Can you explain what happens when you compare null and undefined using == versus ===?
  3. Provide an example of where == type coercion can be useful and an example where it can be dangerous.

2. How can you deep clone an object or array in JavaScript?

Key concepts: Deep vs. shallow copy, data structures, recursion.

Standard Answer:
A shallow copy only copies the top-level properties. If a property is a reference to another object, only the reference is copied, not the object itself. A deep clone creates a completely independent copy of the original object and all its nested objects.

Here are a few ways to achieve a deep clone:

  1. JSON.stringify() and JSON.parse(): This is a quick and easy way for objects that only contain JSON-safe data types (no functions, undefined, Symbols, etc.).

    const original = { a: 1, b: { c: 2 } };
    const clone = JSON.parse(JSON.stringify(original));
    clone.b.c = 20; // This won't change the original object
    
  2. structuredClone(): This is a modern, built-in API designed specifically for deep cloning. It can handle more complex data types than the JSON method, such as Date, RegExp, and even circular references in some environments.

    const original = { a: new Date(), b: { c: 2 } };
    const clone = structuredClone(original);
    
  3. Manual Recursion or Libraries: For complex scenarios, you might write a recursive function that iterates through all properties and creates copies. Alternatively, libraries like Lodash offer robust _.cloneDeep() methods.

Possible 3 Follow-up Questions: πŸ‘‰ (Want to test your skills? Try a Mock Interview β€” each question comes with real-time voice insights)

  1. What are the limitations or downsides of using the JSON.stringify/parse method?
  2. How would you write a simple recursive function to deep clone an object?
  3. What is a circular reference, and how does it pose a problem for deep cloning algorithms?

3. Explain the difference between Array.prototype.map() and Array.prototype.forEach().

Key concepts: Immutability, method chaining, return values.

Standard Answer:
The primary difference between map() and forEach() lies in their return value.

  • map(): Iterates over an array, applies a callback function to each element, and returns a new array containing the results. It is ideal for creating a transformed version of an array without altering the original. This makes it chainable with other array methods.

    const numbers = [1, 2, 3];
    const doubled = numbers.map(num => num * 2); // returns [2, 4, 6]
    
  • forEach(): Executes a callback function for each element in the array but returns undefined. It is a "terminal" method used purely for its side effects, like printing to the console or making an API call for each item. It does not create a new array.

    const numbers = [1, 2, 3];
    numbers.forEach(num => console.log(num)); // logs 1, then 2, then 3
    

In short, use map() when you want to transform data into a new array. Use forEach() when you just want to do something with each element.

Possible 3 Follow-up Questions: πŸ‘‰ (Want to test your skills? Try a Mock Interview β€” each question comes with real-time voice insights)

  1. Can you modify the original array from within a map() or forEach() callback? Is this a good practice?
  2. How could you achieve the functionality of map() using reduce()?
  3. When would you choose a for...of loop over forEach()?

4. What is Array.prototype.reduce() and can you provide a practical use case?

Key concepts: Aggregation, accumulators, functional programming.

Standard Answer:
The reduce() method executes a "reducer" function on each element of the array, resulting in a single output value. It takes a callback function and an optional initial value as arguments. The callback receives two main arguments: the accumulator (the value resulting from the previous iteration) and the current value.

A classic use case is summing up an array of numbers:

const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
// accumulator starts at 0 (the initial value)
// 1st run: 0 + 1 => 1
// 2nd run: 1 + 2 => 3
// 3rd run: 3 + 3 => 6
// ... and so on. Final result: 15
Enter fullscreen mode Exit fullscreen mode

However, reduce() is incredibly versatile. It can be used to flatten an array, group objects by a property, or even implement other array methods like map and filter.

Possible 3 Follow-up Questions: πŸ‘‰ (Want to test your skills? Try a Mock Interview β€” each question comes with real-time voice insights)

  1. What happens if you don't provide an initial value to reduce()?
  2. Show me how to group an array of objects by a specific property using reduce().
  3. Explain how reduce() can be used to "flatten" an array of arrays.

5. How does object and array destructuring work?

Key concepts: Syntax, assignment, default values.

Standard Answer:
Destructuring is a JavaScript expression that makes it possible to unpack values from arrays, or properties from objects, into distinct variables. It provides a more concise and readable way to access nested data.

Object Destructuring:
You use curly braces {} to specify the properties you want to extract.

const user = {
  id: 42,
  name: 'Alice',
  details: {
    age: 30,
    email: 'alice@example.com'
  }
};

// Basic destructuring
const { name, id } = user;
console.log(name); // 'Alice'

// Renaming variables and extracting nested properties
const { name: userName, details: { age } } = user;
console.log(userName); // 'Alice'
console.log(age); // 30```
{% endraw %}


**Array Destructuring:**
You use square brackets {% raw %}`[]`{% endraw %} to unpack values based on their index.
{% raw %}

```javascript
const rgb = [255, 128, 0];

const [red, green, blue] = rgb;
console.log(red); // 255

// You can also skip elements
const [r, , b] = rgb;
console.log(b); // 0
Enter fullscreen mode Exit fullscreen mode

Possible 3 Follow-up Questions: πŸ‘‰ (Want to test your skills? Try a Mock Interview β€” each question comes with real-time voice insights)

  1. How can you assign default values to variables during destructuring?
  2. How can you use destructuring in a function's parameter list?
  3. What is the purpose of the "rest" syntax (...) in destructuring?

6. What are the different ways to iterate over an object's properties?

Key concepts: Enumerability, own vs. inherited properties, iteration protocols.

Standard Answer:
There are several ways to iterate over an object's properties, each with its own specific use case:

  1. for...in loop: Iterates over all enumerable properties of an object, including those inherited from its prototype chain. This is often not recommended for iterating over object data because of the prototype chain inclusion.

    for (const key in myObject) {
      if (myObject.hasOwnProperty(key)) { // Important check!
        // ...
      }
    }
    
  2. Object.keys(obj): Returns an array of a given object's own enumerable property names (keys). You can then iterate over this array.

    Object.keys(myObject).forEach(key => {
      console.log(key, myObject[key]);
    });
    
  3. Object.values(obj): Returns an array of a given object's own enumerable property values.

  4. Object.entries(obj): Returns an array of a given object's own enumerable string-keyed property [key, value] pairs. This is often the most convenient method.

    for (const [key, value] of Object.entries(myObject)) {
      console.log(`${key}: ${value}`);
    }
    

Possible 3 Follow-up Questions: πŸ‘‰ (Want to test your skills? Try a Mock Interview β€” each question comes with real-time voice insights)

  1. What does it mean for a property to be "enumerable"?
  2. Why is it often necessary to use hasOwnProperty() inside a for...in loop?
  3. How would you iterate over properties that are Symbols?

7. Explain the concept of "immutability" in the context of JavaScript arrays and objects.

Key concepts: Mutation, pure functions, state management.

Standard Answer:
Immutability is the principle that data (like objects and arrays) should not be changed after it's created. Instead of modifying the original data structure, you create a new one with the updated values.

Mutating (changing the original):

const colors = ['red', 'green'];
colors.push('blue'); // This mutates the 'colors' array.
Enter fullscreen mode Exit fullscreen mode

Immutable approach (creating a new one):

const colors = ['red', 'green'];
const newColors = [...colors, 'blue']; // Spreading creates a new array.
Enter fullscreen mode Exit fullscreen mode

Working with immutability is crucial in modern JavaScript frameworks (like React) and state management libraries (like Redux). It helps prevent bugs caused by unexpected side effects, makes state changes predictable, and can improve performance by making it easier to detect changes.

Common immutable array methods: map, filter, reduce, concat, slice.
Common mutating array methods: push, pop, splice, sort, reverse.

Possible 3 Follow-up Questions: πŸ‘‰ (Want to test your skills? Try a Mock Interview β€” each question comes with real-time voice insights)

  1. Why is immutability important for state management in a library like React?
  2. How can you update a property in a nested object in an immutable way?
  3. What are the potential performance downsides of immutability, and how can they be mitigated?

8. What is the difference between slice() and splice() for arrays?

Key concepts: Immutability, method signature, return values.

Standard Answer:
This is a classic "gotcha" question. The key difference is that slice() is immutable (it returns a new array) while splice() is mutating (it changes the original array).

  • slice(startIndex, endIndex):

    • Returns a new array containing a shallow copy of a portion of the original array.
    • The original array is not modified.
    • The endIndex is not included in the result.
    const animals = ['ant', 'bison', 'camel', 'duck', 'elephant'];
    const middle = animals.slice(1, 4); // ['bison', 'camel', 'duck']
    console.log(animals); // Original is unchanged
    
  • splice(startIndex, deleteCount, ...itemsToAdd):

    • Modifies the original array by removing, replacing, or adding elements.
    • Returns an array containing the deleted elements.
    const months = ['Jan', 'March', 'April', 'June'];
    months.splice(1, 0, 'Feb'); // Inserts 'Feb' at index 1
    console.log(months); // ['Jan', 'Feb', 'March', 'April', 'June']
    months.splice(4, 1, 'May'); // Replaces 1 element at index 4
    console.log(months); // ['Jan', 'Feb', 'March', 'April', 'May']
    

Possible 3 Follow-up Questions: πŸ‘‰ (Want to test your skills? Try a Mock Interview β€” each question comes with real-time voice insights)

  1. How can you use slice() to create a shallow copy of an entire array?
  2. Show how to remove just one element from an array using splice() without adding any new ones.
  3. If you use a negative index with slice() or splice(), what does it represent?

9. How can you find a specific object in an array of objects?

Key concepts: Iteration, search algorithms, array methods.

Standard Answer:
There are several effective ways to find an object in an array based on one of its properties.

  1. Array.prototype.find(): This is the most modern and readable method. It returns the first element in the array that satisfies the provided testing function. If no values satisfy the testing function, undefined is returned.

    const users = [
      { id: 1, name: 'Alice' },
      { id: 2, name: 'Bob' },
      { id: 3, name: 'Charlie' }
    ];
    const bob = users.find(user => user.name === 'Bob');
    // bob is { id: 2, name: 'Bob' }
    
  2. Array.prototype.findIndex(): This is similar to find(), but it returns the index of the first element that satisfies the condition, or -1 if no such element is found.

  3. Array.prototype.filter(): This method can also be used, but it's less efficient if you only need the first match, as it will iterate through the entire array and return a new array of all matching elements.

Possible 3 Follow-up Questions: πŸ‘‰ (Want to test your skills? Try a Mock Interview β€” each question comes with real-time voice insights)

  1. What is the performance difference between using find() and filter() if you only need one item?
  2. How would you find all objects that match a certain condition, not just the first one?
  3. What would you do if you needed to find an object based on a property of a nested object?

10. Explain the spread (...) and rest (...) syntax for arrays and objects.

Key concepts: Syntax, function arguments, immutability.

Standard Answer:
While the syntax is the same (...), the spread and rest operators are used in different contexts and have opposite effects.

Spread Syntax:
The spread syntax expands an iterable (like an array) or an object's properties into places where multiple elements or properties are expected. It's commonly used for creating new arrays/objects immutably or for passing array elements as individual arguments to a function.

  • For Arrays:

    const arr1 = [1, 2, 3];
    const arr2 = [...arr1, 4, 5]; // [1, 2, 3, 4, 5]
    
  • For Objects (ES2018+):

    const obj1 = { a: 1, b: 2 };
    const obj2 = { ...obj1, c: 3 }; // { a: 1, b: 2, c: 3 }
    

Rest Syntax:
The rest syntax collects multiple elements or properties into a single array or object. It's used in function parameters and destructuring assignments.

  • In Function Parameters: It gathers an indefinite number of arguments into an array.

    function sum(...numbers) { // 'numbers' will be an array
      return numbers.reduce((acc, curr) => acc + curr, 0);
    }
    sum(1, 2, 3); // 6
    
  • In Destructuring: It collects the "rest" of the properties or elements.

    const [first, second, ...others] = [1, 2, 3, 4, 5];
    // first = 1, second = 2, others = [3, 4, 5]
    

Possible 3 Follow-up Questions: πŸ‘‰ (Want to test your skills? Try a Mock Interview β€” each question comes with real-time voice insights)

  1. What happens if you use the spread syntax on an object with a property that also exists in the object you're spreading into?
  2. Can you use the rest syntax anywhere else besides the end of a parameter list or destructuring assignment?
  3. How does the spread syntax differ from Object.assign()?

Top comments (0)