DEV Community

Cover image for 10 JavaScript Output Questions That Look Innocent but Might Betray You in Interviews
Kapil RaghuwanshiđŸ–„
Kapil RaghuwanshiđŸ–„

Posted on

10 JavaScript Output Questions That Look Innocent but Might Betray You in Interviews

JavaScript was written in 10 days, and we've been debugging it for decades.

Microsoft, Amazon, Atlassian, Walmart, eBay, and several similar big tech companies test candidates' JavaScript knowledge with such quirky problems.
So if you are a frontend developer - React, Next.js, or maybe Node.js developer, you know JavaScript doesn't test syntax in interviews. It tests mental models.
If you don't understand how the engine thinks-hoisting, closures, the event loop, mutation-these questions will humble you very quickly.

Let's break 10 of them as a JS engine would.

Are you ready?

Cool, give me the output for 


Problem 1: Modifying Array While Iterating (forEach + splice)

var aa = [1, 2, 3, 4, 5, 6];

aa.forEach(i => {
  console.log(i);
  if (i > 3) aa.splice(aa.indexOf(i), 1);
});
Enter fullscreen mode Exit fullscreen mode

Mutation during iteration + how forEach handles index internally.

Output:
1
2
3
4
6
Enter fullscreen mode Exit fullscreen mode

Explanation:
forEach moves forward using the internal index. When you remove 4, the array becomes [1,2,3,5,6]. Now the next index moves forward - but since elements shifted left, 5 gets skipped. forEach iterates using an internal index and does not account for structural changes in the array during iteration.
When splice() removes an element, subsequent elements shift left, but the internal pointer still increments forward, causing elements to be skipped. This is why mutating an array while iterating over it often produces unexpected results.

You mutate the array while walking on it. Classic trap. 

Dry Run:

Initial: [1,2,3,4,5,6]
i=1 → ok
i=2 → ok
i=3 → ok
i=4 → splice → [1,2,3,5,6]
Next internal index → skips 5
i=6 → splice → [1,2,3,5]
Enter fullscreen mode Exit fullscreen mode

The engine doesn't rewind for your feelings.

JavaScript started as a browser toy and accidentally became the language that runs the internet.

meme

Problem 2: Default Params + Destructuring

function getApp({ name = "Anonymous", age } = {}) {
  console.log(name, age);
}

getApp({name:"Tech Monk Kapil",age:25});
getApp({age:25});
getApp();
Enter fullscreen mode Exit fullscreen mode

Default parameters + destructuring + default object.

Output:

Tech Monk Kapil 25
Anonymous 25
Anonymous undefined
Enter fullscreen mode Exit fullscreen mode

Explanation:
If argument missing → {} used. 
If name missing → default applies. 
If age is missing → undefined. 
Destructuring + default object prevents a crash.

Dry Run:
Call 1 → name="john", age=25
Call 2 → name default, age=25
Call 3 → param defaults to {}, name default, age undefined

meme

All these questions are curated and collected from my past interview experiences in last 10 years.

Problem 3: Immutable vs Mutating Copy

const obj = { id: 5, name: "Tech Monk Kapil" };
const updated = { ...obj, name: "Kapil Raghuwanshi" };
const updated1 = { ...obj };
updated1.name = "Mohan";

obj
updated
updated1
Enter fullscreen mode Exit fullscreen mode

The spread operator makes a shallow copy.

Output:

{ id: 5, name: "Tech Monk Kapil" }
{ id: 5, name: "Kapil Raghuwanshi" }
{ id: 5, name: "Mohan" }
Enter fullscreen mode Exit fullscreen mode

Explanation:
Spread creates a new object reference. But it's shallow - nested objects would still mutate.
Read more about the shallow vs deep copy difference.

Simple right?

Cool, another one on similar lines then 


Problem 4: Default Param + Rest Param

function getOutput(name = "Unknown", ...nums) {
  console.log(name, nums);
}

getOutput("kapil", 10, 20, 30);
getOutput();
getOutput(undefined, 10, 20);
getOutput(null);
Enter fullscreen mode Exit fullscreen mode

Default param triggers only on undefined.

Output:

kapil [10,20,30]
Unknown []
Unknown [10,20]
null []
Enter fullscreen mode Exit fullscreen mode

Explanation:
Only undefined activates the default. null is considered a valid value.

Follow me on Medium and let's be connected on LinkedIn (@kapilraghuwanshi) and Twitter (@techygeeeky) for more such interesting tech articles.đŸ€

Problem 5: Promise.all + fetch

const GOOGLE = "https://www.google.com";

Promise.all([
  fetch(GOOGLE).then(() => console.log("b")),
  fetch(GOOGLE).then(() => console.log("c"))
]).then(() => console.log("after"));
Enter fullscreen mode Exit fullscreen mode

Parallel promises + microtasks.

Output:

b
c
after
(or c then b - depends on network timing)
Enter fullscreen mode Exit fullscreen mode

Explanation:
Promise.all executes promises in parallel and resolves only when all included promises resolve. The order of execution of individual .then() callbacks depends on when each promise settles, not the order they were declared. The final .then() runs after all promises are fulfilled. Both fetch calls fire simultaneously. Promise.all waits until both resolve. Then after runs.

Once you see JavaScript as a runtime engine - not a syntax language - interviews become pattern recognition instead of panic mode.

meme

Also, I have recently started creating tech content on my YouTube channel, just have a look at it TechMonkKapil, and subscribe if you like the content!đŸ€

Problem 6: Event Loop Madness

console.log("Script Start");

setTimeout(() => console.log("setTimeout"), 0);

async function check() {
  console.log("Async Start");
  await Promise.resolve();
  console.log("Async End");
}

check();

new Promise((resolve) => {
  console.log("Promise Executor");
  resolve();
}).then(() => {
  console.log("Promise Then");
});

console.log("Script End");
Enter fullscreen mode Exit fullscreen mode

Call stack + microtask queue + macrotask queue.
Output:

Script Start
Async Start
Promise Executor
Script End
Async End
Promise Then
setTimeout
Enter fullscreen mode Exit fullscreen mode

Explanation:
JavaScript executes synchronous code first, then processes all microtasks (like Promise.then and await), and finally handles macrotasks (like setTimeout). Microtasks are always completed before the next macrotask begins. This strict priority order determines the output sequence.
Order of priority:

  1. Synchronous code
  2. Microtasks (Promise, await)
  3. Macrotasks (setTimeout)

Event loop is deterministic. Chaos is only in our heads.

These questions are not about tricks. They're about understanding execution context, scope, mutation, and the event loop deeply.

meme

Problem 7: map(parseInt) Trap

[10,10,10].map(parseInt)
[10,10,10].map(parseFloat)
Enter fullscreen mode Exit fullscreen mode

map passes (value, index). parseInt(value, radix)
Output:

[10, NaN, 2]
[10,10,10]
Enter fullscreen mode Exit fullscreen mode

Explanation:

parseInt("10", 0) → 10
parseInt("10", 1) → NaN
parseInt("10", 2) → 2
Enter fullscreen mode Exit fullscreen mode

map passes three arguments to its callback: value, index, and array. Since parseInt expects the second argument to be a radix, the array index unintentionally becomes the radix value. This mismatch leads to unexpected results like NaN.

Index becomes radix. Interview classic

JavaScript distinguishes absence from intentional emptiness.

Problem 8: Function vs var Hoisting

console.log(typeof foo)
var foo
function foo(){}
Enter fullscreen mode Exit fullscreen mode

Function hoisting wins over var.

Output:
"function"

Explanation:
Function declarations are hoisted first. var is hoisted, but without overwriting the function at that stage.

JavaScript didn't win because it was perfect; it won because it was everywhere.

The next one is very famous already, and 80% chances are there that you have faced or solved this already. 

But still give it a try one more time

Problem 9: IIFE Closure with setTimeout

for (var i = 0; i < 5; i++) {
  (function(i) {
    setTimeout(() => {
      console.log(i);
    }, i * 2000);
  })(i);
}
Enter fullscreen mode Exit fullscreen mode

Closure + IIFE + var scope.
Output:

0
after 2 sec
1
after 2 sec
2
after 2 sec
3
after 2 sec
4
Enter fullscreen mode Exit fullscreen mode

Explanation:
var is function-scoped, so without additional scoping, all loop iterations would share the same variable reference. Wrapping each iteration in an IIFE creates a new execution context and captures the current value in a closure. This preserves the expected behavior for asynchronous callbacks. IIFE creates a new scope per iteration. Without it → all would print 5.

Problem 10: Arrow Function and this

let person = {
  name: 'Kapil',
  hobbies: ['React', 'Langchain', 'Next.js'],
  showHobbies: function() {
    this.hobbies.forEach((hobby) => {
      console.log(`${this.name} likes ${hobby}`);
    });
  }
};

person.showHobbies();
Enter fullscreen mode Exit fullscreen mode

Arrow function inherits this.

Kapil likes React
Kapil likes Langchain
Kapil likes Next.js
Enter fullscreen mode Exit fullscreen mode

Arrow functions do NOT bind their own this. They inherit from the parent scope (showHobbies). If normal function used → this would be undefined (or window).


That's all, folks, for this article. Hope it was fun!😃

Hope you learned something new today, at least one new concept. If yes, save it and share it with your colleagues.

Also, I have recently started creating tech content on my YouTube channel, just have a look at it TechMonkKapil, and subscribe if you like the content!đŸ€

Also, we are building a tech community on Telegram(Tech Monks) and Discord(Tech Monks). Join if you are looking to interact with like-minded folks.

Follow me on Medium and let's be connected on LinkedIn (@kapilraghuwanshi) and Twitter (@techygeeeky) for more such interesting tech articles.đŸ€

Write your suggestions and feedback in the comment section belowđŸ‘‡đŸ»

Top comments (0)