Maria Ebrahimi
Maria Ebrahimi
  • Home
  • About
  • Experience
  • Services
  • Projects
  • Code Lab
  • Blog
Maria Ebrahimi
HomeAboutExperienceServicesProjectsCode LabBlog
// back to blog
JavaScript Interview QuestionsPART 2 OF 10

100 JavaScript Interview Questions — Part 2: Async & Modern Syntax

Questions 11 to 20 of the 100 JavaScript interview questions series: callbacks, promises, async/await, promise combinators, destructuring, spread/rest, currying, map/filter/reduce, memoization and copying objects.

September 12, 20267 min readJavaScript, Interview, Async, ES6
Glowing blue light curves weaving through connected nodes, representing asynchronous JavaScript
On this page
  • 11. What is a callback? What is callback hell?
  • 12. What is a promise and what states does it have?
  • 13. How does async/await work?
  • 14. Promise.all vs Promise.allSettled vs Promise.race vs Promise.any?
  • 15. What is destructuring?
  • 16. What are the spread and rest operators?
  • 17. What is currying?
  • 18. Explain map, filter and reduce
  • 19. What is memoization?
  • 20. What is the difference between a shallow copy and a deep copy?
On this page (10)
  • 11. What is a callback? What is callback hell?
  • 12. What is a promise and what states does it have?
  • 13. How does async/await work?
  • 14. Promise.all vs Promise.allSettled vs Promise.race vs Promise.any?
  • 15. What is destructuring?
  • 16. What are the spread and rest operators?
  • 17. What is currying?
  • 18. Explain map, filter and reduce
  • 19. What is memoization?
  • 20. What is the difference between a shallow copy and a deep copy?

11. What is a callback? What is callback hell?

A callback is a function passed to another function to be executed later — the foundation of asynchronous JavaScript, used by timers, events and network requests. There is nothing wrong with callbacks themselves; simple cases are perfectly readable.

Callback hell is what happens when asynchronous steps depend on each other and get nested: each step lives inside the previous one's callback, indentation grows, error handling gets duplicated, and the flow becomes hard to follow. Promises and async/await were created to flatten this pyramid.

example.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// nested callbacks — hard to follow
getUser(id, function (user) {
  getOrders(user, function (orders) {
    getItems(orders[0], function (items) {
      render(items); // deeply indented, errors handled nowhere
    });
  });
});

// flattened with promises
getUser(id)
  .then(getOrders)
  .then(function (orders) { return getItems(orders[0]); })
  .then(render)
  .catch(handleError); // one place for errors

12. What is a promise and what states does it have?

A promise is an object representing a value that will exist in the future. It is always in exactly one of three states: pending (initial), fulfilled (resolved with a value) or rejected (failed with a reason). Once settled, a promise can never change state again.

You consume a promise with then for the fulfilled value, catch for failures, and finally for cleanup that must run either way. A promise that neither settles nor is caught produces a silent unhandled rejection, which is a common production bug.

example.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const wait = new Promise(function (resolve, reject) {
  setTimeout(function () { resolve("done"); }, 100);
});

wait
  .then(function (value) {
    console.log(value); // "done" — fulfilled
  })
  .catch(function (err) {
    console.error(err); // runs only on rejection
  })
  .finally(function () {
    console.log("always runs");
  });

13. How does async/await work?

async/await is syntactic sugar over promises — it lets asynchronous code read like synchronous code. Marking a function async makes it always return a promise, and await pauses that function's execution until the awaited promise settles, without blocking the rest of the program.

Errors are handled with ordinary try/catch. A frequent mistake is awaiting inside a loop when the requests are independent — that runs them one by one; Promise.all runs them concurrently and is usually what you want.

example.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
async function loadUser(id) {
  try {
    const res = await fetch("/api/users/" + id);
    if (!res.ok) throw new Error("HTTP " + res.status);
    return await res.json();
  } catch (err) {
    console.error("load failed:", err.message);
    throw err;
  }
}

// sequential (slow):        const a = await f1(); const b = await f2();
// concurrent  (fast):
async function both() {
  const [a, b] = await Promise.all([f1(), f2()]);
  return [a, b];
}
TipBonus points: mention that await only pauses the async function itself — the event loop keeps running other code.

14. Promise.all vs Promise.allSettled vs Promise.race vs Promise.any?

Combinators run several promises together with different policies. Promise.all resolves when every input resolves, but rejects immediately if any one rejects — great for fail-fast parallel loading. Promise.allSettled waits for everything to settle and reports each outcome, success or failure, so nothing is lost.

Promise.race settles as soon as the first input settles, with its result or error — useful for timeouts. Promise.any resolves with the first fulfilled promise and only rejects if every input rejects. Choosing the right combinator is usually the whole interview question.

example.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const fast = Promise.resolve("fast");
const slow = new Promise(function (resolve) {
  setTimeout(function () { resolve("slow"); }, 1000);
});
const bad = Promise.reject(new Error("boom"));

Promise.all([fast, slow]);        // ["fast", "slow"]
Promise.all([fast, bad]);         // rejects immediately

Promise.allSettled([fast, bad]);  // [{status:"fulfilled"},
                                  //  {status:"rejected"}]

Promise.race([fast, slow]);       // "fast"
Promise.any([bad, slow]);         // "slow"

15. What is destructuring?

Destructuring is syntax for unpacking values from objects and arrays into distinct variables. It also works in function parameters, which is how options objects get clean defaults, and it can rename variables on the fly with name: alias.

Defaults only apply when the value is undefined — null counts as a present value. Combining destructuring with the rest pattern is how you both extract and keep 'the rest' of an object.

example.js
1
2
3
4
5
6
7
8
9
10
11
12
13
const user = { id: 7, name: "Sara", role: "dev" };

const { name, role: job = "user" } = user;
console.log(name, job); // "Sara" "dev"

const [first, second, ...others] = [1, 2, 3, 4];
console.log(first, others); // 1 [3, 4]

// defaults in function parameters
function greet({ name = "guest", emoji = "hi" } = {}) {
  return name + " says " + emoji;
}
greet(); // "guest says hi"

16. What are the spread and rest operators?

The same ... syntax plays two opposite roles depending on position. Spread expands a collection where values are expected: ...arr in array literals, ...obj in object literals — the classic way to shallow-clone or merge.

Rest gathers values where a variable is expected: ...args in parameters collects remaining arguments into a real array, and ...rest in destructuring collects whatever was not explicitly unpacked.

example.js
1
2
3
4
5
6
7
8
9
10
11
12
13
// spread — expand
const a = [1, 2];
const b = [...a, 3];            // [1, 2, 3]
const merged = { x: 1, ...{ y: 2 } }; // { x: 1, y: 2 }

// rest — collect
function sum(...nums) {
  return nums.reduce(function (t, n) { return t + n; }, 0);
}
sum(1, 2, 3); // 6

const { id, ...payload } = { id: 1, name: "Sara", age: 30 };
// payload = { name: "Sara", age: 30 }

17. What is currying?

Currying transforms a function that takes several arguments into a chain of functions that each take one. Instead of f(a, b, c) you call f(a)(b)(c). It enables partial application: fix some arguments now, get a reusable specialized function.

Currying shines in composition-heavy code — pipelines, event-handler factories, config-driven helpers. It is related to, but not the same as, partial application; interviewers often ask you to implement a generic curry function.

example.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function multiply(a) {
  return function (b) {
    return a * b;
  };
}

const double = multiply(2); // partially applied
const triple = multiply(3);

double(5);  // 10
triple(5);  // 15

// generic version
function curry(fn) {
  return function next(...args) {
    return args.length >= fn.length
      ? fn(...args)
      : function (...more) { return next(...args, ...more); };
  };
}

18. Explain map, filter and reduce

The three workhorse array methods. map returns a new array with every element transformed by your function — same length. filter returns a new array with only the elements for which your predicate returns true. Neither mutates the original.

reduce is the general one: it folds the array into a single value of any shape — a number, string, object, even another array — by carrying an accumulator through each element. Any map or filter can be expressed with reduce, which is why it is seen as the fundamental operation.

example.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const orders = [
  { item: "pen", total: 20 },
  { item: "book", total: 120 },
  { item: "bag", total: 80 },
];

const expensive = orders
  .filter(function (o) { return o.total > 50; })   // 2 items
  .map(function (o) { return o.item; });           // ["book", "bag"]

const sum = orders.reduce(function (acc, o) {
  return acc + o.total;
}, 0); // 220

// reduce building an object
const byItem = orders.reduce(function (acc, o) {
  acc[o.item] = o.total;
  return acc;
}, {});

19. What is memoization?

Memoization caches the result of an expensive function call keyed by its arguments, so repeated calls with the same inputs return instantly. It trades memory for CPU time and is a standard optimization for pure functions.

The classic interview task is writing a generic memoizer. The key detail is the cache key — for multiple arguments you usually serialize them. Be ready to discuss when memoization hurts: impure functions, huge input spaces, or cheap functions where the cache lookup costs more than recomputing.

example.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function memoize(fn) {
  const cache = new Map();
  return function (...args) {
    const key = JSON.stringify(args);
    if (cache.has(key)) return cache.get(key);
    const result = fn.apply(this, args);
    cache.set(key, result);
    return result;
  };
}

const fib = memoize(function (n) {
  return n < 2 ? n : fib(n - 1) + fib(n - 2);
});

fib(40); // fast — without memoization this is very slow

20. What is the difference between a shallow copy and a deep copy?

A shallow copy duplicates only the first level: nested objects are still shared references, so mutating a nested property through the copy also changes the original. Spread, Object.assign and Array.prototype.slice are all shallow.

A deep copy recursively clones everything, so the copy is fully independent. Modern JavaScript has structuredClone built in, which handles nested objects, arrays, dates, maps and cycles. Before it existed, people used JSON.parse(JSON.stringify(obj)), which silently breaks on functions, undefined, dates and circular references.

example.js
1
2
3
4
5
6
7
8
9
const original = { name: "Sara", address: { city: "Tabriz" } };

const shallow = { ...original };
shallow.address.city = "Tehran";
console.log(original.address.city); // "Tehran" — shared!

const deep = structuredClone(original);
deep.address.city = "Shiraz";
console.log(original.address.city); // unchanged
TipMention structuredClone by name — it is the modern answer and many interviewers listen for it specifically.
// all parts of 100 JavaScript Interview Questions
Previous · Part 1

100 JavaScript Interview Questions — Part 1: Foundations

Next · Part 3

Coming soon...

Keep reading

Ready-to-Use AI Prompts for Front-End Developers

AI Prompts

Ready-to-Use AI Prompts for Front-End Developers

Ten battle-tested AI prompt templates for front-end work: component generation, code review, CSS debugging, refactoring, accessibility, performance, TypeScript and more. Copy, fill the placeholders, paste.

September 14, 20265 min read
Glowing blue glass cubes stacked on a blueprint grid, representing JavaScript foundations
JavaScript Interview QuestionsPART 1

100 JavaScript Interview Questions — Part 1: Foundations

Questions 1 to 10 of the 100 JavaScript interview questions series: var/let/const, hoisting, closures, this, the event loop and other core fundamentals, with clear answers and runnable examples.

September 5, 20267 min read
© 2026 Maria Ebrahimi— Designed & built with care. · Become a writer · Privacy Policy