-->
I absolutely botched an interview by answering just 2 out of 10 questions correctly, and my comeback tactic was to ask Claude to find me about 30 more such questions so that I could answer all 40 as a way of both learning from my mistakes as well as hoping not to fail another interview due to lack of technical depth.
You can find the original list Claude generated (without answers) here. Referencing it here since I won’t be answering every single thing down in this post, especially the things I find super basic or commonly known.
Without further ado (I hope that’s the right word), fire away!
bind
do?Function.prototype.bind()
is used to create a new function from an existing function that is bound to a specific context, this means that the this
keyword inside the bound function is always tied to the context you provided.
function fruit(price) {
console.log(`${this.name} is priced at $${price}`);
}
const mango = { name: "Mango" };
const mangoFruit = fruit.bind(mango);
mangoFruit(5);
// Expected output: "Mango is priced at $5"
Notice how fruit is able to reference this.name
even though this.name is not being set inside fruit. This is because fruit is tied to mango’s context, which has a name property.
What’s lovely about bind is that you can also predefine certain parameters, meaning your bound functions don’t need to provide obvious parameters every single time.
function add(a, b, c) {
return a + b + c;
}
const addFiveAndTen = add.bind(null, 5, 10);
console.log(addFiveAndTen(3)); // 18 (5 + 10 + 3)
One famous usage of bind was in React’s class components. Event handlers had to be bound to the component instances in order for them to access the state and other properties.
class MyComponent extends React.Component {
render() {
// When React calls component.render(), `this` = component instance
return <button onClick={this.handleClick.bind(this)}>Click Me</button>;
}
handleClick() {
// When called as an event handler, `this` would be undefined/window
// So, we bind handleClick to `this` and that gives it access to the this.setState and other class properties
this.setState({
/* ... */
});
}
}
call
and apply
do?Function.prototype.call()
is used to call a function with a certain context. Meaning that using call, you can define what context the this
keyword refers to.
This means that you can call a function named fruit with the context of Mango and whatever changes fruit makes using the this
keyword apply to the context of Mango, whose this
you provided.
function Fruit(name, price) {
this.name = name;
this.price = price;
}
function Mango(price) {
Fruit.call(this, "mango", price);
this.headline = "King of all fruits";
}
console.log(new Mango(5).name);
// Expected output: "mango"
// Mango has a name property even though name was set in Fruit
Function.prototype.apply
does the exact same thing as call just with an array of parameters instead of individual parameters. Hence, in the example above, apply would make this slight difference:
Fruit.apply(this, ["mango", price]);
const user = {
signIn: () => {
console.log(this);
},
signOut: function () {
console.log(this);
},
};
user.signIn(); // What gets logged?
user.signOut(); // What gets logged?
signIn
is an arrow function, arrow functions inherit the this
value from their lexical scope, which means it’ll either log Window
or global
depending on whether the runtime is browser or Node. Now, if user is defined inside a nested lexical scope, such as inside a class, then the this
keyword would refer to that class’ instance.// At global level:
const user = {
signIn: () => {
console.log(this); // `this` inherited from global scope = global/Window object
},
};
// Inside a class:
class MyClass {
createUser() {
const user = {
signIn: () => {
console.log(this); // `this` inherited from createUser method = MyClass instance
},
};
return user;
}
}
signOut
will log the user object cuz its a function that gets called as a method of the user object, which binds the function to the user’s context.// At global level:
const user = {
signOut: function () {
console.log(this); // `this` bound to the object that calls signOut = user
},
};
// Inside a class:
class MyClass {
createUser() {
const user = {
signOut: function () {
console.log(this); // `this` bound to the object that calls signOut = user
},
};
return user;
}
}
Promise.all
do?Promise.all
can be used to execute multiple promises in parallel and get their results in the same order. Here’s an example:
const getUser = new Promise((resolve) => setTimeout(() => resolve({})), 1000);
const getProduct = new Promise((resolve) => setTimeout(() => resolve({})), 100);
const [user, product] = await Promise.all([getUser, getProduct]);
Even though getUser
has a timeout that waits for a whole second as opposed to the 100ms timeout of getProduct
, Promise.all
returns you the results in order.
One catch about Promise.all
is that you don’t get any results at all if even one of the promises fail. Which is why it’s encouraged to use Promise.all
when you need all promises to succeed. Like if you have a component that uses both the user and the product, you can ensure that both are available using Promise.all
before handing them over.
Promise.all
without using Promise.all
.Okay, let’s first identify what Promise.all
does:
const getUser = new Promise((resolve) => setTimeout(() => resolve({})), 1000);
const getProduct = new Promise((resolve) => setTimeout(() => resolve({})), 100);
const [user, product] = await executePromises([getUser, getProduct]);
function executePromises(promises) {
if (!promises || promises.length === 0) {
return Promise.resolve([]); // Guard against faulty promises
}
return new Promise((resolve, reject) => {
let resultsCount = 0; // Counting manually since results are always equal to promises in length
const results = new Array(promises.length); // Create an array equally sized as the promises
promises.forEach((promise, index) => {
Promise.resolve(promise) // Handle non-promises
.then((result) => {
results[index] = result;
resultsCount++;
if (resultsCount === promises.length) {
resolve(results);
}
})
.catch(reject);
});
});
}
That works (I wish I’d written it this nicely in that interview I botched), checks all the boxes and looks clean.
Promise.allSettled
without using Promise.allSettled
.Okay, from MDN, Promise.allSettled
does the following:
status
: A string, either “fulfilled” or “rejected”, indicating the state of the promise.value
: Only present if status is “fulfilled”. The result of the promise.reason
: Only present if status is “rejected”. The reason why the promise was rejected.This means we can modify the code we have above a tiny bit and we’ll have a working allSettled
example at our hands, let’s do it:
const getUser = new Promise((resolve) => setTimeout(() => resolve({}), 1000));
const getProduct = new Promise((_, reject) =>
setTimeout(() => reject({}), 100)
);
const [userResult, productResult] = await executePromises([
getUser,
getProduct,
]);
const user = userResult.status === "fulfilled" ? userResult.value : null;
const product =
productResult.status === "fulfilled" ? productResult.value : null;
function executePromises(promises) {
if (!promises || promises.length === 0) {
return Promise.resolve([]);
}
return new Promise((resolve) => {
let resultsCount = 0;
const results = new Array(promises.length);
promises.forEach((promise, index) => {
Promise.resolve(promise) // Handle non-promises
.then((result) => {
results[index] = { status: "fulfilled", value: result };
resultsCount++;
if (resultsCount === promises.length) resolve(results);
})
.catch((reason) => {
results[index] = { status: "rejected", reason };
resultsCount++;
if (resultsCount === promises.length) resolve(results);
});
});
});
}
That, is well done. Notice that the only prominent differences are 2:
Essentially, Promise.allSettled
is for when you want to execute multiple promises but your components don’t depend on all of them, meaning partial success. Consider this React server component as an example:
function App() {
const [userResult, productResult] = await executePromises([
getUser,
getProduct,
]);
const user = userResult.status === "fulfilled" ? userResult.value : null;
const product = productResult.status === "fulfilled" ? productResult.value : null;
return (
<>
{user && <User user={user} />}
{product && <Product product={product} />}
</>
)
}
Based on these, try to recreate Promise.any and Promise.race by yourself, will be fun.
Did a little piece on the event loop here, you can check it out.
Given that:
const testCases = [
{ input: [1, 5, 8, 11, 9], expected: 792 },
{ input: [-10, -11, -1, 0, 5], expected: 550 },
{ input: [-8, 0, 9, 10, 11], expected: 990 },
{ input: [-5, -4, -3, -2, -1], expected: -6 },
];
We need to create a function that takes an input and finds out what the maximum product can be from any 3 numbers in the array. If you look at the first array, 8 * 11 * 9 = 792
, so the expected answer is 792. Look at the second array, -10 * -11 * 5 - 550
, but notice that the largest numbers in the array were 1, 0 and, 5
, not -10, -11, and 5
.
What that results in is an expectation that the largest number in such an array can either be:
Keeping that in mind, this is what we can do:
function findMaxProduct(array) {
if (array.length < 3) return null; // Since we know that we need minimum 3 from the question statement.
const sorted = array.toSorted((a, b) => a - b);
// Would result in [-11, -10, -1, 0, 5] for the second input
const n = sorted.length;
// Based on the criteria above, there can be 2 largest numbers in the array:
const option1 = sorted[n - 1] * sorted[n - 2] * sorted[n - 3]; // The largest positive numbers at the end of the array
const option2 = sorted[0] * sorted[1] * sorted[n - 1]; // The largest 2 negative numbers * the largest positive number
// JavaScript is convenient
return Math.max(option1, option2);
}
Pretty simple, actually.
Event delegation is a practice that is used to handle events on child elements through listeners on parents.
// Instead of this (inefficient):
document.querySelectorAll("li").forEach((li) => {
li.addEventListener("click", handleClick);
});
// Do this (delegation):
document.querySelector("ul").addEventListener("click", (event) => {
if (event.target.matches("li")) handleClick(event);
});
Event bubbling is the default behavior of events. Events bubble up/propagate to the root element from the target element.
When you click a button, the event starts from the button all the way to the root, triggering all click event handlers on the way.
Something like <button> → <body> → <html> → Document → Window
You might have encountered a scenario where a parent clickable element has a clickable child but when you click the child, the parent’s click handler also triggers. To prevent this, you can call the stopPropagation
helper on the event and that will cancel the bubbling effect.
function handleDelete(event) {
event.stopPropagation(); // Stop the propagation of this event, preventing the button's click event from being fired
deleteSomething();
}
return (
<button onClick={handleClick}>
<SomeComponent />
<span onClick={handleDelete}>Delete</span>
</button>
);
Event capturing is the opposite of bubbling. Events can be configured to flow downwards, such as Window → Document → <html> → <body> → <button>
Capturing is particularly useful when you want to run something before the target element’s event gets fired.
// Stop all clicks on disabled elements before they reach any handlers
document.addEventListener(
"click",
(event) => {
if (event.target.closest("[disabled]")) {
event.stopPropagation();
event.preventDefault();
}
},
true // capturing phase
);
And there you have it, tbf I keep forgetting what delegation is and I have to constantly remind myself before interviews. Hopefully, this will help me out as well.
I’ll probably keep adding more and more questions here as I encounter them or just want to. Until then, Arrivederci baby (I think it means goodbye).
Send me an email or message me on LinkedIn if you're looking for someone who builds without BS.