Back to blog
JavaScript

JavaScript Loops -Production + Interview Mastery

Imagine you are a warehouse manager in Patna - 1000 orders have arrived. The same work has to be done on every order - check it, pack it, dispatch it.

PublishedApril 4, 2026
AuthorRahul Verma
Readers
Counting views...
JavaScript
JavaScript Loops -Production + Interview Mastery
Article cover
Share or save

Save this post for later or send it to someone working on SEO, frontend, or product UX.


  1. MENTAL MODEL

Imagine you are a warehouse manager in Patna - 1000 orders have arrived. The same work has to be done on every order - check it, pack it, dispatch it.

There are two options:

Option A - Hire 1000 different people, one for each order.

Option B - One person picks up one order, does the work, then moves to the next one.

Option B = Loop.

A loop exists because:

The same task, different data, repeated again and again - this is the most common pattern in programming.

Without loops, you would write 1000 lines - separate code for every user. The loop said write it once, run it many times.

But here is the twist - a loop is not just “repeat”.
A loop is a controlled iteration machine where you decide:

  • When to start

  • When to stop

  • What to do at each step

If you understand these three things, no form of loop will confuse you.

  1. CORE CONCEPT

for loop - the most fundamental#

snippet
for (let i = 0; i < 3; i++) {
console.log(i);
}
// 0, 1, 2
 

Three parts:

snippet
for ( INIT ; CONDITION ; UPDATE )
↓ ↓ ↓
where to until when what changes
start to run every step
 

All other loops are just variations of this:

snippet
// while — focuses on condition
let i = 0;
while (i < 3) { console.log(i); i++; }
 
// for...of — when you need values (arrays)
for (const item of arr) { }
 
// for...in — when you need keys (objects)
for (const key in obj) { }
 
// forEach — array method, callback based
arr.forEach((item, index) => { })
 
  1. UNDER THE HOOD

When this loop runs:

snippet
for (let i = 0; i < 3; i++) {
console.log(i);
}
 

The JS engine internally does this:

snippet
CALL STACK:
┌─────────────────────────┐
1. let i = 0 │ ← INITonce
2. i < 3? YESexecute │ ← CONDITION CHECK
3. console.log(0) │ ← BODY
4. i++i = 1 │ ← UPDATE
5. i < 3? YESexecute │ ← CONDITION CHECK again
6. console.log(1)
7. i++i = 2
8. i < 3? YESexecute
9. console.log(2)
10. i++i = 3
11. i < 3? NOEXIT │ ← LOOP ENDS
└─────────────────────────┘
 

let vs var in loops - memory level:#

snippet
// var — function scoped, there is ONE i in memory
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Output: 3, 3, 3
 
snippet
// let — block scoped, there is a DIFFERENT i for every iteration
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Output: 0, 1, 2
 

With var, there is only one i in memory - when setTimeout fires, the loop has already finished, so i = 3.

With let, every iteration creates its own closure - a different i gets captured.

  1. REAL WORLD USAGE

Use Case 1 - GenExpence: Transactions filter + calculate#

snippet29 lines
// Production code — create monthly summary from user's transactions
async function getMonthlySummary(userId, month) {
const transactions = await Transaction.find({ userId });
let totalIncome = 0;
let totalExpense = 0;
const categoryBreakdown = {};
 
for (const txn of transactions) {
// Month filter
if (txn.date.getMonth() !== month) continue;
 
// Income vs Expense
if (txn.type === 'income') {
totalIncome += txn.amount;
} else {
totalExpense += txn.amount;
}
 
// Category breakdown
if (!categoryBreakdown[txn.category]) {
categoryBreakdown[txn.category] = 0;
}
categoryBreakdown[txn.category] += txn.amount;
}
 
return { totalIncome, totalExpense, categoryBreakdown };
}
 

Use Case 2 - LMS: Batch certificate generation#

snippet31 lines
// Generate certificates for 50 students at once
async function generateBatchCertificates(courseId) {
const students = await Student.find({ courseId, status: 'completed' });
const results = [];
 
for (let i = 0; i < students.length; i++) {
const student = students[i];
try {
const pdfBuffer = await generateCertificatePDF(student);
const url = await uploadToImageKit(pdfBuffer, student._id);
await Student.updateOne(
{ _id: student._id },
{ certificateUrl: url }
);
 
results.push({ studentId: student._id, status: 'success' });
// Progress log
console.log(`Generated ${i + 1}/${students.length}`);
} catch (err) {
// If one fails, do not stop the rest — continue
results.push({ studentId: student._id, status: 'failed', err });
}
}
 
return results;
}
 

Use Case 3 - CRM: Calculate total for proposal line items#

snippet28 lines
// Smart Proposal Generator — calculate total + tax from items
function calculateProposalTotal(lineItems, gstRate = 0.18) {
let subtotal = 0;
const itemizedList = [];
 
for (const item of lineItems) {
const itemTotal = item.quantity * item.unitPrice;
subtotal += itemTotal;
 
itemizedList.push({
...item,
total: itemTotal,
formattedTotal: `₹${itemTotal.toLocaleString('en-IN')}`
});
}
 
const gst = subtotal * gstRate;
const grandTotal = subtotal + gst;
 
return {
items: itemizedList,
subtotal,
gst,
grandTotal,
formatted: `₹${grandTotal.toLocaleString('en-IN')}`
};
}
 
  1. COMMON MISTAKES

Mistake 1 - Trying to break inside forEach#

snippet
// WRONG — this does not work, forEach does not support break
users.forEach(user => {
if (user.role === 'admin') break; // SyntaxError
});
 
snippet
// RIGHT — use for...of
for (const user of users) {
if (user.role === 'admin') break; // Works perfectly
}
 

Mistake 2 - Using for...of on an object#

snippet
const config = { db: 'mongo', port: 3000 };
 
// WRONG
for (const item of config) { } // TypeError: config is not iterable
 
snippet
// RIGHT
for (const [key, value] of Object.entries(config)) {
console.log(key, value);
}
 

Mistake 3 - Forgetting await in an async loop#

snippet
const userIds = ['a1', 'b2', 'c3'];
 
// WRONG — forEach does not handle async/await properly
userIds.forEach(async (id) => {
const user = await fetchUser(id); // this await does not flow outward properly
console.log(user);
});
// Everything fires in parallel in an uncontrolled way
 
snippet
// RIGHT — use for...of with async
for (const id of userIds) {
const user = await fetchUser(id); // properly awaits
console.log(user);
}
 

Mistake 4 - Modifying an array inside the loop#

snippet
const arr = [1, 2, 3, 4, 5];
 
// WRONG — splice inside loop shifts indexes
for (let i = 0; i < arr.length; i++) {
if (arr[i] % 2 === 0) arr.splice(i, 1);
}
// Result: [1, 3, 5] — wrong, some elements get skipped
 
snippet
// RIGHT — use filter
const result = arr.filter(n => n % 2 !== 0);
 
  1. EDGE CASES & GOTCHAS

Gotcha 1 - Do not use for...in on arrays#

snippet
const arr = [10, 20, 30];
arr.customProp = 'hello'; // someone added a property to the array
 
for (const key in arr) {
console.log(key); // "0", "1", "2", "customProp" ← unexpected!
}
 

for...in also iterates over the prototype chain.

For arrays, always use for...of.

Gotcha 2 - Infinite loop = production killer#

snippet
// This is an infinite loop — condition will never become false
for (let i = 0; i >= 0; i++) {
processOrder(i); // server crash
}
 

In production, always keep a safety limit:

snippet
const MAX_ITERATIONS = 10000;
let count = 0;
 
while (condition) {
if (count++ > MAX_ITERATIONS) {
console.error('Safety limit hit');
break;
}
doWork();
}
 

Gotcha 3 -var in loop + closure (classic interview trap)#

snippet
const funcs = [];
 
for (var i = 0; i < 3; i++) {
funcs.push(() => i);
}
 
console.log(funcs[0]()); // 3 — not 0!
console.log(funcs[1]()); // 3 — not 1!
console.log(funcs[2]()); // 3 — not 2!
 

Fix 1 - Use let#

snippet
for (let i = 0; i < 3; i++) {
funcs.push(() => i); // each iteration gets its own i
}
 

Fix 2 - IIFE (old approach)#

snippet
for (var i = 0; i < 3; i++) {
funcs.push(((j) => () => j)(i));
}
 
  1. INTERVIEW SECTION

a) 3 Conceptual Questions#

Q1. What is the difference between for...of and for...in?#

for...in iterates over an object's enumerable keys, and it may also include properties from the prototype chain. Avoid using it on arrays.

for...of iterates over iterable values (Arrays, Strings, Maps, Sets). It does not work directly on plain objects.

Q2. forEach vs for...of - when to use which?#

forEach = use it when you need simple iteration, no async/await, and no need for break or continue.

for...of = use it when you need break/continue, or when using async/await inside the loop.

Q3. What difference does let vs var make inside loops?#

var = function scoped, only one variable exists in memory, and it creates closure issues (classic setTimeout problem).

let = block scoped, every iteration gets its own binding, so closures capture values correctly.

b) 2 Tricky Output Questions#

Q1. What will be the output?#

snippet
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
 

Answer: 3, 3, 3

Why: var creates only one i, and by the time setTimeout runs, the loop has already finished and i = 3.

Q2. What will be the output?#

snippet
const arr = [1, 2, 3];
for (const num of arr) {
if (num === 2) continue;
console.log(num);
}
 

Answer: 1, 3

continue skips the current iteration - so 2 is skipped, and the rest are printed.

c) System Design Question#

Your LMS has to send emails to 10,000 students at once after course completion.
How would you design the loop?
Would you directly send all 10k emails in one loop?
Why not?
What approach would you use?

Answer direction:#

  • Directly doing 10k API calls in one loop = server timeout + rate limit hit

  • Use batching in chunks of 100

  • Use a queue system (Bull / BullMQ) so each job runs separately

  • Add rate limiting - for example, max 10 emails/second

  • Add retry logic for failed ones

snippet
// Batching approach
const BATCH_SIZE = 100;
 
for (let i = 0; i < students.length; i += BATCH_SIZE) {
const batch = students.slice(i, i + BATCH_SIZE);
await Promise.all(batch.map(s => sendEmail(s)));
await sleep(1000); // respect rate limit
}