Must know JS questions for interviews

Rajdeep Chandra
4 min readMay 20, 2024

--

1. How closure affects the garbage collection of JavaScript?

Closures can affect garbage collection in JavaScript by extending the lifetime of variables that are captured by the closure. A closure is a function that retains access to its lexical scope, even when the function is executed outside that scope. This means that if a function inside another function captures variables from its parent scope, those variables will not be garbage collected as long as the inner function (closure) exists.

For example:


function outerFunction() {
let capturedVariable = “I’m captured”;
return function innerFunction() {
console.log(capturedVariable);
}
}

const closure = outerFunction();
// Even after outerFunction execution is done, capturedVariable is not garbage collected because innerFunction still references it.
closure();

In this example, capturedVariable is kept in memory as long as `innerFunction` exists because `innerFunction` forms a closure over `capturedVariable`.

2. Why only variables created with `var` keyword are hoisted and initialized with `undefined`?

In JavaScript, variable declarations using `var` are hoisted to the top of their containing function or global scope, and they are initialized with `undefined`. This behavior stems from the design of JavaScript’s variable scoping and hoisting mechanisms.


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

Here’s what happens behind the scenes:
1. The variable declaration `var a;` is hoisted to the top of its scope.
2. The variable `a` is initialized with `undefined`.
3. The assignment `a = 5;` occurs where it appears in the code.

On the other hand, variables declared with `let` and `const` are also hoisted but are not initialized. They are in a “temporal dead zone” from the start of the block until the declaration is encountered:

console.log(b); // ReferenceError: Cannot access ‘b’ before initialization
let b = 5;

3. How hoisting works in JavaScript if it is interpreted?

JavaScript is an interpreted language, but modern JavaScript engines like V8 (used in Chrome and Node.js) actually use a mix of interpretation and Just-In-Time (JIT) compilation for performance. Hoisting is a result of the way JavaScript engines parse and execute code in two phases: the creation phase and the execution phase.

During the creation phase:
- The engine parses the code and sets up the scope.
- All variable and function declarations are hoisted to the top of their scope.

During the execution phase:
- The code is executed line by line.
- Variable assignments and other expressions are evaluated.


function foo() {
console.log(bar); // undefined due to hoisting
var bar = 10;
}
foo();

4. How does `Promise.all` work behind the scenes in a single-threaded JavaScript environment?

`Promise.all` takes an iterable of promises and returns a single promise that resolves when all of the promises in the iterable have resolved or rejects when any of the promises reject. Here’s a simplified version of what happens behind the scenes:

1. `Promise.all` iterates over the iterable of promises.
2. For each promise, it attaches a `then` handler to capture the resolved value or a `catch` handler to capture the rejection.
3. It keeps track of how many promises have been resolved.
4. When all promises have resolved, it resolves the returned promise with an array of the resolved values.
5. If any promise rejects, it immediately rejects the returned promise with the reason for the first rejection encountered.

Even though JavaScript is single-threaded, it can handle asynchronous operations using the event loop and microtask queue. Promises use the microtask queue, which allows them to be resolved or rejected as soon as possible after the current operation completes.


Promise.all([promise1, promise2, promise3])
.then(values => {
// values is an array of resolved values
})
.catch(error => {
// error is the reason of the first rejected promise
});

5. Why does function currying have no official reference on MDN, and what are the right use cases for function currying?

Function currying may not have an extensive reference on MDN because it is a functional programming concept rather than a JavaScript-specific feature. Currying transforms a function that takes multiple arguments into a sequence of functions that each take a single argument.


function curry(fn) {
return function curried(…args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
} else {
return function(…nextArgs) {
return curried.apply(this, args.concat(nextArgs));
};
}
};
}

function add(a, b) {
return a + b;
}
let curriedAdd = curry(add);
console.log(curriedAdd(1)(2)); // 3

Currying can be useful for:
- Creating higher-order functions.
- Partially applying functions, which can be useful in functional programming to create reusable, specialized functions from more general ones.
- Enhancing code readability and maintainability by breaking down functions into simpler, composable parts.

In summary, currying is a powerful technique in functional programming that, while not unique to JavaScript, can be effectively used in JavaScript to improve code flexibility and reusability.

--

--