Fix 'this' Coding Interview Question | Skilled.dev
Interview Question

Fix 'this'

Course launching November 17, 2020

Follow me on YouTube for free coding interview videos.

Users who sign up for the email list will receive an exclusive 75% discount at launch.

Your subscription could not be saved. Please try again.
Your subscription was successful! 🤓
Loading...

You are building a web application in JavaScript to host meetings. You are on a tight deadline, so you immediately head to StackOverflow to copy-paste some code to get started.

You try it out in the JS console in your browser, but it doesn't work. The getPresenterName and printSeatNumber calls are both broken. The getPresenterName should return the name of the first person in the users array, and printSeatNumber should console.log their index in the users array.

You should only make changes to the Meeting class. Fix these two issues by using the minimal amount of refactoring required, and don't add any new properties or methods to either class.

Each function has its own independent bug, so you should fix them separately.

class User {
  constructor(name) {
    this.name = name;
  }
  getName() {
    return this.name;
  }
  printSeatNumber() {
    console.log('The seats have not been set');
  }
}

class Meeting {
  constructor(users) {
    const presenter = users[0];
    this.getPresenterName = presenter.getName;
    this.setUserSeats(users);
  }
  setUserSeats(users) {
    for (var i = 0; i < users.length; i++) {
      users[i].printSeatNumber = () => {
        console.log(i);
      }
    }
  }
}

const users = [new User('Jeni'), new User('Dan'), new User('Carol')];
const meeting = new Meeting(users);

// Broken results
console.log(meeting.getPresenterName()); // undefined (expected ‘Jeni’)
users[0].printSeatNumber(); // 3 (expected 0)
users[1].printSeatNumber(); // 3 (expected 1)
users[2].printSeatNumber(); // 3 (expected 2)

This question is a bit open ended because there are many ways to fix or refactor a problem. This also isn't a data structure and algorithm question, so we can't use Big O complexity as our guiding metric. Instead, we're told to fix the bugs each case by performing as little refactoring as possible and by only changing Meeting.

Once we have the code debugged, the actual output should be:

console.log(meeting.getPresenterName()); // Jeni
users[0].printSeatNumber(); // 0
users[1].printSeatNumber(); // 1
users[2].printSeatNumber(); // 2

To solve our problem, we will focus on each issue separately.

getPresenterName()

We set the getPresenterName function in the contructor of Meeting. At first glance it seems pretty straightforward. We consider the first user to be the presenter, so we assign their getName method to getPresenterName. However, when we call it, we get back undefined when we're expecting it to be "Jeni".

To begin debugging, we console.log(presenter) which does match to the "Jeni" user. Let's now console.log(this) inside getName. When we do, we realize that this is pointing to Meeting even thought we originally define this.name on the user.

With JavaScript, the context of this is set based on where a function is called and not where it is defined. When we assign this.getPresenterName = presenter.getName;, we are changing the object that calls the method. To fix this bug, we need to bind getName with the presentor.

constructor(users) {
  const presenter = users[0];

  // Bind our the presenter since this.name
  // needs to refer to the user but is called in the Meeting object.
  this.getPresenterName = presenter.getName.bind(presenter);

  this.setUserSeats(users);
}

printSeatNumber()

When we try to print the seat for any user, it logs 3 every time. This is interesting because the index 3 is larger than our input array, and it is duplicated for each user even though we use their index in each loop.

It appears that it is using the value of i after the loop ends - i < array.length is 3 < 3 which is false and the terminating condition.

Since we declared our variable var i = 0, it is function scoped. Since printSeatNumber is a callback, it has formed a closure around i and the value is controlled by the outer function scope. What happens is that the declaration of var i is hoisted outside the for loop to the top of the function and its value persists inside the entire scope.

setUserSeats(users) {
  var i;

  for (i = 0; i < users.length; i++) {
    users[i].printSeatNumber = () => {
      console.log(i);
    }
  }

  console.log(i); // 3
}

One way to fix this would be to create a new scope inside our callback. We can create an IIFE (immediately invoked function expression) where we pass the index as a parameter, and then our callback will use this new value.

setUserSeats(users) {
  for (var i = 0; i < users.length; i++) {
    // Wrap the callback in an IIFE to scope the index
    users[i].printSeatNumber = ((seatNumber) => {
      return () => console.log(seatNumber);
    })(i);
  }
}

There's an even more concise way to handle it though. In ES2015, there were two new ways to declare variables - let and const. These values are block scoped instead of function scoped. This means if we declare let i = 0, its value will only live inside a given iteration and will be what the callback has access to.

setUserSeats(users) {
  // Use let instead of var which is block scoped
  for (let i = 0; i < users.length; i++) {
    users[i].printSeatNumber = () => {
      console.log(i);
    }
  }
}

Solution

This problem contained two very common issues in JavaScript. The first is using the correct this context since it changed based on where the method was called, which we solved by calling bind with the correct object. The other issue was a bug with our scope which we fixed by changing var to let so that the array index was retained within block scope instead of being controlled in the function scope of setUserSeats.

The complete changes we make to Meeting to fix our code:

class Meeting {
  constructor(users) {
    const presenter = users[0];

    // Bind our the presenter since this.name
    // needs to refer to the user but is called in the Meeting object.
    this.getPresenterName = presenter.getName.bind(presenter);

    this.setUserSeats(users);
  }
  setUserSeats(users) {
    // Use let instead of var which is block scoped
    for (let i = 0; i < users.length; i++) {
      users[i].printSeatNumber = () => {
        console.log(i);
      }
    }
  }
}

TLDR learning outcomes: this context, function vs block scope

Prev
JavaScript Interview Questions
Next
Array Methods
Loading...

Table of Contents