Javascript Interview Preparation: Part 4

Demystifying Closures

Closures are one of the most important topics for Javascript interviews. In this article, we are going to discuss theory, code and some important output questions on closures. So lets get this party started?

Lexical Scoping

So before learning about closures first we will need to discuss about lexical scoping.
It simply means a variable declared outside a function scope will be accessbile inside it but the opposite is not true.

var a="hello";//global scope

function displayHello(){
console.log(a);//function scope
}

displayHello();

The above code will work and give the expected output.

But this code where the opposite is implemented will give error :

function displayHello(){
var a="hello";
}

console.log(a);

displayHello();

Here is another example of lexical scoping.

function init() {
  var name = "hello"; // name is a local variable created by init in this 
//parent scope
  function displayName() {
    // displayName() is the inner function, that forms the closure
    console.log(name); // use variable declared in the parent scope here in the
//local scope
  }
  displayName();
}
init();

And heres the expected output :

What are Closures?

According to MDN Web Docs,

A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function's scope from an inner function. In JavaScript, closures are created every time a function is created, at function creation time.

Heres an example:

function makeFunc() {
  const name = "hello";
  function displayName() {
    console.log(name);//reference to name's value which is "hello" being 
//retained even after makeFunc's call gets over
  }
  return displayName;
}

const myFunc = makeFunc();//this returns displayName func's reference
//such that name which was originally in makeFunc definition is also maintained
//due to the formation of a closure and this thing is not always possible in 
//other languages where once makeFunc's call gets over , name is also destroyed

myFunc();//will print "hello" as expected

Output:

💡
Closures are useful because they let you associate data (the lexical environment) with a function that operates on that data. This has obvious parallels to object-oriented programming, where objects allow you to associate data (the object's properties) with one or more methods.

Consider this modified example:

function makeFunc(name) {
  function displayName() {
    console.log(name);
  }
  return displayName;
}
//...()() simply means calling the function returned by the first call
//closures remember the data associated and hence are very useful
//where private variables are required
makeFunc("mainak")();//here the closure will have name="mainak" and hence
//"mainak" will get printed

makeFunc("hello")();//here "hello" will be remembered and get printed

Heres the expected output:

Okay I hope we got to understand little bit about closures .So now lets delve into Closure Scoping Chain.

Closure Scoping Chain

Till now what we have seen is: a local scope, outer function scope and global scope. What we are yet to see that the outer function is itself nested. So what we need to remember here is that the inner scope will have access to all the nested scopes upto global scope.

Lets see an example:

// global scope
const e = 10;
function sum(a) {
//outer function 3 scope
  return function (b) {
    //outer function 2 scope
    return function (c) {
      // outer function 1 scope
      return function (d) {
        // local scope
        return a + b + c + d + e;//all the variables are accessible here
      };
    };
  };
}

console.log(sum(1)(2)(3)(4)); // 20

Output :

💡
Hint: see the immediate block in which a variable is declared and remember no matter how many blocks deeper you go inside as long as are inside the declaration block, you can access it. But again don't try to access it before its declaration.

I hope you were able to understand the content discussed till now and now we will dive into some interview questions.

Interview Questions-the fun starts here

Question 1

Have a look at the below code using IIFE (have a look at my previous blog for understanding this topic) and try to find the output and then see the explanation:

let count=0;

(function printCount(){
if(count===0){
let count=1;
console.log(count);
}
console.log(count);
})();

Explanation:

let count=0;//declared in global scope and will be accessible inside the func
//here an IIFE is used basically the function defined will get invoked 
//immediately
(function printCount(){
if(count===0)//count = 0 is true
{
let count=1;//here shadowing will occur and local variable will shadow 
//outer variable with same name
console.log(count);//count=1 will be used
}
console.log(count);//here the count(=1) is no longer accessible but the global
//scoped count(=0) is
})();

//output should be 1 0

Output:

Question 2:

You are required to write a function which will allow you to do this:

function createBase(num){
//something here
}

var addFive=createBase(5);
addFive(10);//returns 15
addFIve(21);//returns 26

Solution:

function createBase(num){
return function(x){
   console.log(x+num);
 }
}

var addFive=createBase(5);
addFive(10);
addFive(21);

var addSix=createBase(6);
addSix(11);//will give 17
addSix(20);//will give 26

//expected output: 15 26 17 26

Output:

Question 3:

See the below code and try to figure out that how using closures you can optimise the time complexity of it:

function find(index){
let a=[];
for(let i=0;i<1000000;i++){
a[i]=i+1;
} //quite a big loop running here
console.log(a[index]);
}

console.time("5");
find(5);
console.timeEnd("5");
console.time("10");
find(10);
console.timeEnd("10");
//time and timeEnd are simply used to return the time taken
//for the execution of the statement

Solution:

If you see carefully, everytime we are trying to find the value , we are recreating the array and running the whole loop again .So for searching n elements and for finding each element running the loop lets sat m times will result in time complexity of O(n*m) but we can reduce it if don't run the loop every time and rather have it stored in lexical environment using closure.

function returnFindHavingArray() {
  let a = [];
  for (let i = 0; i < 1000000; i++) {
    a[i] = i + 1;
  }
  return function (index) {
    console.log(a[index]);
  };
}

var find=returnFindHavingArray();
console.time("5");
find(5);
console.timeEnd("5");
console.time("10");
find(10);
console.timeEnd("10");

So what we did here is run the loop to prepare the array only once and return the function having its reference. Now the function can be used again and again to find the values without re-running the loop. Now the time complexity has been reduced to O(m+n).

Output:

As you can see the time taken is drastically less.

Ouestion 4:

Here there are two functions given and you will need to tell whether their outputs are going to be the same or if they are different ,what are the ouptuts?

//function 1
for (var i = 0; i < 3; i++) {
  setTimeout(() => {
    console.log(i);
  }, i * 1000);
}

//function 2
for (let i = 0; i < 3; i++) {
  setTimeout(() => {
    console.log(i);
  }, i * 1000);
}

Explanation: They are going to be different since var does not have a block scope.

//function 1
for (var i = 0; i < 3; i++) {
  setTimeout(() => {
    console.log(i);
  }, i * 1000);
//here what happens is reference to the value of i is not maintained ,its like
// after 0s  i neds to be printed, after 1s i needs to be printed and like this 
//it goes on and in this case its like i i i so after loop gets completed i=3
//and then timed functions run and give output 3 3 3
}

//function 2
for (let i = 0; i < 3; i++) {
  setTimeout(() => {
    console.log(i);
  }, i * 1000);
//here the reference to the value is maintained and hence its like first 0 will 
//be printed and then 1 and then 2 and hence the output is 0 1 2
}

Output:

Now a follow up question ,how using var only you can rewrite the function 1 to generate 0 1 2 as output?

Well of course by using closure since it will retain the value ina lexical enviornment and hence when the timed functions run they can access the correct values.

for (var i = 0; i < 3; i++) {
  function closure(i) {
    setTimeout(() => {
      console.log(i);
    }, i * 1000);
  }
  closure(i);//here what happens is the value of i gets retained in each 
//separate lexical environment getting created and hence on running the 
//timed functions they are able to access the correct values
}

Output:

Question 5:

Using closure, you are needed to create a private counter.

function createCounter(initialValue) {
  var _counter = initialValue;
  //assigning the _counter with its initial value
  function add(x) {
    _counter += x; //updating _counter's value which is maintained in the
    // closure
  }

  function show() {
    return _counter; //returning the _counter value retained in the closure
  }

  return {
    add,
    show,
  };
  //returning the functions wrapped in an object
}

var counter = createCounter(6); //returning the counter object having functions
//with access to _counter with initial value of 6
console.log("Initial value =", counter.show());
counter.add(12);
console.log("On adding 12 =", counter.show());
counter.add(10);
console.log("After further adding 10 =", counter.show());

Output:

Question 6:

Okay using code explain what is module pattern.

Explanation:

var Module = (function() {
    function _privateMethod() {
        // do something
    }

    return {
        publicMethod: function() {
            // can call privateMethod();
        }
    };
})();
// so whats happening here is that an object having publicMethod's reference is
//returned but it does not directly return the reference to the private method
//the public method can call the private method internally due to closure

Question 7:

View the below code and make appropriate changes such that the code runs only once even on multiple calls.

function hello(){
    console.log("hello");
}

hello(); // hello
hello(); // hello
hello(); // hello
hello(); // hello

Explanation: using closure , we can track of whether the function call is made more than once and accordingly we can run the code.

function main() {
  var _counter = 0;
  //assigning the _counter with 0 as initial value

  function hello() {
    console.log("hello");
  }

  function decider(x) {
    _counter += 1; //updating _counter's value which is maintained in the
    // closure and will decide whether the hello code needs to run
    if (_counter == 1)
      //function call is made only once till now
      hello();
  }

  return decider;
  //returning the functions wrapped in an object
}

const decider = main(); //returning the decider func which will decide
//whether hello func will run or not
decider(); // will give "hello"
decider(); // no output
decider(); // no output

Output:

Question 8:

View the below code and memoise/cache it.

const heavyProduct = (num1,num2) => {
  for (let i = 1; i <= 100000000; i++) {
   //some heavy calculation
}
  return num1*num2;
});

console.time("First call");
console.log(heavyProduct(9467,2345));
console.timeEnd("First call");

console.time("Second call");
console.log(heavyProduct(8234,1234));
console.timeEnd("Second call");

Currently for both of the calls, it takes a lot of time. We can memoise the function such that for a pair of parameters , we can store it in an object and keep updating the object based on the pairs encountered and their values calculated. And in case we encounter a pair whose value we have already stored , we can directly give out the result from the object without going through the heavy calculation.

Solution:

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

  return function (...args) {
    const argsIndex = JSON.stringify(args);
    if (!res[argsIndex]) 
             res[argsIndex] = func(...args);
    return res[argsIndex];
  };
}

const heavyProduct = memoize((num1,num2) => {
  for (let i = 1; i <= 100000000; i++) {
    //heavy calculation
  }

  return num1*num2;
});

console.time("First call");
console.log(heavyProduct(9467,2345));
console.timeEnd("First call");

// use a different value
console.time("Second call");
console.log(heavyProduct(8234,1234));
console.timeEnd("Second call");

//use one pair which has been already calculated
console.time("Third call");
console.log(heavyProduct(8234,1234));
console.timeEnd("Third call");

Output:

As you can see on reusing stored values, we were able to trim down a lot of time for the third case.

Wrapping Up

So yep thats it ! I hope you enjoyed the article and I really wish that it helps you in your interviews . Happy interviewing!