DEV Community

Cover image for Typical JavaScript interview exercises (explained)
Maxence Poutord
Maxence Poutord

Posted on

Typical JavaScript interview exercises (explained)

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' })
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode
  • 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)
}
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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)()
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
})
Enter fullscreen mode Exit fullscreen mode

Do you have any idea? 🙂

Top comments (20)

Collapse
 
dwmiller profile image
David Miller • Edited
const newHeroes = heroes.map(h => {
  return {
    ...h,
    name: h.name.toUpperCase()
  }
})
Enter fullscreen mode Exit fullscreen mode
Collapse
 
prmichaelsen profile image
Patrick Michaelsen

cool but proofread your articles :P

Collapse
 
iskndrvbksh profile image
Baba • Edited
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.

Collapse
 
gdotdesign profile image
Szikszai Gusztáv • Edited

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?

Collapse
 
maxart2501 profile image
Massimo Artizzu

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 🙂

Collapse
 
richseviora_12 profile image
Rich Seviora • Edited

To be very thorough:

Object.freeze(heroes);
const newHeroes = heroes.map(h => {
  Object.freeze(h);
  return { ...h, name: h.name.toUpperCase() }
})
Collapse
 
dwmiller profile image
David Miller

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

Collapse
 
richseviora profile image
Rich Seviora

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.

Collapse
 
jeffreyducharme profile image
JDucharme

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

Collapse
 
dhammapurnima profile image
Purnima

A solution to the last question:

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

})
Collapse
 
nick_leake profile image
Nick Leake

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

Collapse
 
worsnupd profile image
Daniel Worsnup • Edited
const newHeroes = heroes.map(h => {
    return Object.assign({}, h, { name: h.name.toUpperCase() });
});
Collapse
 
hovik97 profile image
Hovik97

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