JavaScript Loops -Production + Interview Mastery

Read
JavaScript Loops -Production + Interview Mastery
  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#

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

Three parts:

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

All other loops are just variations of this:

// 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:

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

The JS engine internally does this:

CALL STACK:
┌─────────────────────────┐
│ 1. let i = 0            │  ← INIT — once
│ 2. i < 3? YES → execute │  ← CONDITION CHECK
│ 3. console.log(0)       │  ← BODY
│ 4. i++  → i = 1         │  ← UPDATE
│ 5. i < 3? YES → execute │  ← CONDITION CHECK again
│ 6. console.log(1)       │
│ 7. i++  → i = 2         │
│ 8. i < 3? YES → execute │
│ 9. console.log(2)       │
│ 10. i++ → i = 3         │
│ 11. i < 3? NO → EXIT    │  ← LOOP ENDS
└─────────────────────────┘

let vs var in loops - memory level:#

// 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
// 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#

29 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#

31 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#

28 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#

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

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

const config = { db: 'mongo', port: 3000 };

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

Mistake 3 - Forgetting await in an async loop#

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
// 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#

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
// RIGHT — use filter
const result = arr.filter(n => n % 2 !== 0);
  1. EDGE CASES & GOTCHAS

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

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#

// 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:

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)#

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#

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

Fix 2 - IIFE (old approach)#

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?#

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?#

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

// 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
}

Rahul Verma

Full Stack Developer passionate about building modern web applications. Sharing insights on React, Next.js, and web development.

Learn more about me

Enjoyed this article?

Check out more articles on similar topics in the blog section.

Explore More Articles