TMThiru Manikandan

20 Essential JavaScript Interview Questions

Below is a mix of easy, medium, and hard JavaScript interview questions on ES6, closures, events, DOM, functional programming, and real-world problems.

1. Sort the array of objects by year in descending order


    const arr = [
      {name: "amy", year:"1989"},
      {name: "ben", year:"1991"}
    ]
  
Show answer

    arr.sort((a,b) => b.year - a.year);
  

The callback function passed to the sort function determines the sort order. In the code above, the function compares the two values. When the result is greater than zero, b is placed before a which means they will be in descending order. If the result is less than zero, they're already in descending order.

2. What does console.log print?


    const global = 1;

    function outer(x) {
        function inner(y) {
            return global + x + y;
        }
        return inner;
    }

    const calc = outer(10);
    console.log(calc(20));
  
Show answer

It will print 31. This code is a typical example of closure. The inner function has access to the outer function's x variable even after it returns and when the inner function is called it uses the latest value of x.

3. What is the output of each comparison?


    "test" === "test"

    [1,2,3] === [1,2,3]

    {} === {}
  
Show answer

    "test" === "test" //true

    [1,2,3] === [1,2,3] //false

    {} === {} //false
  

For primitive types like number, string, boolean, null, and undefined, the values are used for comparison. For objects like arrays, functions, and literal objects {}, object references are used for comparison.

4. What does console.log print?


    const test = {
        value : 10,
        getValue: () => this.value
    };

    console.log(test.getValue());
  
Show answer

It prints undefined. Note that any pair of curly braces defines a new scope. In an arrow function the value of this is derived from the enclosing scope. Here, the enclosing scope is that of the test object.

In the test object, the value of this is the window object. Since there is no 'value' set in the window object, it evaluates to undefined. The code below fixes the issue.


    const test = {
        value : 10,
        getValue: function() { return this.value  }
    };
    console.log(test.getValue()); //prints 10
 

This works because in a function expression the value of this is determined by how a function is invoked. getValue is invoked as a method of test object. So, the value of this is set to the test object.

5. You're given an array of 5 elements. Multiply each element by 10. Remove elements below 40. Find the sum of the result array. Use built-in array methods to complete this task.


  Input array   [1,2,3,4,5]
                      |
               [10,20,30,40,50]
                      |
                   [40,50]
                      |
   Result            90
  
Show answer

    arr.map(elem => elem * 10)
       .filter(elem => elem >= 40)
       .reduce((acc, curr) => acc + curr);
 

6. Does this code throw an error?


    const test = {count:1};
    test.count = 2;
  
Show answer

The code doesn't throw an error because const is about assignment and not mutation. If you define a variable as const, a new value can't be assigned to a variable. But, the value itself - the object - can be mutated.

7. Write code to select only the list item with data-color value 'blue'


    <ul id="parent">
        <li data-color="black"></li>
        <li data-color="blue"></li>
        <li data-color="red"></li>
    </ul>
  
Show answer

    document.querySelector("ul#parent li[data-color='blue']");
  

8. Does "hi" gets printed in exactly 1 second?


    setTimeout(() => console.log("hi"), 1000)
  
Show answer

There is no guarantee that "hi" gets printed in exactly 1 second. It will take atleast 1 second but there is no upper limit.

This is because JavaScript is single threaded and event driven. User events like click, network events like fetch, and timer events generated by setTimeout and setInterval are added to an event queue as they're generated.

JavaScript processes events from the queue one by one. If other events are ahead of our timeout call back function in the queue (or) if any other event is still being processed, it could take more than 1 second for our function to be called.

9. The code below uses global variable to increment a counter. Rewrite it using closure to avoid using the global variable.


    let count = 0;

    function increment() {
        count++;
        return count;
    }

    increment();
    
Show answer

    function increment() {
        let count = 0;

        function inner() {
          count++;
          return count;
        }

        return inner;
    }

    const inc = increment();
    inc(); //1
    inc(); //2
    

10. Flatten an array at any depth

Write a function that will flatten an array as below. The elements could be nested deep to any level.


   Input - [[1,2]] Output - [1,2]

   Input - [[1],[2,[3,[4]]]] Output - [1,2,3,4]
  
Show answer

The code below flattens an array at any depth in a recursive way.


    function flatten(arr) {
        if (!Array.isArray(arr))
            return [];

        let result = [];

        for (const elem of arr) {
            if (Array.isArray(elem))
                result.push(...flatten(elem));
            else
                result.push(elem);
        }

        return result;
    }
  

In the input array, an element could be a value or an array. If it's an array, the flatten function is called recursively. Note the use of spread ... operator to expand the returned array into individual elements before adding them to the result.

11. Determine if a string is a palindrome. Use built-in string and array methods for this task.


    palindrome("kayak") //returns true
    palindrome("love"); //returns false
    palindrome(""); //returns false
  
Show answer

    function palindrome(str) {
        return str ? str === str.split('').reverse().join('') : false;
    }
  

If the input is a valid string, it's split into an array, the array is reversed, and then joined as a new string. The original string is then compared with the new string.

12. What is the output of the code?


   const promise = new Promise((resolve, reject) => reject());

   promise
    .then(() => console.log('first'))
    .catch(() => console.log('second'))
    .then(() => console.log('third'));
  
Show answer

'second' and 'third' will be printed. Why is that? The function passed to the Promise constructor is known as the executor. When the executor calls reject, control will pass to catch and 'second' gets printed. Once it's completed, control falls through to the next then and 'third' gets printed.

13. Assume that the below functions are defined in Math.js. Write code to import all the functions in a different module in the same directory.


    //Math.js
    export const add = (a,b) => a+b;
    export const subtract = (a,b) => a-b;

    export default multiply = (a,b) => a*b;
  
Show answer

    import multiply, {add, subtract} from "./Math.js";
  

14. Write your own version of Array's map method. Function signature of the map method is below.


    map(array, callback)

    For example - map([1,2,3], elem => elem*2) returns [2,4,6]
  
Show answer

    function map(array, callback) {
        let index = -1;
        const length = array == null ? 0 : array.length;
        const result = [];

        while (++index < length) {
            result[index] = callback(array[index], index, array);
        }
        return result;
    }
 

For every array element, the callback function is called with the element, its index and the array itself. The return value is added to the result array.

Only the element is strictly required to pass to the callback. Index and the array are optional.

15. In the HTML below, when any of the span elements are clicked, print 'clicked' to the console. Assume you have hundreds of span elements.


    <div id="parent">
        <span id="one">one</span>
        <span id="two">two</span>
        <span id="three">three</span>
        <span id="four">four</span>
        <span id="five">five</span>
        ...
    </div>
  
Show answer

  document.getElementById("parent").addEventListener("click", (e) => {
    if (e.target && e.target.nodeName === "SPAN") {
        console.log("clicked")
    }
  });
 

The code above works through event bubbling. When any of the span elements are clicked, the event bubbles to the parent div and the parent's event listener captures the event. It checks if the event target matches the span element.

It's inefficient to write an event listener for every span element. Also, writing the event listener in the parent allows us to dynamically add and remove span elements.

16. You're given an array of language codes. Use an ES6 feature in the construction of the object's keys.


   const codes = ["en", "fr" ,"de", "ta"];

   //result
   {"lang-en": true, "lang-fr": true, "lang-de": true, "lang-ta": true};
  
Show answer

    let result = {};
    codes.forEach(code => result['lang-'+code] = true);
 

The code uses the computed property name feature of ES6 to dynamically construct the object's key.

17. Write a Memoization function of the form below


    //It takes a function as a parameter and returns a memoized version of it
    function memoize(func) {}
  

A memoized function doesn't rerun its function when it receives the same arguments more than once. It caches the result of every unique combination of arguments.

Show answer

    function memoize(func) {
        let cache = {};

        function inner(...args) {
            const key = JSON.stringify(args);

            if(key in cache)
                return cache[key];

            cache[key] = func.apply(this, args);

            return cache[key];
        }

        return inner;
    }
  

In the code above, we receive a function func and return a memoized version of it using closure. Note the use of rest parameter ... in args to get any number of arguments as an array.

We build a cache with keys and values. The key is a string formed by combining all the arguments to the function func. The value is the result of calling the function using apply.

18. Write code to create an array that contains values in first array but not in second. Use built-in array methods to complete this task.


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

    //result
    [1,2,3]
  
Show answer

    first.filter(elem => !second.includes(elem));
 

19. Write code so that when the button is clicked, the form shouldn't be submitted and the button value is changed from 'confirm' to 'cancel'


    <form>
        <button id="submit">confirm</button>
    </form>
  
Show answer

    document.getElementById("submit").addEventListener("click", event => {
        event.preventDefault();
        event.target.innerHTML = "cancel";
    });
  

20. Write a function that can be called only once


   let test = () => console.log("entered test");
   test  = once(test); //write the once function

   test(); //prints "entered test"
   test(); //function is not called and nothing is printed

  
Show answer

  function once(func) {
    let count = 2;
    let result;

    if (typeof func !== 'function') {
        throw new TypeError('Not a function');
    }

    return function(...args) {
        if (--count > 0)
            result = func.apply(this, args);

        return result;
    }
  }
 

Since count is closed over, it's easy to keep track of how many times a function is invoked.