09Oct
JS Common Questions
JS Common Questions

Whether you are starting to learn JavaScript or looking for a refresher on its principles, here are 15 questions and answers that will help you accomplish that.

1. Are values passed by value or reference?

Short answer: Primitive types are passed by value, while the rest of the objects (Function, Array, Object) are passed by reference. The five primitive types in JS, Number, String, Boolean, null, and undefined are passed by value:

 let foo = 10
 let bar = 'Hello'
 let foobar = null
 
 console.log(foo, bar, foobar); // 10 'Hello' null
 
 let aCopy = foo
 console.log(aCopy) // 10
 foo = 42
 
 console.log(aCopy) // 10

You can assign the value of one variable to another, and the value gets copied. That’s why changing the value of foo does not alter the value of aCopy.

However, other objects values are passed by reference:

 let foo = [1, 2, 3]
 let bar = foo;
 
 console.log(foo, bar); // [1, 2, 3] [1, 2, 3]
 
 foo.push(4)
 
 console.log(bar) // [1, 2, 3, 4]

In this example, bar is assigned to the array foo, which has three numbers. Adding another element to foo‘s array also changes bar values since they are connected by reference.

EDIT: Wording of this answer might create confusion, and I apologize for that. In JS, all values are passed by value. The thing with objects is that, technically, the object’s value passed is actually a reference. That’s why I said that objects’ values are passed by reference.

2. What’s coercion?

Short answer: It’s value conversion from one type to another. Coercion is the word used in JS and in other languages to refer to a conversion of a value from one type to another. We can identify two types of coercion in JS: explicit and implicit. Explicit coercion is when you can look at the code and tell that a value is being converted. In statically typed languages you might know it as type casting. Example:

 Number("15") // 15

Implicit coercion is when the type conversion is done as a side effect; for example, when using loose equals (more of this on the next question):

 !!("15" == 15) // 15 == 15 -> true

There are many nuances with coercion between different object types, but what you must understand is if your code can handle coercion in its logic or not. Sometimes implicit coercion can help you with code readability, if well understood.

3. What’s the difference between == or ===?

Short answer: == allows type conversion (coercion), while === does NOT. Many will say that the difference is that ===, or ‘strict equals’, checks for type AND value, while ==, or ‘loose equals’, just checks for value. That’s right for a quick answer, but not completely accurate; and we want to go deeper.The real difference between strict and loose equality is that strict equality does not allow coercion, while loose equality does. Consider these examples:

console.assert(1 == 1) // true
console.assert('1' == 1) // true
console.assert(1 === 1) // true
console.assert('1' === 1) // false

The first line is true because both values are of the same type and value.

The second line is true because loose equality allows coercion, so '1' is coerced to a number, and the actual comparison is 1 == 1.

The third line is true due to the same reason as the first line. Since values are the same type, no coercion is needed.

The fourth line is false because coercion is not allowed, so a string '1' is not the same as the number 1.

These examples are simple, but things start to get tricky when we are comparing booleans. Take these examples:

console.assert("15" == true)

What do you think the output should be? We can confirm that "15" is a truthy value:

But the actual answer "15" == true is false. Why?

We are not checking if "15" is a truthy value even if it appears like it; we are comparing them. Since we are using loose equality, one of the values will get coerced. In this case, true will get coerced to a number, 1, so the comparison is now "15" == 1.

This triggers another coercion since now we are comparing a string to a number. The final comparison is 15 == 1, which is not true at all.

These are just cases for String and Boolean comparisons. I’m not covering other objects right now, but here are a couple of things to keep and mind and study further, if interested:

    • NaN is never equal to itself
    • +0 and -0 are equal to each other
    • Comparison between objects are equal if they are both references to the same value

4. What’s the difference between undefined and not defined?

Short answer: undefined it’s a JS type that appears when a variable has been declared, but not assigned a value. ‘not defined’ is an error that happens when a variable has not even been declared and you try to use it.

undefined is one of JavaScript’s primitive types. We can say that undefined is the type of a variable which value as not yet been assigned. In other words, if a variable has been declared, but doesn’t have a value, it’s undefined. Consider this:

var a;
console.log(a); // undefined

a has been declared but has no value assigned.

There are a couple of reasons why a variable can be considered undefined, apart from the previous one:

      • When an array item doesn’t have a value:
        var arr = [1,2,3,,5] typeof(arr[3]) // undefined
      • When a variable is assigned a function call, and that function does not have a return statement:
        function sayHello() { 
           console.log('Hello!'); 
        }
        var hello = sayHello();
        console.log(hello) // undefined

On the other hand, ‘not defined’ is an error that appears like this:

Uncaught ReferenceError
Uncaught ReferenceError

This ‘not defined’ error occurs when we are trying to use a variable that is not yet been declared. Using a slight variation of the first example for this question:

a;
console.log(a); // Uncaught ReferenceError: a is not defined

5. What’s hoisting?

Short answer: Is the action, done by the JS Engine, of moving variable and function declarations to the top of the scope automatically.

Before we move on we must remember one important aspect of JS, the host environment compiles the code. In the case of a browser, it compiles the JS code on the fly, so it looks like an interpreted language. Taking that out of the way, let’s illustrate what hoisting is with an example:

 console.log(foo);
 var foo = 2;

We see the code in the order the lines appear, from top to bottom. But it doesn’t mean that it has to run that way. Can you guess what’s the output of the snippet above?

My first impression when I was learning this concept was: ReferenceError. Why? Because we are trying to use the foo variable before it was declared. In reality, the output is undefined!

The issue here is that the JS Engine compiles variables and function declarations differently than they appear in code. For the compiler, var foo = 2; represent two statements rather than one: var foo; foo = 2;.

The code that ends running then is:

var foo;
console.log(foo);
foo = 2;

As we saw on a question before, the variable is declared but doesn’t have a value before the console.log appears. That means the output is undefined.

So the compiler takes care of ‘hoisting’ all variables and function declarations to the top of the scope. Only the declarations are hoisted; that means the assignments remain where you wrote them.

6. What’s scope?

Short answer: Scope is the place where the JS engine looks for variable and function declarations to determine what to do; declare a new variable/function or take the already existent declaration.

You usually need to keep some sort of state in your app or script to achieve your goals; being fully aware of the places where you can access that state and how it is determined means knowing the scope. The most accessible scope is the global scope. Declarations on this scope are available to your script or program at any time. You can access these declarations inside your functions or closures without worrying about their scope availability. It’s also generally considered as a bad practice to have too much global state since it can change without you even noticing. It is; however, a good place to have constant values that are used in different places. All other scopes are defined by the enclosing block. You can have a function scope, in which declarations are only available inside that same function block. The important thing to know is how the JS Engine traverses the scope to find, or not, what it is looking for. Take the following example:

var foo = 5;
 
function scoped() {
  var foo = 10;
  console.log(foo);
}
 
console.log(foo)
scoped();

What’s the output? One may be tempted to think that there will be a conflict between both foo‘s variables, but there will be not since they live in a different scope.

foo = 5 lives in the global scope, while foo = 10 lives in the function’s scope. So the output will be:

5
10

However, with a slight modification to our above script we get the same output, but a different behavior:

var foo = 5;
 
function scoped() {
  foo = 10;
  console.log(foo);
}
 
console.log(foo)
scoped();

Here I removed the variable declaration inside the function, which means that it will be the same variable as the first foo. The output will be the same, but now the global foo variable has a value of 10, and many times updating a global value like this is not what you want. It can become a really hard problem to debug, especially in larger apps.

7. What’s the Prototype chain?

Short answer: It’s a property of all objects that defines some common utilities such as getters and setters. It’s called a chain because objects can be linked and their prototype chain traversed to find the property being requested, much like a scope traversal.

Everything in JS is an Object, and all objects have a property, Object.prototype, that defines certain utilities that the object can do. For example, objects have in their prototype a get operation so it knows how to return a value when you do something like foo.bar.toString() is another utility that can be found on the prototype of objects. Now, why is it important? It’s important because through this prototype is how objects’ properties are resolved. And it’s called a chain because one object can delegate a property resolution to another object to which it is linked. To illustrate:

let foo = {
 age: 25
};
 
let bar = Object.create(foo);
 
bar.age; // 25

In this example, we are using the Object.create call to “create a copy” of the foo object and assign it to bar. I say “create a copy” because internally these two objects are not copies of each other, but they are linked through the prototype chain.

So, bar.age looks into its own properties and can’t find the age property, so it traverses the prototype chain and finds age on the foo object, and that’s the value it returns. We can confirm this by checking if an object has a specific property on its own chain:

let foo = {
 age: 25
};
 
let bar = Object.create(foo);
 
console.log(bar.age); // 25
 
console.log(bar.hasOwnProperty('age')); // false
console.log(foo.hasOwnProperty('age')); // true

This feature allows you to define delegation behavior for your objects, which can greatly improve and simplify how you app interacts and communicates with its own objects.

8. What’s the Event Loop?

Short answer: The event loop is like a storage of things that should run later, and sits between the JS engine and the hosting environment, like the browser.

JS is a synchronous language; don’t believe otherwise. What makes it feel like asynchronous is the event loop. This loop is what manages the interaction of code that should run now and code that should run later. The JS engine can take a call to a web API like setTimeout(...), and push it to the event loop, which later returns to your program through a callback function and runs. What’s interesting is that this event loop also runs things synchronously since it has to wait for the JS stack to be available to handle the callback. This means that a call to a setTimeout function might run after the time you have specified if the program is too busy. Here is a great explanation of the details of this concept, I encourage you to take a look!

9. What’s a closure?

Short answer: A closure is a function that remembers the parent scope where it was defined, even if that parent scope has already ran. You are probably already using closures and you have not even noticed. Consider this example:

function sum() {
  let count = 1;

  let increase = function(){ 
    count++;
    console.log(count)
  }

  return increase;
}


let adder = sum();
adder(); // 2
adder(); // 3

The variable adder is a closure! Why? Because it can work with its parent scope, the count variable, even though the sum function has already been executed when we declared the adder variable.

This can help you to, among other things:

  • Keep variables private: There is no way to access the count variable since only the closure has references to it
  • Share state: You could create a new variable and assign it the function call sum() again, and it will have another count variable, different from the first one

10. What does 'use strict' do?

Short answer: Typing em>'use strict' at the top of a JS script will report common JS side effects as errors, effectively forcing us to rewrite the code in question as more correct or specific. Writing 'use strict' at the very top of a JS file will enable strict mode. This mode forces you to write “more secure” JS code because it will throw errors instead of working with side effects. Let me illustrate:

name = "David";

we are assigning a value to name even when we never declared the variable. This behavior relies on the fact that a global name variable was created for us and allowed us to continue. However, in strict mode, this would fail since we are using an undeclared variable:

"use strict";
name = "David"; // ReferenceError

There are many other mistakes that strict mode converts to errors, so you can feel more confident with your code. Here is a more comprehensive list of those.

Also, strict mode enables warning messages on some IDEs, such that a loose equals will be marked as not safe and suggest to switch to use strict equals.

11. How to correctly clone an object?

Short answer: Not really a short answer for this one. Use a function that shallows copy properties that are not objects themselves, then iterate over the ones that are objects and repeat. Specific options are below.

We can’t simply use = to copy or clone an object since, if you remember from a previous question, objects are passed by reference. That means that doing objectA = objectB will actually reference the same memory address.I’m going to show two ways to clone objects, one is for shallow copy, and one is for deep copy.A shallow copy is a copy of the most upper-level properties of an object, that is, no nested objects are copied. You can do it like this:

let foo = { bar: 'baz', life: 42, today: new Date() };
let fooClone = Object.assign({}, foo);
console.log(foo !== fooClone);         // true
console.log(foo.bar === fooClone.bar); // true
console.log(foo);      // {bar: "baz", life: 42, today: Thu Oct 03 2019 19:21:39 GMT-0500 (Central Daylight Time)}
console.log(fooClone); // {bar: "baz", life: 42, today: Thu Oct 03 2019 19:21:39 GMT-0500 (Central Daylight Time)}

The Object.assign solution is great if all you need is to clone objects with basic properties like strings, numbers or dates. You can try this same technique with nested objects and see how it will not work:

let foo = { bar: 'baz', life: 42, today: new Date(), complex: { nested: 'OMG' } };
let fooClone = Object.assign({}, foo);
console.log(foo !== fooClone);         // true
console.log(foo.bar === fooClone.bar); // true
console.log(foo.complex !== fooClone.complex) // false
console.log(foo);      // {bar: "baz", life: 42, today: Thu Oct 03 2019 19:26:58 GMT-0500 (Central Daylight Time), complex: {…}}
console.log(fooClone); // {bar: "baz", life: 42, today: Thu Oct 03 2019 19:26:58 GMT-0500 (Central Daylight Time), complex: {…}}

fooClone.complex holds the same reference as foo.complex, and that’s not a complete clone.

To achieve a deep copy of an object we can use this utility function:

function deepClone(obj) {
  let clone = Object.assign({}, obj);
  Object.keys(clone).forEach(key => 
		(clone[key] = typeof obj[key] === 'object' ? 
			deepClone(obj[key]) : 
			obj[key])
  );
	if(Array.isArray(obj)) {
		clone.length = obj.length;
		return Array.from(clone);
	} else {
		return clone;
	}
};
let foo = { foo: 'bar', complex: { a: 1, b: [2, 3, 4] } };
let fooClone = deepClone(foo);
console.log(foo !== fooClone);                 // true
console.log(foo.complex !== fooClone.complex); // true
console.log(foo.complex.b.length === fooClone.complex.b.length) // true
console.log(foo);      // {foo: "bar", complex: {…}}
console.log(fooClone); // {foo: "bar", complex: {…}}

Here things get more tricky. First thing we do is:

  let clone = Object.assign({}, obj);

and this allows us to have a shallow copy to start off. Next thing, we need to iterate over each of the original object keys and check if it is an object; if it is, we recursively clone that object:

Object.keys(clone).forEach(key => 
	(clone[key] = typeof obj[key] === 'object' ? 
		deepClone(obj[key]) : 
		obj[key])
  );

Lastly, we check if the original property is an array, and if so, return a new array with the exact same length:

if(Array.isArray(obj)) {
	clone.length = obj.length;
	return Array.from(clone);
} else {
	return clone;
}

This way you end with a deep copy of an object.

If you already looked into this question before, you probably found some solutions using JSON.parse and JSON.stringify. I’m not covering those since even though they can do deep copy of objects, they mess up with Date objects.

Here is another way as described by the MDN docs.

A little more into JS on the browser

12. What’s Event Delegation?

Short answer: Event delegation is a pattern used to avoid attaching event listeners to similar elements, which can grow in quantity, and to do so on the parent element instead in order to increase performance.

When you are developing a webpage you usually want to attach some behavior to elements, like button clicks or list item drags. To do so, using a framework or not, you end up adding event listeners to your HTML elements.For example, you can have a shopping list that, when an item is clicked, would allow you to edit its contents:

<ul>
	<li>Bread</li>
	<li>Eggs</li>
	<li>Watermelon</li>
	<li>Onion</li>
</ul>

To achieve that, you would normally attach event listeners to each list item. That’s okay when you are sure that the list will not grow much. If it does, you will start to have performance issues due to so many listeners attached to so many elements.

When you don’t know the size of the list, or the list is likely to grow a lot, you can use event delegation.

In this example, the event delegation pattern allows you to attach a single event listener to the list itself, instead of doing so on each of the items. Then, when a list item is clicked, you can delegate control to the parent, the list, and avoid having a lot of event listeners. Knowing this:

<ul id="shopping-list">
	<li>Bread</li>
	<li>Eggs</li>
	<li>Watermelon</li>
	<li>Onion</li>
</ul>

document.getElementById('shopping-list').addEventListener('click', function(event) {
	let listItem = event.target;
	// Execute edit logic on listItem, which is the actual item clicked
}); 

event.target is the actual list item clicked, but the click behavior is managed by the parent listener. Now, only this single listener can handle edits on any of its children.

13. What’s Debouncing?

Short answer: Debouncing is a way to group multiple identical events into only one once the trigger has stopped firing to avoid doing excessive processing.Here is a great example: a search bar.

<input type="search" id="search-bar" />

document.getElementById('search-bar').addEventListener('input', function(event) {
	executeSearch();
});

With this code, every input event would fire the executeSearch function, even if the user has not even stopped typing its term. A debounce technique will help ease that load and allow the executeSearch function to only be called after the user has stopped typing.

Here is how a debounced search looks:

var timer;

function debounce(callback, delay) {
	clearTimeout(timer);

	timer = setTimeout(callback, delay);
}

document.getElementById('search-bar').addEventListener('input', function(event) {
	debounce(executeSearch, 300);
});

We use a combination of setTimeout and clearTimeout to create and remove the timer that controls when the executeSearch function will run. This way, if the user has not stopped typing for 300 milliseconds, the search will wait. Once the timer has passed, the search will happen as usual.

14. What’s throttling?

Short answer: Throttling in JS is similar to debouncing. It’s a way to run multiple identical events at regular intervals rather than allowing them to run when triggered.

As opposed to debouncing, throttling does not need to wait until an event has stopped triggering to continue. Instead, it waits for a specific time to run, even if more events are occurring. Let’s take a scroll event as an example:

document.addEventListener('scroll', function(event) {
	processScroll();
});

Scrolling with this listener set will cause a lot of events to be triggered. If whatever you do on the processScroll function is demanding, it will cause performance issues on your page. We can use throttling to only process the scroll event at regular periods of time:

var timer;

function throttle(callback, delay) {
	if(timer) {
		return;
	}

	timer = setTimeout(function() {
		callback();
		timer = undefined;
	}, delay);
};

document.addEventListener('scroll', function(event) {
	throttle(processScroll, 500);
});

Now, our processScroll function will run at most once each 500 milliseconds and not on every scroll event.

15. What’s a Polyfill?

Short answer: A polyfill is a browser fallback, written in JS, that allows latest features to run on older/outdated browsers.

Most developers don’t want to fall behind on the newest technologies and features- curiosity catches with us. JS is constantly evolving, adding new features, syntaxes and improvements. However, those additions are not automatically available on browsers; vendors need to implement and ship them first. Those are some of the benefits you get when you update your browser to the latest version. When you want to use a JS feature that is not yet supported on many browsers, and you are aware that not all your users will have the latest version at all times, you need a polyfill. This will allow you to have that feature, even if the browser does not natively support it yet. In other words, a polyfill is a JS snippet that fills a “hole” of functionality in the browser. Modernizr has a great collection of polyfills, you should definitely need to check them out if you plan to implement them. Here is a list of supported features and browsers. Also, Babel is a popular option for compiling your modern JS into old-browser-supported JS.

I hope these questions will help you to gain a deeper understanding of the language, and to use it to your advantage!

Leave a Reply