Chapter One
String coercion
What is the value of foo?
Answer
The string '1020'
Explantation:Type coercion means that when the operands of an operator are different types, one of them will be converted to an “equivalent” value of the other operand’s type. For instance, if you do:
the boolean operand will be converted to an integer: false becomes 0, true becomes 1. Then the two values are compared.
However, if you use the non-converting comparison operator ===, no such conversion occurs. When the operands are of different types, this operator returns false, and only compares the values when they’re of the same type.
Floating point precision
What will be the output of the code below?
Answer
False
Explanation: Floating-point numbers (like 0.1 and 0.2) cannot always be represented with perfect accuracy in a binary system. When 0.1 and 0.2 are added together in JavaScript, the result is not exactly 0.3, but slightly more than that (approximately 0.30000000000000004). Therefore, when this sum is compared to 0.3 using the == or === operator, the result is false because the numbers are not exactly equal due to this small precision error.
Currying
How would you make this work?
Answer
Explanation: you can create a function that checks the number of arguments provided and behaves accordingly. This pattern is often referred to as “currying” or partially applying a function.
String Manipulation
What value is returned from the following statement?
Answer
'goh angasal a m\'i'
Explanation:This line of JavaScript code takes the string “i’m a lasagna hog”, splits it into an array of characters, reverses that array, and then joins the characters back into a string. The split("")
method splits the string into an array of individual characters. reverse()
reverses the order of the array’s elements. Finally, join("")
merges the elements of the array back into a single string, resulting in the reversed version of the original string.
Logical Assignment
What is the value of window.foo?
Answer
"bar"
Explanation:This expression uses the logical OR operator to assign a value to window.foo
if it does not already have one. If window.foo
is undefined, falsy, or not declared, it will be set to “bar”. After this operation, window.foo
will definitely contain “bar”.
Scope and Closures
What is the outcome of the two alerts below?
Answer
The first alert shows "Hello World", the second results in an error.
Explanation:The first function is an immediately invoked function expression (IIFE) that has its own scope. Inside this function, bar
is declared and concatenated with foo
which is accessible due to its higher (global) scope, resulting in the alert “Hello World”. The second alert(foo + bar)
tries to access bar
, which is not defined in the global scope (it’s only defined within the IIFE), hence it will throw a reference error saying that bar
is not defined.
Array Length
What is the value of foo.length?
Answer
2
Explanation:The variable foo
is initialized as an empty array. The push
method is used to add elements to the array. foo.push(1)
adds the number 1 to the array, and foo.push(2)
adds the number 2. After these operations, the array foo
contains two elements, thus foo.length
returns 2.
Object Reference and Assignment
What is the value of foo.x?
Answer
The value of foo.x
is undefined
.
This type of expression is evaluated from left to right. Initially, foo
references an object {n: 1}
. When foo.x = foo = {n: 2};
is executed, the original object referenced by foo
(now also referenced by bar
) gets a new property x
assigned to it. Immediately afterward, foo
is reassigned to a new object {n: 2}
, which does not have the x
property. Thus, foo.x
is undefined
, but bar.x
would be {n: 2}
.
Event Loop Order
What does the following code print?
Answer
The output is:
Explanation: The code demonstrates JavaScript's event loop and task queues. `console.log('one');` and `console.log('four');` are executed immediately as part of the synchronous code. Although `setTimeout` has a delay of 0 ms, it places its callback (to log 'two') into the Web APIs and it is moved to the task queue after the current script completes. The `Promise.resolve()` places its callback (to log 'three') into the microtask queue, which is processed immediately after the current script but before any tasks from the task queue (like our setTimeout callback).Promise Execution Differences
What is the difference between these four promises?
Answer
The differences are related to how and when `doSomethingElse` is executed and whether its result is passed down the promise chain:
- The first example returns the result of `doSomethingElse()`, chaining the promises correctly. Whatever `doSomethingElse()` returns (a value or promise) will be awaited before resolving the outer promise.
- The second example calls `doSomethingElse()` without returning its result, so the outer promise resolves independently of the completion of `doSomethingElse()`.
- The third example is incorrect because `doSomethingElse()` is executed immediately when the promise chain is defined, not after `doSomething()` resolves.
- The fourth example correctly chains the promises by passing `doSomethingElse` as a function reference to be called after `doSomething()` resolves.
Scope and Variable Declaration
What will the code below output to the console and why?
Answer
The output is:
Explanation: Inside the immediately-invoked function expression (IIFE), `a` is declared with `var`, making it local to the function scope and undefined outside of it. However, `b` is assigned without being declared with `var`, `let`, or `const`, implicitly creating a global variable. Thus, outsideMake this work
Task: Make this work:
Answer
To make this work, you can create a function duplicate
that concatenates the array with itself using the concat
method.
FizzBuzz
Task: Create a for loop that iterates up to 100 while outputting “fizz” at multiples of 3, “buzz” at multiples of 5 and “fizzbuzz” at multiples of 3 and 5
Answer
You can achieve this with the following for loop:
Logical Operators
Task: What will be returned by each of these?
Answer
-
console.log("hello" || "world")
returns"hello"
.Explanation: The
||
(logical OR) operator returns the first truthy value it encounters. Since"hello"
is a truthy value, it is returned. -
console.log("foo" && "bar")
returns"bar"
.Explanation: The
&&
(logical AND) operator returns the first falsy value it encounters, or the last value if all are truthy. Both"foo"
and"bar"
are truthy, so the last value,"bar"
, is returned.
Immediately Invoked Function Expression (IIFE)
Task: Write an immediately invoked function expression (IIFE)
Answer
An immediately invoked function expression (IIFE) can be written as follows:
This will log “This is an IIFE” immediately upon execution.
Function Returns
Question: Consider the two functions below. Will they both return the same thing? Why or why not?
Answer
The two functions will not return the same thing.
Explanation:
In foo1
, the object { bar: "hello" }
is returned correctly.
In foo2
, due to the placement of the newline after return
, JavaScript’s automatic semicolon insertion inserts a semicolon immediately after return
. This means foo2
effectively returns undefined
.
To correct foo2
so it returns the same object as foo1
, the opening curly brace should be on the same line as return
.
Refer to learn about quality comparators, strict and abstract equality
prediction 0
what will be the output of this code?
Answer
Understanding the execution order of tasks and microtasks in JavaScript is crucial for handling asynchronous operations. Here’s an explanation with examples.
Event Loop, Task Queue, and Microtask Queue
In JavaScript, the event loop is responsible for handling asynchronous operations. It manages two main queues: the Task Queue (or Macro Task Queue) and the Microtask Queue.
- Task Queue (Macro Task Queue): This queue handles tasks such as
setTimeout
,setInterval
, and other asynchronous tasks. These are processed after the current execution stack is empty. - Microtask Queue: This queue handles tasks such as
Promise
callbacks andqueueMicrotask
. These are processed immediately after the current execution stack is empty but before any tasks from the Task Queue.
Example 1
Execution Explanation:
-
Synchronous Code:
console.log('Start');
is executed immediately.setTimeout(() => { console.log('setTimeout'); }, 0);
is scheduled in the Task Queue.Promise.resolve().then(() => { console.log('Promise'); });
is scheduled in the Microtask Queue.console.log('End');
is executed immediately.
-
Microtasks Execution:
- Microtasks in the Microtask Queue are processed next. So,
console.log('Promise');
is executed.
- Microtasks in the Microtask Queue are processed next. So,
-
Tasks Execution:
- Finally, tasks in the Task Queue are processed. So,
console.log('setTimeout');
is executed.
- Finally, tasks in the Task Queue are processed. So,
Output Order:
Execution Explanation:
-
Synchronous Code:
Promise.resolve().then(() => console.log(1));
is scheduled in the Microtask Queue.setTimeout(() => console.log(2), 10);
is scheduled in the Task Queue.queueMicrotask(() => { console.log(3); queueMicrotask(() => console.log(4)); });
is scheduled in the Microtask Queue.console.log(5);
is executed immediately.
-
Microtasks Execution:
- Microtasks in the Microtask Queue are processed next.
- First,
console.log(1);
is executed. - Next,
console.log(3);
is executed and a new microtaskqueueMicrotask(() => console.log(4));
is added to the Microtask Queue. - Then,
console.log(4);
is executed.
- First,
- Microtasks in the Microtask Queue are processed next.
-
Tasks Execution:
- Finally, tasks in the Task Queue are processed after the specified delay. So,
console.log(2);
is executed after approximately 10ms.
- Finally, tasks in the Task Queue are processed after the specified delay. So,
Output Order:
- Synchronous code is executed first.
- Microtasks (e.g.,
Promise
callbacks,queueMicrotask
) are executed after the current execution stack is empty but before any tasks from the Task Queue. - Tasks (e.g.,
setTimeout
,setInterval
) are executed after all microtasks are processed and the current execution stack is empty.
Understanding this order is essential for debugging asynchronous JavaScript code and ensuring predictable behavior in your applications.
prediction 0.5
What will get logged?
Answer
prediction 1
Do you know what will be the output of this code?
Answer
This will actually result in 5
but this is pretty strange, isn’t it?
Please note, that this example will only work in lax mode, and not in strict mode, and we will come to the explanation soon enough.
In earlier versions of JavaScript, a leading 0 on a number marked an octal number.
Octal numbers are numbers with a base of 8 (instead 10 like with decimal numbers).
This means that numbers from 0 to 7 are valid digits.
In our case, 018
can’t actually be a octal number, can it?
Well it can’t and the runtime knows this, which is why it treats 018
as 18
although the right side contains a perfectly valid octal number 015
which is 13
in decimal.
And the runtime will perform the calculation exactly like this, which results in:
prediction 2
Do you know what will be the output of this code?
Answer
The result is "122"
, but why is that?
The plus operator is defined for numbers and for strings and the expression is evaluated from left to right.
The interesting thing about the algorithm behind it is, that it checks whether a string is present.
If we take a look at how the runtime handles the expression, it will start with the first part as follows:
And as the right side contains a string, a concatenation will be made, which results in n intermediary result of
Only after that, the last part is evaluated as follows:
prediction 3
Do you know what will be the output of this code?
Answer
The answer is true.
In this situation, the abstract equality comparison algorithm is used, which is also called the type-coercing equality check. What it does is converting the values as long as possible, until they match in type and can be compared strictly.
In this case, the following steps are performed:
prediction 4
Do you know what will be the output of this code?
Answer
This returns 33
, but why is that?
JavaScript is a dynamically typed language, which means that all standard library functionality must, at some point, decide how to work for most, if not all, use-cases.
Always keep in mind that the following array is valid:
Array.prototype.sort now needs to make a decision on how to handle such scenarios, and the solution is pretty straight-forward:
Convert all values to their string representation (because every value in JavaScript can always be converted to a string!), and then sort them in lexicographic order.
Which basically makes sort see the array as this:
And in lexicographic order, no matter how many characters a string has, comparison starts at position 0, and “3” comes before “8”.
And in the end, the result is this:
prediction 5
Do you know what will be the output of this code?
Answer
This actually returns “string”.
This expression is evaluated from right to left.
The first sub-expression evaluated actually is typeof 1 which will return “number”.
Only after that the next sub-expression is evaluated which now is typeof “number” which returns “string”.
prediction 6
Do you know what will be the output of this code?
Answer
typeof NaN actually returns “number”, but why is that?
You can actually thank the IEEE Standard for Floating-Point Arithmetic (IEEE 754), which is the specification most programming languages base their implementation of floating point numbers on.
Imagine it this way:
Let’s use TypeScript here as an example to bring statical typing to JavaScript and imagine the following scenario:
const result: number = parseFloat(inputArgument);
The code is pretty straight-forward. We want a number, and we have to parse an input argument for that.
But what if parseFloat actually cannot parse a float?
What if we forgot to validate the input and are now passing ‘Does this work?’ to parseFloat?
And this is where NaN comes into the game.
NaN simply states: “This is not a number”, but in a type-safe way, so you always get a result of type number .
prediction 7
Do you know what will be the output of this code?
Answer
Let’s break this up: The comparison performed is the abstract comparison operation, which uses type coercion under the hood, when necessary.
As the first parameter is a boolean, the following rule applies: “If Type(x) is Boolean, return the result of the comparison ToNumber(x) == y.” And in the next round, the next rule applies, “If Type(y) is Boolean, return the result of the comparison x == ToNumber(y).”
Which then boils down to: Number(true) == Number([]) => 1 == 0
Here, two booleans are present. The right side gets converted through the unary negation, and then negated. An empty array is truthy, converting and negating it yields false. true == false
This now evaluates to: false + false which is coered to: Number(false) + Number(false) => 0 + 0
prediction 8
Do you know what will be the output of this code?
Answer
The answer is “banana”.
What actually happens here is the following:
The plus operator is defined for numbers and strings and as soon as a string is present on either the left or right side, a string concatenation is perfomed.
If we follow the execution path, this is what happens:
There are two plus operators in this expression. But one of those is actually a prefix-operator, and not a classical plus. What it does is converting the right-side argument to a number, but converting ‘a’ to a number will actually yield NaN! Let’s continue with following the execution path:
prediction 9
Do you know what will be the output of this code?
Answer
The answer is undefined.
This quiz starts with an array of length 3, containing 3 numbers.
By not using Array.prototype.push and then assigning a value to a position in the array, that is out of the bounds of the original array, the array is extended automatically by the runtime while also increasing the length property.
But this leads to the interesting behavior, that a lot of holes are created within the array.
After the expression array[6] = 9; the array looks as follows:
And this is why, when accessing array[5] undefined is returned.
prediction 10
Do you know what will be the output of this code?
Answer
What String.raw actually does is taking a template literal, processing all substitutions (${variable}, e.g.), but ignoring well-known escape-sequences.
The following will be printed above: HelloTwitter\nworld
\n is a well-known escape-sequence, like \t is, e.g.
Those will simply be ignored and put into the resulting string, as they are.
Here an example of how String.raw works when you also include substitutions:
const varOne = “Hey”;
const varTwo = “there”;
const str = String.raw${varOne}\t\n${varTwo}
// => “Hey\t\nthere”
prediction 11
What will be output of that code?
Answer
The output will be a ReferenceError
because the function fun
tries to access the variable name
before it’s declared. In JavaScript, variable declarations using let
and const
are not hoisted to the top of their enclosing block. Therefore, when fun
is called, name
is still in the temporal dead zone.
-
Function Hoisting:
- Function declarations are fully hoisted. This means the entire function is moved to the top of its scope, allowing the function to be called before its declaration in the code.
-
Variable Hoisting:
- Variables declared with
let
andconst
are hoisted to the top of their block, but not initialized. This means they exist in a “temporal dead zone” from the start of the block until the declaration is encountered. Accessing them in this zone results in aReferenceError
.
- Variables declared with
prediction 12
What will be the output of this program?
Input:
Answer
Output:
For the first loop using let
, the output will be 0 1 2 3 4
, as let
creates a new scope for each iteration. For the second loop using var
, the output will be 5 5 5 5 5
, as var
does not create a new scope for each iteration and the value of i
is shared.
prediction 13
What will be the output of this program?
Input:
Answer
Output:
Code Analysis
-
Global Scope:
var num = 4;
declares and initializes a global variablenum
with the value4
.
-
outer
Function:- Inside
outer
, a new local variablenum
is declared and initialized with the value2
. - The
inner
function is defined withinouter
. It has its own scope.
- Inside
-
inner
Function:- When
inner
is called, the first statement isnum++
.- However, due to JavaScript’s variable hoisting, the local variable
num
withininner
is hoisted to the top of the function scope but remains uninitialized (i.e.,undefined
) until the point of its initialization (var num = 3
). - Thus, the
num++
operation increments theundefined
value, which results inNaN
(Not a Number).
- However, due to JavaScript’s variable hoisting, the local variable
- Next,
var num = 3;
initializes the local variablenum
withininner
to3
. console.log("num", num);
logs the value of this localnum
, which is3
.
- When
-
Execution:
outer();
is called, which in turn callsinner();
.- The
inner
function logs the value of its localnum
after it has been incremented and initialized to3
.
Additional Function: sayHi
- This function
sayHi
is not called anywhere in the program, so its definition doesn’t affect the output.
Output:
- The output of the program comes from the
console.log("num", num);
statement inside theinner
function. - Despite the
num++
operation, due to hoisting and scoping rules in JavaScript, the output will be3
.
prediction 14
What will be the output of this program?
Input:
Answer
Output:
Object keys are automatically converted into strings. We are trying to set an object as a key to object a, with the value of 123. However, when we stringify an object, it becomes “[object Object]“. So what we are saying here, is that a[“[object Object]”] = 123. Then, we can try to do the same again. c is another object that we are implicitly stringifying. So then, a[“[object Object]”] = 456. Then, we log a[b], which is actually a[“[object Object]”]. We just set that to 456, so it returns 456.