loading...
Cover image for Typical JavaScript interview exercises (explained)

Typical JavaScript interview exercises (explained)

maxpou profile image Maxence Poutord ・5 min read

Few weeks ago I found on my twitter feed a very interesting blog post: "The Best Frontend JavaScript Interview Questions (written by a Frontend Engineer)" written by Boris Cherny.

As you may guess, the author show some interesting questions to ask during a job interview. Questions are splitted in 4 parts: Concepts, Coding, Debugging, and System Design. Here, I'll focus on the Debugging part.

I really like theses question, because they deal with the specificities of JavaScript: object comparison, event loop, scope, this, prototypal inheritance and the equal operator combined with Abstract Equality Comparison Algorithm.

Before reading the solution, I'd recommend you to find the answer by yourself.

Exercise 1

I want this code to log out "hey amy", but it logs out "hey arnold" - why?

function greet (person) {
  if (person == { name: 'amy' }) {
    return 'hey amy'
  } else {
    return 'hey arnold'
  }
}
greet({ name: 'amy' })

Answer

Here the problem is the following: { name: 'amy' } != { name: 'amy' }. When comparing two object with equality or strict equality, JavaScript gonna compare the related internal references. Here, these two objects have the same properties and the same value. But in memory, this is 2 different objects.

A solution here could be:

function greet (person) {
  if (person.name === 'amy') {
    return 'hey amy'
  }
  return 'hey arnold'
}
greet({ name: 'amy' }) // "hey amy"

Exercise 2

I want this code to log out the numbers 0, 1, 2, 3 in that order, but it doesn’t do what I expect (this is a bug you run into once in a while, and some people love to ask about it in interviews).

for (var i = 0; i < 4; i++) {
  setTimeout(() => console.log(i), 0)
}

Problem

I like this one because it's a bit trickier and it deal with scope and the JavaScript Event Loop.

The classic pitfall here is the Zero delays. setTimeout(callback, 0) doesn't mean that the callback will be fire after zero milliseconds.

Here's what happen on the event loop side:

  1. Current Call Stack is set to the first setTimeout().
  2. windows.setTimeout() is considered as a Web APIs (for better Non-Blocking I/O). So the call stack send this part of code to correct Web APIs. After 0 milliseconds, the callback (here an anonymous function) would be send to the Queue (not to the call stack).
  3. As the call stack is free, for-loop can continue to the second setTimeout ...(repeat after we meet this condition i < 4)...
  4. Now the loop is over and i === 4. JS can now execute the callback queue one by one. Each console.log(i) will print the 4.

Do you feel lost? I hope this animation will better helps you!

Animation made with Loupe (try it it's fun!)

The second problem is related to scope. The 4 instance of setTimeout function share the same instance of i.

var foo = 'bim'
//                                â–¼ this is a reference to variable foo, not his associated value ('bim') 
var getFoo = function () { return foo }
foo = 'boum'

getFoo() // 'boum'

Answer

So, there are several solutions available:

  • use an Immediately-Invoked Function Expression a.k.a. IIFE. The 'wrapper function' will run as soon as she's defined.
for (let i = 0; i < 4; i++) {
  (function (i) {
    setTimeout(() => console.log(i), 0)
  })(i)
}
  • switch to let keyword (instead of var). This (new?) keyword makes scope a bit more easy to understand.
for (let i = 0; i < 4; i++) {
  setTimeout(() => console.log(i), 0)
}

Exercise 3

I want this code to log out "doggo", but it logs out undefined!

let dog = {
  name: 'doggo',
  sayName () {
    console.log(this.name)
  }
}
let sayName = dog.sayName
sayName()

Answer

The previous code return undefined. Why? Looks, on the first let condition, we define an object with 2 attributes (name and the function sayName()). Then on the second let, we copy the attribute sayName, which is a function into another variable. And then, we call this variable out of her context (in the global one). The function sayName() will return window.name (global if the environment is Node). And typeof window.name === "undefined".

  • 👎 (the dirty one). If we want to keep the sayName variable. Then we need to bind the dog the context on it:
sayName.bind(dog)()
// or:
dog.sayName.bind(dog)()

This is dirty, right? ðŸ¤

  • 👍 call the function directly on her original context
let dog = {
  name: 'doggo',
  sayName () {
    console.log(this.name)
  }
}
dog.sayName() // will log "doggo"

Exercise 4

I want my dog to bark(), but instead I get an error. Why?

function Dog (name) {
  this.name = name
}
Dog.bark = function () {
  console.log(this.name + ' says woof')
}
let fido = new Dog('fido')
fido.bark()

Answer

We got the following error TypeError: fido.bark is not a function. On the previous code, we set the bark function on an another function (Dog()), which is also a constructor. Is it possible because in JavaScript, functions are objects.

2 solutions:

  • 👎 (the dirty one). fido.bark isn't a function but Dog.bark is. So let's use this one and solve the this issue with a function.prototype.bind() as in the exercise above:
var boundedBark = Dog.bark.bind(fido)
boundedBark() // "fido says woof"

But from my point of view using function.prototype.bind() (almost always) lead to confusion.

  • 👍 set bark() on the Dog's prototype
function Dog (name) {
  this.name = name
}

Dog.prototype.bark = function () {
  console.log(this.name + ' says woof')
}

let fido = new Dog('fido')
fido.bark() // "fido says woof"

We can also use the class keyword (ES2015) which is just a syntactic sugar to the previous code.

class Dog {
  constructor (name) {
    this.name = name
  }

  bark () {
    console.log(this.name + ' says woof')
  }
}

let fido = new Dog('fido')
fido.bark() // "fido says woof"

Exercise 5

Why does this code return the results that it does?

function isBig (thing) {
  if (thing == 0 || thing == 1 || thing == 2) {
    return false
  }
  return true
}
isBig(1)    // false
isBig([2])  // false
isBig([3])  // true

Answer

We are using here the simple equality operator (e.g. ==) by opposition to strict comparison (e.g. ===). With this operator, it's not mandatory to compare the same type.

  • isBig(1) pass the condition thing == 1 as expected.
  • isBig([2]) will pass the condition thing == 2. When comparing an array to a number, the array will be converted to a number. This is a part of the Abstract Equality Comparison Algorithm. According to this algorithm, if we compare a number with an Object (reminder: arrays are object in JS), this array will be converted to an array. Here, there is only one item inside so [2] == 2.

Because this algorithm is obscure for the most common developers, we should aboid this operator (ESLint eqeqeq rule is your friend 👍).

// weird results
[] == ![]     // true
[] == false   // true

// Non transitive relation
"1" == true   // true
"01" == true  // true
"01" == "1"   // false

Exercise 6 (bonus)

How to preserve the immutability on my heroes list?

const heroes = [
  { name: 'Wolverine',      family: 'Marvel',    isEvil: false },
  { name: 'Deadpool',       family: 'Marvel',    isEvil: false },
  { name: 'Magneto',        family: 'Marvel',    isEvil: true  },
  { name: 'Charles Xavier', family: 'Marvel',    isEvil: false },
  { name: 'Batman',         family: 'DC Comics', isEvil: false },
  { name: 'Harley Quinn',   family: 'DC Comics', isEvil: true  },
  { name: 'Legolas',        family: 'Tolkien',   isEvil: false },
  { name: 'Gandalf',        family: 'Tolkien',   isEvil: false },
  { name: 'Saruman',        family: 'Tolkien',   isEvil: true  }
]

const newHeroes = heroes.map(h => {
  h.name = h.name.toUpperCase()
  return h
})

Do you have any idea? 🙂

Posted on by:

maxpou profile

Maxence Poutord

@maxpou

🎒 Digital nomad • 👨🏼‍💻 Software engineer • 🗣 Public speaker • 🌏 Remote worker

Discussion

markdown guide
 
const newHeroes = heroes.map(h => {
  return {
    ...h,
    name: h.name.toUpperCase()
  }
})
 
const heroes = [
  { name: 'Wolverine',      family: 'Marvel',    isEvil: false },
  { name: 'Deadpool',       family: 'Marvel',    isEvil: false },
  { name: 'Magneto',        family: 'Marvel',    isEvil: true  },
  { name: 'Charles Xavier', family: 'Marvel',    isEvil: false },
  { name: 'Batman',         family: 'DC Comics', isEvil: false },
  { name: 'Harley Quinn',   family: 'DC Comics', isEvil: true  },
  { name: 'Legolas',        family: 'Tolkien',   isEvil: false },
  { name: 'Gandalf',        family: 'Tolkien',   isEvil: false },
  { name: 'Saruman',        family: 'Tolkien',   isEvil: true  }
];

function copyObject(obj, cb) {
  let callbackPassed = false,
      stringToObj = null,
      objToString = null;

  if (cb && typeof cb !== "function") throw new Error("Second Argument must be function.");
  else callbackPassed = true;

  objToString = JSON.stringify(obj);
  stringToObj = JSON.parse(objToString);

  if (callbackPassed) cb(stringToObj);

  return stringToObj;
}


const newHeroes = heroes.map(h => {
  return copyObject(h, function (obj) {
    obj.name = obj.name.toUpperCase();
  });
});
// you can copy Objects with JSON :)
// Object.assign is better solution for this kind of problems.

 
 

I've been writing for about a decade now, but didn't know about that it is possible to bind functions to objects like this:

let dog = {
   sayName () {
    console.log(this.name)
  }
}

Is this a relatively new thing or it has always worked like this? Are there any reference material on this?

 

This is dirty, right? 🤠

Maybe, but explain why. The second version has been used ad nauseam by React developers who had to bind the component's prototype methods to the component's instance in order to use them nicely in their JSX templates.

The real ugliness here is that object methods lose their context so easily - but it's also one of the best part of JavaScript, since this makes functions as first class objects.

Which means I can do something like this:

class Cat() {
  meow() { ... }
}
const mittens = new Cat('Mittens');
mittens.bark = fido.bark;
mittens.bark(); // 'Mittens says woof'

You already got good answers for your final question 🙂

 

A solution to the last question:

const newHeroes = heroes.map(h => {
  return  Object.assign({}, h, {name: h.name.toUpperCase()})

})
 

To be very thorough:

Object.freeze(heroes);
const newHeroes = heroes.map(h => {
  Object.freeze(h);
  return { ...h, name: h.name.toUpperCase() }
})
 

This approach is the only one that seems to work for me.

 

Why would you need to freeze it? This stuff is synchronous, how would it change in the middle of your operation?

 

It's to guard against future changes. But the requirement was to "preserve immutability" and so this makes the original data set immutable going forward.

 
const newHeroes = heroes.map(h => {
          return h.name.toUpperCase();
        });
 

Very nice explanation for each scenario. Thanks for the write-up!

 
const newHeroes = heroes.map(h => {
    return Object.assign({}, h, { name: h.name.toUpperCase() });
});
 
 

deep copy this my approach!!!!
adding this line:
const hr=JSON.parse(JSON.stringify(heroes));
It's work fine

 

Very good but very depressing.

I've been at this for a very long time now and... it's time to give up.

 

const newHeroes = JSON.parse(JSON.stringify(heroes)).map(h => {
h.name = h.name.toUpperCase()
return h
})

 

I know this was posted a while ago but here is my answer.

heroes.forEach(hero => Object.freeze(hero));

 

const newHeroes = heroes.map(h => {
const newHero = {...h}
newHero.name = newHero.name.toUpperCase()
return newHero
})

 

Object.freeze({name: '',family:'', isEvil:''})