সরাসরি পর্বে যান
পর্ব ১২ · ৫৯-এর মধ্যে · মডিউল ২

লুপ — for, while, for…of আর for…in

JavaScript Loops — for, while, do…while, for…of, for…in, break & continue

Taught in Bangla, this lesson covers JavaScript loops: the exact execution order of a for loop, while and do...while, for...of over arrays, strings, Maps and Sets, for...in over keys and why not to use it on arrays, break, continue and labeled break, common traps like infinite loops and off-by-one errors, and the accumulator pattern.

এই পর্বে যা শিখবেন

  • for লুপ ঠিক কোন ক্রমে চলে, আর let কেন প্রতিটি ঘোরায় নতুন ভ্যারিয়েবল দেয়
  • while, sentinel আর do…while — কখন থামবে আগে জানা না থাকলে কোনটি
  • for…of আর for…in-এর আসল পার্থক্য, আর object ঘোরার আধুনিক উপায়
  • break, continue আর labeled break দিয়ে লুপ নিয়ন্ত্রণ
  • অসীম লুপ, off-by-one আর AI-এর লেখা লুপের সাধারণ ভুল ধরার চেকলিস্ট

১ · লুপ কেন — একই কাজ বারবার, শর্ত যতক্ষণ সত্য

আগের পর্বে আপনি if আর switch দিয়ে প্রোগ্রামকে পথ বেছে নিতে শিখিয়েছেন। এবার দ্বিতীয় বড় ক্ষমতা: একই কাজ বারবার করা। একটি অনলাইন দোকানের কার্টে ৩টি পণ্য থাকলে আপনি তিনটি যোগ হাতে লিখতে পারেন। কিন্তু ৩০০টি থাকলে? বা কতগুলো থাকবে আগে জানাই না থাকলে?

এখানেই loop. লুপ হলো এমন একটি গঠন, যা একটি শর্ত যতক্ষণ সত্য থাকে, ততক্ষণ একটি কোড-অংশ (body) বারবার চালায়। প্রতিটি ঘোরাকে বলে একটি iteration. যেকোনো লুপ লেখার আগে তিনটি প্রশ্নের উত্তর জানা চাই:

  1. কোথা থেকে শুরু? — প্রথম পণ্য, প্রথম মাস, ১ নম্বর রোল।
  2. কখন থামবে? — পণ্য শেষ হলে, টাকা লক্ষ্যে পৌঁছালে, ব্যবহারকারী “শেষ” বললে।
  3. প্রতিবার কী বদলাবে? — পরের পণ্যে যাওয়া, মাস এক বাড়ানো। এই বদলই লুপকে থামার দিকে এগিয়ে নেয়।

স্কুলের সকালের রোল কল ভাবুন। শিক্ষক জানেন কোথা থেকে শুরু (রোল ১), কখন থামবেন (শেষ রোল পার হলে), আর প্রতিবার কী বদলায় (পরের রোল)। মাঝে প্রতিটি নামের জন্য একই কাজ — ডাকা, “উপস্থিত” শোনা, খাতায় দাগ দেওয়া। তৃতীয় প্রশ্নটি ভুলে গেলে, অর্থাৎ রোল না বাড়ালে, শিক্ষক সারাদিন “রোল ১, রোল ১…” ডাকতেই থাকবেন। প্রোগ্রামে একেই বলে অসীম লুপ — অংশ ৭-এ দেখবেন এটি browser-এ কেন বিশেষ বিপজ্জনক।

JavaScript-এ লুপের পাঁচটি রূপ আছে। প্রত্যেকটির নিজের জায়গা — আর ভুল জায়গায় ভুলটি ব্যবহার করাই সবচেয়ে চেনা বাগের উৎস:

for

গোনা জানা থাকলে — “১ থেকে ১০”, “প্রতিটি index”. শুরু, শর্ত আর বদল এক লাইনে পাশাপাশি।

নামতা, index ধরে কাজ

while / do…while

কখন থামবে আগে জানা নেই — “লক্ষ্যে না পৌঁছানো পর্যন্ত”. do…while অন্তত একবার চলে।

সঞ্চয়ের লক্ষ্য, মেনু, আবার চেষ্টা

for…of

যেকোনো iterable-এর মান একটি একটি করে — array, string, Map, Set.

ES2015 থেকে · প্রতিদিনের পছন্দ

for…in

একটি object-এর key (property-র নাম) একটি একটি করে — উত্তরাধিকারে পাওয়া key-সহ।

১৯৯৫ থেকে · সাবধানে
মূল ধারণা

সব লুপ আসলে একই জিনিস: একটি শর্ত, একটি body, আর থামার দিকে একটি অগ্রগতি। রূপগুলোর পার্থক্য শুধু এই তিনটি কোথায় লেখা থাকে আর কে সামলায়। for-এ আপনি নিজে তিনটি লেখেন; for…of-এ “পরের মান আছে কি?” শর্ত আর “পরেরটায় যাও” বদল — দুটোই ভাষা নিজে সামলায়। কাজ যত কম আপনার হাতে, ভুলের সুযোগও তত কম।

C/C++/Python/Java জানলে

for, while, do…while, break, continue আর labeled break — Java-র সাথে প্রায় হুবহু এক। Java-র for-each for (int x : arr)-এর JavaScript রূপ for (const x of arr), আর Python-এর for x in items-এর সমতুল্যও for…of — JavaScript-এর for…in নয়, যা key দেয়। Python-এর for-এর else অংশ JavaScript-এ নেই। গভীর তুলনার জন্য দেখুন Java কোর্সের লুপ পর্ব আর Python কোর্সের লুপ পর্ব.

২ · for লুপ — শুরু, শর্ত, বদল আর চলার আসল ক্রম

ক্লাস থ্রিতে পড়া ছোট ভাই মিতুলের জন্য ৭-এর নামতা ছাপাতে চান। for লুপ তিনটি প্রশ্নের উত্তর এক লাইনে লিখতে দেয়:

for (init; condition; update) { body }
  • init — শুরুতে একবারই চলে। সাধারণত এখানে loop variable তৈরি হয়: let i = 1.
  • condition — প্রতিটি ঘোরার আগে যাচাই হয়। truthy হলে body চলে, falsy হলে লুপ শেষ (truthy/falsy পর্ব ০৮-এ দেখেছেন)।
  • update — প্রতিটি ঘোরার শেষে চলে, যেমন i++.

লেখার ক্রম আর চলার ক্রম এক নয় — update লেখা থাকে শর্তের পাশে, কিন্তু চলে body-র পরে। নামতাটি দুই ঘর পর্যন্ত ছোট করে নিচে ধাপে ধাপে দেখুন। “পরে” চাপলে দাগানো লাইনটি এইমাত্র চলেছে:

1const n = 7;2for (let i = 1; i <= 2; i++) {3  console.log(`${n} × ${i} = ${n * i}`);4}5console.log('শেষ');
Trace table (দাগানো লাইনটি এইমাত্র চলেছে)
এখন চলছে
i
i ≤ 2?
  1. লুপের আগে। n তৈরি হলো, মান 7. এটি লুপের বাইরে, তাই লুপ শেষ হলেও থাকবে।
  2. init — একবারই। let i = 1 চলল। পুরো লুপে এই অংশ আর কখনো চলবে না। i জন্ম নিল লুপের নিজের scope-এ।
  3. প্রথম শর্ত যাচাই। 1 <= 2 সত্য, তাই body-তে ঢোকার অনুমতি মিলল। শর্ত body-র আগে যাচাই হয় — শুরুতেই মিথ্যা হলে body একবারও চলত না।
  4. body — প্রথম ঘোরা। Template literal হিসাব করে ছাপল 7 × 1 = 7.
  5. update. body শেষ, এবার i++ — i হলো 2. একটি সূক্ষ্ম কথা: let থাকায় engine update-এর ঠিক আগে i-এর একটি নতুন কপি বানায়, পুরোনো মান সেখানে বসিয়ে তারপর বাড়ায়। তাই প্রতিটি ঘোরার নিজের আলাদা i — নিচে এর গুরুত্ব দেখবেন।
  6. আবার শর্ত। 2 <= 2 — এখনো সত্য, কারণ <= সমানকেও ধরে।
  7. body — দ্বিতীয় ঘোরা। ছাপা হলো 7 × 2 = 14.
  8. update. i হলো 3. লুপ এখনো জানে না যে কাজ শেষ — সেটা জানবে পরের যাচাইয়ে।
  9. শর্ত মিথ্যা — লুপ শেষ। 3 <= 2 মিথ্যা। body আর চলে না; প্রোগ্রাম লুপের পরের লাইনে চলে যায়।
  10. লুপের পরে। i এখন আর নেই — লুপের বাইরে console.log(i) লিখলে ReferenceError: i is not defined. ছাপা হলো শেষ. হিসাব মিলিয়ে নিন: শর্ত যাচাই ৩ বার, body ২ বার — শর্ত সবসময় body-র চেয়ে একবার বেশি যাচাই হয়।

আসল নামতার জন্য শর্তটি শুধু i <= 10 করে দিন — বাকি সব এক। তিনটি অংশের যেকোনোটি খালিও রাখা যায়, শুধু দুটি ; থাকতেই হবে: for (;;) মানে “শর্ত নেই, অর্থাৎ সবসময় সত্য” — ইচ্ছাকৃত অসীম লুপ, যা থেকে বেরোতে ভেতরে break লাগবেই। কমা দিয়ে দুটি ভ্যারিয়েবলও চালানো যায়: for (let lo = 0, hi = 9; lo < hi; lo++, hi--) — দুই প্রান্ত থেকে মাঝের দিকে আসার কাজে।

let, const আর var — loop variable কোথায় বাঁচে

let i দিয়ে তৈরি ভ্যারিয়েবলের block scope শুধু লুপের ভেতরে। একই function-এ পাঁচটি লুপ থাকলে পাঁচটিতেই নিশ্চিন্তে i নাম ব্যবহার করা যায়। আর ওপরের ধাপ ৫-এর “প্রতি ঘোরায় নতুন কপি” নিয়মের কারণে লুপের ভেতরে তৈরি কোনো function পরে চললেও সে নিজের ঘোরার i-টাই দেখে।

var দিয়ে লিখলে পুরো লুপে একটাই i, আর সেটি লুপের বাইরেও বেঁচে থাকে। তাই for (var i = 0; i < 3; i++) fns.push(() => i) লিখে পরে তিনটি function চালালে [ 3, 3, 3 ] আসে, let দিলে [ 0, 1, 2 ] (Node-এ চালিয়ে মিলিয়ে দেখা)। এই বিখ্যাত বাগের পুরো ব্যাখ্যা পর্ব ০৭-এ দেখেছেন, আর তার পেছনের যন্ত্র — closure — আসবে পর্ব ২২-এ।

for (const i = 0; i < 3; i++) লিখবেন না। প্রথম ঘোরা চলবে, তারপর update-এ i++ করতে গিয়েই TypeError: Assignment to constant variable. কিন্তু for (const item of cart) একদম ঠিক — for…of প্রতিটি ঘোরায় নতুন item তৈরি করে, কেউ তাকে বদলায় না। সহজ নিয়ম: গোনার for-এ let, for…of / for…in-এ const.

৩ · while আর do…while — কখন থামবে আগে জানা নেই

সুমাইয়া একটি ল্যাপটপ কেনার জন্য প্রতি মাসে ১,৫০০ টাকা জমাবেন, লক্ষ্য ১০,০০০ টাকা। কত মাস লাগবে? এখানে আমরা ঘোরার সংখ্যা আগে জানি না — সেটাই তো প্রশ্ন। জানি শুধু থামার শর্ত। এমন জায়গায় while:

const goal = 10000;
const perMonth = 1500;
let saved = 0;
let month = 0;

while (saved < goal) {      // শর্ত আগে, তারপর body
  saved += perMonth;
  month++;
}
console.log(`${month} মাসে জমবে ${saved} টাকা`);
// প্রত্যাশিত আউটপুট: 7 মাসে জমবে 10500 টাকা

while-এ শুধু শর্ত লেখা থাকে। শুরু (init) থাকে লুপের আগে, আর বদল (update) থাকে body-র ভেতরে — আপনার দায়িত্বে। body থেকে saved += perMonth বাদ দিলে saved চিরকাল 0, শর্ত চিরকাল সত্য — অসীম লুপ। প্রতিটি while লেখার পর নিজেকে জিজ্ঞেস করুন: “body-র কোন লাইনটি শর্তকে মিথ্যার দিকে নিচ্ছে?”

Sentinel — “থামো” বোঝানোর বিশেষ মান

একটি দোকানের ক্যাশিয়ার একের পর এক দাম ঢোকান; দিনের শেষে 0 ঢোকালে বোঝা যায় “আর নেই”। এমন বিশেষ মানকে বলে sentinel — পাহারাদার। কতগুলো দাম আসবে আগে জানা নেই, শুধু জানি শেষটা কেমন দেখতে:

const entries = [250, 120, 80, 0, 999];   // 0 = দিনের শেষ
let i = 0;
let sum = 0;
while (entries[i] !== 0) {
  sum += entries[i];
  i++;
}
console.log(sum);   // প্রত্যাশিত আউটপুট: 450 — 999 কখনো পড়াই হয়নি

Sentinel-এর একটি ঝুঁকি আছে: তালিকায় যদি কখনো 0 না থাকে? i বাড়তে বাড়তে array-র বাইরে যাবে, entries[i] হবে undefined, আর undefined !== 0 সবসময় সত্য — অসীম লুপ। নিরাপদ রূপ: while (i < entries.length && entries[i] !== 0). বাস্তবে sentinel-এর পরিচিত উদাহরণ হলো C-র string-এর শেষে '\0', বা ফাইল পড়ার সময় “আর কিছু নেই” সংকেত।

do…while — আগে কাজ, পরে প্রশ্ন

do…while-এ শর্ত যাচাই হয় body-র পরে। তাই body অন্তত একবার চলবেই। কোথায় কাজে লাগে? যেখানে প্রশ্ন করার আগে একবার কাজটি করতেই হবে — একটি মেনু দেখানো, বা নেটওয়ার্ক কল একবার চেষ্টা করে দেখা সফল হলো কি না:

let attempt = 0;
let ok;
do {
  attempt++;
  ok = trySendSms(attempt);       // কাল্পনিক function: সফল হলে true
} while (!ok && attempt < 3);     // ব্যর্থ হলে আবার — সর্বোচ্চ ৩ বার
console.log(ok ? `${attempt} বারে পাঠানো গেল` : 'পাঠানো গেল না');

লক্ষ করুন attempt < 3 — আবার চেষ্টার লুপে সবসময় একটি সর্বোচ্চ সীমা রাখুন। নইলে সার্ভার বন্ধ থাকলে লুপ কখনো থামবে না। আর while (…);-এর শেষে সেমিকোলন do…while-এর ব্যাকরণের অংশ। সত্যিকারের নেটওয়ার্ক কল async হয় — সেখানে আবার চেষ্টার নকশা (retry, backoff) পর্ব ৩৬-এ।

পরিস্থিতিলুপশর্ত কখন যাচাই
ঘোরার সংখ্যা জানা (১ থেকে ১০, প্রতিটি index)forপ্রতিটি ঘোরার আগে
থামার শর্ত জানা, সংখ্যা জানা নেইwhileপ্রতিটি ঘোরার আগে — ০ বারও চলতে পারে
অন্তত একবার চালাতেই হবেdo…whileপ্রতিটি ঘোরার পরে — কমপক্ষে ১ বার
একটি তালিকার প্রতিটি মানfor…ofভাষা নিজে: “পরের মান আছে?”

৪ · for…of — মানের ওপর হাঁটা: array, string, Map আর Set

বাস্তব কোডের বেশিরভাগ লুপের কাজ একটাই: “এই তালিকার প্রতিটি জিনিস নিয়ে কিছু করো”। সেখানে index (i), শর্ত (i < arr.length) আর বদল (i++) — তিনটিই আসলে বাড়তি ঝামেলা, আর তিনটিতেই ভুলের সুযোগ। for…of এগুলো সরিয়ে দেয়:

const prices = [320, 145, 199];
for (const price of prices) {
  console.log(price);      // 320, তারপর 145, তারপর 199
}

এখানে কোনো index নেই, তাই index-এর ভুলও নেই। Index সত্যিই দরকার হলে (যেমন “১. চাল, ২. ডাল” ছাপাতে) লিখুন for (const [i, item] of items.entries()) — array-র entries() জোড়া দেয় [index, মান], আর [i, item] সেটি দুই ভাগে খুলে নেয় (destructuring, পর্ব ১৭)।

কে কে “iterable”?

for…of শুধু array-র জন্য নয়। যে জিনিস একটি নির্দিষ্ট চুক্তি মানে — “আমাকে জিজ্ঞেস করলে আমি একটি একটি করে মান দেব” — সেটিই iterable. এই চুক্তির নাম iteration protocol: জিনিসটির [Symbol.iterator]() নামে একটি method থাকে, যা একটি iterator ফেরত দেয়; iterator-এর next() প্রতিবার দেয় { value, done }. for…of ভেতরে ভেতরে ঠিক এটাই করে — done: true না আসা পর্যন্ত next() ডাকে। নিজের iterable আর generator বানানো দেখবেন পর্ব ২৭-এ।

কিসের ওপরপ্রতি ঘোরায় যা পাবেনমনে রাখুন
Arrayপ্রতিটি মান, ক্রম অনুযায়ীবাড়তি property আসে না
Stringপ্রতিটি code pointemoji ভাঙে না, কিন্তু বাংলা যুক্তাক্ষর/কার আলাদা হয়
Map[key, value] জোড়া, যোগ করার ক্রমেfor (const [k, v] of map) — পর্ব ১৮
Setপ্রতিটি অনন্য মান, যোগ করার ক্রমেপুনরাবৃত্তি নেই — পর্ব ১৮
সাধারণ object {}— চলে নাTypeError: obj is not iterable — অংশ ৫-এর উপায় দেখুন

বাংলা শব্দের ওপর হাঁটা — code point আর “অক্ষর”-এর পার্থক্য

পর্ব ০৯-এ দেখেছেন, JavaScript-এর string ভেতরে UTF-16 code unit-এর সারি, আর .length গোনে code unit. পুরোনো ধাঁচের for (let i = 0; i < s.length; i++) s[i] তাই একটি emoji-কে দুই টুকরো করে ফেলে — দুটি অর্থহীন অর্ধেক। for…of string-কে হাঁটে code point ধরে, তাই 👍 একটি গোটা মান হিসেবেই আসে ([...'👍'].length হলো 1, কিন্তু '👍'.length হলো 2).

কিন্তু code point মানেই পাঠকের চোখের “একটি অক্ষর” নয়। 'বাংলা'-র ওপর for…of চালালে আসে পাঁচটি মান: ব, া, ং, ল, া — আকার-চিহ্ন (া) আলাদা হয়ে যায়, যদিও আমরা “বা”-কে একটি অক্ষর হিসেবে পড়ি। যুক্তাক্ষরে (যেমন “ক্ষ” = ক + ্ + ষ) আরও বেশি টুকরো। চোখের অক্ষর ধরে গুনতে চাইলে লাগে Intl.Segmenter — পর্ব ০৯-এ এর বিস্তারিত দেখেছেন। নিয়ম: emoji বা বাংলা থাকলে index-এর লুপ নয়, অন্তত for…of; আর “অক্ষর” গোনা জরুরি হলে Segmenter.

৫ · for…in — key-র ওপর হাঁটা, আর কেন array-তে কখনো নয়

for…in JavaScript-এর প্রথম সংস্করণ (১৯৯৫) থেকেই আছে। এটি একটি object-এর key — property-র নাম — একটি একটি করে দেয়, string হিসেবে। কোন key আসবে? যেসব property enumerable (গোনার যোগ্য বলে চিহ্নিত), সেগুলো — object-এর নিজের তো বটেই, prototype chain থেকে উত্তরাধিকারে পাওয়া enumerable property-ও। Object নিয়ে বিস্তারিত পর্ব ১৪-এ, prototype পর্ব ২৫-এ।

নামের মিল দেখে for…of আর for…in গুলিয়ে ফেলা JavaScript-এর সবচেয়ে চেনা বাগগুলোর একটি — বিশেষ করে যাঁরা Python থেকে আসেন, যেখানে for x in list মান দেয়। একই array-র ওপর দুটো চালিয়ে পার্থক্যটা নিজের চোখে দেখুন:

letters.jsএকটি array, সাথে একটি বাড়তি property
const letters = ['ক', 'খ'];
letters.extra = 'গ';

// ভেতরে আসলে এমন একটি object:
//   "0"     → 'ক'
//   "1"     → 'খ'
//   "extra" → 'গ'      (enumerable)
//   length  → 2        (enumerable নয়)
for…ofiterator-কে জিজ্ঞেস করে — মান
for (const v of letters) console.log(v);

ক
খ
// 'গ' আসেনি: array-র iterator শুধু 0..length-1 দেয়
for…inproperty-র নাম গোনে — key
for (const k in letters) console.log(k);

0
1
extra
// বাড়তি property-ও চলে এলো
ফাঁদ ১key-গুলো number নয়, string
for (const k in letters) console.log(k + 1);

01
11
extra1
// typeof k === 'string' → + জোড়া দিল
ফাঁদ ২উত্তরাধিকারের property
// কোনো পুরোনো লাইব্রেরি সব array-তে method যোগ করল:
Array.prototype.shuffle = function () { /* … */ };

for (const k in ['ক']) console.log(k);
0
shuffle      // ← আপনার array-তে এটা কোথা থেকে?
সমাধানযার যে কাজ
// array-র মান:           for (const v of letters)
// array-র index + মান:   for (const [i, v] of letters.entries())
// object-এর key + মান:   for (const [k, v] of Object.entries(obj))
// for…in লাগলেই:         if (Object.hasOwn(obj, k)) { … }
  1. Array আসলে একটি object. Index-গুলো ভেতরে "0", "1" নামের property — নাম string. JavaScript-এ array-তে বাড়তি property বসানোও বৈধ (letters.extra). length-ও একটি property, কিন্তু enumerable নয়, তাই for…in-এ আসে না।
  2. for…of — মান। এটি array-র iterator-কে জিজ্ঞেস করে, আর array-র iterator শুধু 0 থেকে length−1 পর্যন্ত মান দেয়। বাড়তি property তার হিসাবেই নেই। এটিই আপনি প্রায় সবসময় চান।
  3. for…in — key. এটি iterator ব্যবহার করে না; object-এর enumerable property-র নাম গোনে। তাই index-গুলো আসে, আর extra-ও চলে আসে। মান চাইলে আবার letters[k] লিখতে হয়।
  4. ফাঁদ ১ — string index. k দেখতে 0, 1 হলেও আসলে "0", "1". তাই k + 1 যোগ নয়, জোড়া দেয় — "01". পরের index বের করতে গিয়ে এমন ভুল নিঃশব্দে ভুল ফল দেয়, কোনো error ছাড়াই।
  5. ফাঁদ ২ — উত্তরাধিকার। for…in prototype chain ধরে ওপরের enumerable property-ও দেয়। ২০০০-এর দশকে কিছু জনপ্রিয় লাইব্রেরি (যেমন Prototype.js) Array.prototype-এ নিজের method যোগ করত — ফলে সারা পৃথিবীর for…in লুপে হঠাৎ অচেনা key দেখা দিত। “Array-তে for…in নয়” নিয়মটি সেই অভিজ্ঞতা থেকেই।
  6. যার যে কাজ। মান চাইলে for…of; index-সহ চাইলে entries(); সাধারণ object-এর জন্য Object.entries() + for…of — যা শুধু নিজের enumerable string key দেয়। কোনো কারণে for…in রাখতেই হলে ভেতরে Object.hasOwn(obj, k) দিয়ে নিজের property ছেঁকে নিন (ES2022-এ যুক্ত; পুরোনো কোডে obj.hasOwnProperty(k) দেখবেন)।

এবার নিজে চালিয়ে দেখুন — একই array-র ওপর দুই লুপ, আর একটি বাংলা শব্দের ওপর for…of:

const letters = ['ক', 'খ'];
letters.extra = 'গ';            // array-তে একটি বাড়তি property

for (const value of letters) console.log('of →', value);
for (const key in letters) console.log('in →', key, typeof key);

const word = 'বাংলা';
let count = 0;
for (const ch of word) {
  console.log(count, ch);
  count++;
}
console.log('code point:', count, '| length:', word.length);
console.log('👍 →', [...'👍'].length, 'বনাম', '👍'.length);

প্রথমে of → ক আর of → খ — শুধু মান। তারপর in → 0 string, in → 1 string, in → extra string — key, তিনটিই string, বাড়তি property-সহ। এরপর বাংলা-র পাঁচটি code point: 0 ব, 1 া, 2 ং, 3 ল, 4 া — আকার-চিহ্ন আলাদা ঘোরায়। বাংলার এই অক্ষরগুলো সব BMP-র ভেতরে, তাই code point আর length দুটোই 5: code point: 5 | length: 5. শেষ লাইনে পার্থক্যটা ধরা পড়ে: 👍 → 1 বনাম 2 — emoji-টি একটি code point, কিন্তু দুটি UTF-16 code unit. একটি পরীক্ষা: word-কে 'মা👍' করে চালান — for…of ৩ বার ঘুরবে, অথচ length দেবে 4.

Object ঘোরার আধুনিক উপায় — Object.keys, values, entries

ধরুন একটি কোম্পানির জেলাভিত্তিক বিক্রি একটি object-এ: { 'ঢাকা': 52000, 'চট্টগ্রাম': 31000 }. এর ওপর ঘুরতে তিনটি সহায়ক function আছে, তিনটিই একটি array ফেরত দেয় — আর array-র ওপর তো for…of চলেই:

ডাকলেনফেরত পেলেনকখন
Object.keys(sales)[ 'ঢাকা', 'চট্টগ্রাম' ]শুধু নাম দরকার (ES5, ২০০৯)
Object.values(sales)[ 52000, 31000 ]শুধু মান — যেমন মোট যোগফল (ES2017)
Object.entries(sales)[ [ 'ঢাকা', 52000 ], … ]নাম আর মান দুটোই (ES2017)

তিনটিই শুধু object-এর নিজের enumerable property দেয় — উত্তরাধিকারের কিছু নয়। তাই hasOwn-এর পাহারা লাগে না, key-গুলো আলাদা array-তে থাকায় দরকার হলে সাজানো বা ছাঁকাও যায়। আধুনিক কোডে object ঘোরার ডিফল্ট উপায় এটাই: for (const [district, amount] of Object.entries(sales)). অংশ ৮-এ এটি একটি চালানো যায় এমন উদাহরণে দেখবেন।

Key-র ক্রম কি নিশ্চিত? ES2015 থেকে মানদণ্ড object-এর নিজের property-র একটি ক্রম নির্দিষ্ট করেছে, আর ES2020-এ সাধারণ ক্ষেত্রে for…in-কেও সেই ক্রম মানতে বলা হয়েছে: আগে integer-এর মতো দেখতে key ছোট থেকে বড়, তারপর বাকি string key যোগ করার ক্রমে। তাই { b: 1, 2: 'x', a: 2, 1: 'y' }-এর key আসে 1, 2, b, a ক্রমে (Node-এ চালিয়ে দেখা)। কিন্তু ক্রমের ওপর ব্যবসায়িক যুক্তি দাঁড় করাবেন না — নির্দিষ্ট ক্রম লাগলে array বা Map ব্যবহার করুন।

৬ · break, continue আর labeled break — লুপের লাগাম

কখনো কাজ শেষ হয়ে যায় লুপ শেষ হওয়ার আগেই। গুদামের তালিকায় প্রথম যে পণ্যের স্টক শূন্য, সেটি পেলেই হলো — বাকি ৫০০টি দেখার দরকার নেই। break সঙ্গে সঙ্গে সবচেয়ে ভেতরের লুপ (বা switch) থেকে বের করে দেয়।

আবার কখনো শুধু একটি ঘোরা বাদ দিতে চান — বাতিল হওয়া অর্ডারগুলো হিসাবে না ধরে বাকিগুলো যোগ করা। continue বর্তমান ঘোরার বাকি অংশ ফেলে সোজা পরের ঘোরায় যায়। for লুপে continue-এর পর update (i++) ঠিকই চলে; কিন্তু while-এ update যদি body-র শেষে থাকে, continue তাকে টপকে যায় — আর লুপ একই জায়গায় আটকে অসীম হয়ে যায়। এটি একটি চেনা ফাঁদ।

const orders = [
  { id: 101, amount: 500, cancelled: false },
  { id: 102, amount: 900, cancelled: true },
  { id: 103, amount: 300, cancelled: false },
];
let revenue = 0;
for (const order of orders) {
  if (order.cancelled) continue;    // এই ঘোরার বাকিটা বাদ
  revenue += order.amount;
}
console.log(revenue);   // প্রত্যাশিত আউটপুট: 800

continue-এর বদলে if (!order.cancelled) { … } লিখলেও একই ফল। লম্বা body-তে শুরুতেই “বাদ দেওয়ার শর্তগুলো” continue দিয়ে সেরে ফেলা — একে বলে guard clause — কোডকে কম ইন্ডেন্টে, বেশি পরিষ্কার রাখে।

লুপের ভেতরে লুপ — আর label দিয়ে একসাথে বেরোনো

একটি দোকানের তাক সারি আর কলামে সাজানো — একটি array-র ভেতরে আরও array. “ডাল” কোন তাকে আছে, খুঁজতে দুটি লুপ লাগে: বাইরেরটি সারি ধরে, ভেতরেরটি কলাম ধরে। সমস্যা হলো, সাধারণ break শুধু ভেতরের লুপ থামায় — বাইরের লুপ পরের সারিতে খোঁজা চালিয়েই যায়। সমাধান: বাইরের লুপের সামনে একটি label (নাম আর কোলন), আর break label:

1const shelves = [['চাল', 'তেল'], ['ডাল', 'চিনি'], ['লবণ', 'আটা']];2let found = null;3search: for (let r = 0; r < shelves.length; r++) {4  for (let c = 0; c < shelves[r].length; c++) {5    if (shelves[r][c] === 'ডাল') { found = [r, c]; break search; }6  }7}8console.log(found);
Trace table
r
c
দেখছি
found
  1. প্রস্তুতি। তিনটি তাক, প্রতিটিতে দুটি পণ্য। found শুরুতে null — “এখনো পাইনি”। বাইরের লুপের সামনে search: — এটিই label; এটি লুপের একটি নাম মাত্র, কোনো ভ্যারিয়েবল নয়।
  2. সারি 0, কলাম 0. বাইরের লুপ r = 0, ভেতরের লুপ c = 0. 'চাল' — মিলল না।
  3. সারি 0, কলাম 1. ভেতরের লুপ এগোল। 'তেল' — মিলল না।
  4. ভেতরের লুপ শেষ, বাইরের লুপ এগোল। c হলো 2, 2 < 2 মিথ্যা — ভেতরের লুপ শেষ। বাইরের লুপের update: r হলো 1. ভেতরের লুপ আবার শুরু থেকে — নতুন c = 0.
  5. সারি 1, কলাম 0 — পাওয়া গেল! 'ডাল' মিলল। found = [1, 0]. এবার break search চলবে।
  6. এক লাফে দুই লুপের বাইরে। break search মানে “search নামের লুপটি থামাও” — সেটি বাইরের লুপ, তাই ভেতরেরটিও সাথে সাথে শেষ। 'চিনি' আর পুরো তৃতীয় তাক কখনো দেখাই হলো না। শুধু break লিখলে ভেতরের লুপ থামত, কিন্তু বাইরের লুপ r = 2-তে গিয়ে 'লবণ', 'আটা' অকারণে দেখত — বড় ডেটায় অনেক অপচয়, আর একাধিক মিল থাকলে found পরেরটা দিয়ে মুছেও যেত।
  7. ফল। ছাপা হলো [ 1, 0 ] — দ্বিতীয় তাকের প্রথম ঘর (index 0 থেকে গোনা)।

একইভাবে continue search লিখলে ভেতরের লুপের বাকিটা বাদ দিয়ে বাইরের লুপের পরের ঘোরায় যাওয়া যায়। তবে label সচরাচর লাগে না, আর বেশি label কোড পড়া কঠিন করে। পরিষ্কার বিকল্প প্রায়ই আছে: খোঁজার অংশটি একটি function-এ রেখে পাওয়ামাত্র return (function পরের পর্বে)।

switch-এর ভেতরে break লুপ থামায় না। লুপের ভেতরে একটি switch থাকলে সেখানকার break শুধু switch থেকে বেরোয় — লুপ চলতেই থাকে। লুপ থামাতে চাইলে লুপে label দিয়ে break loopName লিখুন, বা switch-এর বদলে if ব্যবহার করুন। পর্ব ১১-এর switch আর এই পর্বের লুপ যেখানে মেলে, সেখানে এই ভুল খুব সাধারণ।

৭ · লুপের তিন চেনা ফাঁদ — অসীম লুপ, off-by-one আর ঘোরার মাঝে array বদলানো

ফাঁদ ১ — অসীম লুপ আর জমে যাওয়া পাতা

যে লুপের শর্ত কখনো মিথ্যা হয় না, সেটি infinite loop. C বা Java-তে এটি “প্রোগ্রাম আটকে গেছে” — terminal-এ Ctrl+C চাপলেই মুক্তি। Browser-এ এর ফল আরও নাটকীয়। পর্ব ০৫-এ দেখেছেন, একটি পাতার JavaScript চলে একটিমাত্র thread-এ (single-threaded) — আর সেই একই main thread বোতামের ক্লিক সামলায়, পাতা আঁকে, স্ক্রল করে। আপনার লুপ যতক্ষণ চলছে, event loop পরের কোনো কাজে হাত দিতে পারে না:

// ⚠ চালাবেন না — পাতা পুরোপুরি জমে যাবে
let stock = 5;
while (stock > 0) {
  console.log('বিক্রি হলো');
  // stock-- ভুলে গেছি → শর্ত চিরকাল সত্য
}

এই কোড চললে বোতাম চাপলে সাড়া নেই, স্ক্রল হয় না, কিছুক্ষণ পর Chrome বা Edge “Page Unresponsive” বলে পাতা বন্ধ করার প্রস্তাব দেয়। এই পর্বের runner-এ এমন কোড চালাবেন না — runner-ও একই পাতার ভেতরে চলে। অসীম লুপ নিজেই সবসময় ভুল নয়: সার্ভার বা গেমের মূল লুপ ইচ্ছা করেই অসীম। কিন্তু তাদের প্রতিটির ভেতরে বেরোনোর পথ (break/return) থাকে, আর browser-এ এমন দীর্ঘ কাজ চলে await-এর ফাঁকে ফাঁকে বা আলাদা Web Worker-এ (পর্ব ৪২) — main thread আটকে নয়।

মূল ধারণা

Browser-এ একটি লুপ শুধু “ধীর” হয় না — চলার পুরো সময়টা পুরো পাতাকে জিম্মি রাখে। Browser-এর পরিভাষায় ৫০ মিলিসেকেন্ডের বেশি সময় main thread আটকে রাখা কাজকে বলে long task. লাখো উপাদানের ওপর ভারী লুপ চালাতে হলে কাজটি টুকরো করুন, বা Worker-এ পাঠান। এ নিয়ে ভাবনার প্রশ্ন ৪-এ আরও গভীরে যাব।

ফাঁদ ২ — Off-by-one: একটি বেশি বা একটি কম

৫টি জিনিসের array-র index 0 থেকে 4. শর্তে <-এর জায়গায় <= লিখলেই একটি বাড়তি ঘোরা, যেখানে arr[5] হলো undefined:

const marks = [78, 85, 62];
let sum = 0;
for (let i = 0; i <= marks.length; i++) sum += marks[i];
console.log(sum);   // NaN — শেষ ঘোরায় 225 + undefined

এই ভুলের নাম off-by-one error. JavaScript এখানে কোনো error দেয় না — C-র মতো মেমরি নষ্ট করে না, Java-র মতো exception ছোড়ে না; চুপচাপ undefined দেয়, আর হিসাব হয়ে যায় NaN. প্রতিরোধের তিনটি অভ্যাস: (১) index লাগলে প্রায় সবসময় i = 0; i < arr.length — “শূন্য থেকে, length-এর কম”; (২) সম্ভব হলে index-ই বাদ — for…of; (৩) সীমানার মানগুলো হাতে যাচাই — খালি array, এক উপাদানের array, আর শেষ উপাদানে কী হয়।

ফাঁদ ৩ — ঘোরার মাঝে array বদলানো

একটি তালিকা থেকে সব 2 মুছতে চান। স্বাভাবিক মনে হয় এভাবে লেখা:

const a = [1, 2, 2, 3];
for (let i = 0; i < a.length; i++) {
  if (a[i] === 2) a.splice(i, 1);   // index i-তে একটি মুছে ফেলো
}
console.log(a);   // [ 1, 2, 3 ] — একটি 2 রয়ে গেল!

কেন? i = 1-এ প্রথম 2 মুছতেই পরের সবাই এক ঘর বাঁয়ে সরে এল — দ্বিতীয় 2 এখন index 1-এ। কিন্তু লুপ ততক্ষণে i++ করে index 2-তে চলে গেছে, তাই সরে আসা 2-টিকে আর দেখা হলো না (Node-এ চালিয়ে দেখা ফল)। for…of-এও একই সমস্যা, কারণ array-র iterator-ও ভেতরে একটি index ধরে এগোয়; আর ঘোরার মাঝে push করলে for…of নতুন উপাদানগুলোও দেখে — প্রতি ঘোরায় push করলে লুপ কখনো শেষই হবে না। নিরাপদ উপায়: (ক) নতুন array বানানো — a.filter(x => x !== 2) (পর্ব ১৬); (খ) পেছন থেকে ঘোরা — for (let i = a.length - 1; i >= 0; i--), যাতে মুছলে যে উপাদানগুলো সরে, সেগুলো আগেই দেখা হয়ে গেছে।

৮ · Accumulator প্যাটার্ন, বাস্তব উদাহরণ আর লুপ বনাম array method

লুপের সবচেয়ে প্রচলিত ব্যবহার একটি ছকে পড়ে: লুপের আগে একটি ভ্যারিয়েবল শুরু-মান নিয়ে তৈরি হয় (যোগফলের জন্য 0, লেখার জন্য '', তালিকার জন্য []), প্রতিটি ঘোরায় তাতে কিছু যোগ হয়, আর লুপের পরে সেটিই ফল। একে বলে accumulator প্যাটার্ন — হিসাবের খাতার মতো, প্রতিটি লেনদেনে শুধু চলতি মোট আপডেট। নিচের উদাহরণে তিনটি বাস্তব কাজ — কার্টের মোট, প্রথম স্টক-শেষ পণ্য খুঁজে break, আর জেলাভিত্তিক বিক্রি Object.entries দিয়ে পড়া:

const cart = [
  { name: 'চাল', price: 320, stock: 12 },
  { name: 'ডাল', price: 145, stock: 0 },
  { name: 'তেল', price: 199, stock: 5 },
];
let total = 0;                          // accumulator
for (const item of cart) total += item.price;
console.log('কার্টের মোট:', total);

let firstOut = null;
for (const item of cart) {
  if (item.stock === 0) { firstOut = item.name; break; }
}
console.log('প্রথম স্টক-শেষ পণ্য:', firstOut);

const sales = { 'ঢাকা': 52000, 'চট্টগ্রাম': 31000, 'রাজশাহী': 18500 };
let report = '';
for (const [district, amount] of Object.entries(sales)) {
  report += `${district}=${amount} `;
}
console.log(report.trim());

আউটপুট তিন লাইন: কার্টের মোট: 664 (320 + 145 + 199), প্রথম স্টক-শেষ পণ্য: ডাল, আর ঢাকা=52000 চট্টগ্রাম=31000 রাজশাহী=18500. দ্বিতীয় লুপে “ডাল” পাওয়ামাত্র break — “তেল” আর দেখাই হয়নি। তৃতীয় লুপে Object.entries প্রতিটি জোড়া দেয় ['ঢাকা', 52000] রূপে, আর [district, amount] সেটি দুই নামে খুলে নেয়; report একটি string accumulator, শেষের বাড়তি ফাঁকা trim() দিয়ে কাটা। পরীক্ষা করুন: “ডাল”-এর stock 3 করে দিন — firstOut থাকবে null, কারণ কোনো পণ্যই শেষ হয়নি। “পাওয়া যায়নি” অবস্থাটি সামলানো আপনার দায়িত্ব।

লুপ নাকি array method — forEach, map, find

পর্ব ১৬-এ দেখবেন, array-র নিজের method দিয়ে অনেক লুপ এক লাইনে লেখা যায়: যোগফল reduce, ছাঁকা filter, রূপান্তর map, আর “প্রথম যেটি মেলে” — cart.find(item => item.stock === 0). এগুলো উদ্দেশ্য সরাসরি বলে দেয়, তাই পড়তে সহজ। তবে একটি বড় পার্থক্য মনে রাখুন:

forEach থেকে break করা যায় না। forEach-কে আপনি একটি function দেন, আর forEach নিজে সেটিকে প্রতিটি উপাদানের জন্য ডাকে। ভেতরে break লিখলে SyntaxError — কারণ function-এর ভেতরে কোনো লুপ নেই। ভেতরে return লিখলে শুধু সেই একটি ডাক শেষ হয় — continue-এর মতো আচরণ, বাকি উপাদানগুলো চলতেই থাকে। আগেভাগে থামা দরকার হলে for…of + break, বা find / some / every — যারা উত্তর পাওয়ামাত্র নিজেরাই থামে।

কাজলুপেMethod-এ (পর্ব ১৬)আগেভাগে থামে?
প্রতিটির জন্য কিছু করাfor…offorEachলুপ: break দিয়ে; forEach: না
প্রথম মিল খোঁজাfor…of + breakfind, findIndexহ্যাঁ
“কোনো একটি কি…?” / “সবগুলো কি…?”flag + breaksome / everyহ্যাঁ
যোগফল / একটি মান বানানোaccumulatorreduceনা
await করে একটি একটি করেfor…of + await— (forEach অপেক্ষা করে না)লুপ: হ্যাঁ

এক ঝলক সামনে — for await…of

কিছু ডেটা একবারে আসে না, টুকরো টুকরো আসে — নেটওয়ার্ক থেকে আসা ফাইল, বা একটি API-র পাতার পর পাতা। এদের ওপর ঘোরার জন্য ES2018-এ যুক্ত হয়েছে for await (const chunk of stream) — প্রতিটি মানের জন্য অপেক্ষা করে, তারপর body চালায়। Promise আর async/await না জেনে এর মানে পুরো বোঝা যাবে না, তাই বিস্তারিত পর্ব ৩৪-এ। এখন শুধু মনে রাখুন: async কাজ একটির পর একটি করতে forEach নয়, লুপ।

লুপের গতি — মাইক্রো-বেঞ্চমার্ক নয়, অভ্যাস

ইন্টারনেটে অনেক পুরোনো পরামর্শ পাবেন — “arr.length একটি ভ্যারিয়েবলে রেখে দিন”, “উল্টো দিকে ঘোরান, দ্রুত হয়”। আধুনিক engine (V8, SpiderMonkey, JavaScriptCore) সরল লুপ নিজেই চিনে নেয়: বারবার চলা কোড JIT compiler machine code-এ রূপান্তর করে, অপ্রয়োজনীয় যাচাই সরিয়ে দেয়। একটি সাধারণ for, for…of আর forEach-এর পার্থক্য প্রায় সব বাস্তব প্রোগ্রামে নগণ্য। গতির আসল পার্থক্য আসে অ্যালগরিদম থেকে — লুপের ভেতরে লুপ (১,০০০ × ১,০০০ = ১০ লাখ ঘোরা), বা প্রতি ঘোরায় পাতার DOM ছোঁয়া। মাপতে চাইলে DevTools-এর Performance প্যানেল (পর্ব ৫৩) — অনুমান নয়।

পরীক্ষা ও ইন্টারভিউতে
  • for…of আর for…in-এর পার্থক্য? — for…of iterable-এর মান দেয় (iteration protocol ধরে); for…in object-এর enumerable key দেয়, string হিসেবে, উত্তরাধিকারের key-সহ।
  • while আর do…while-এর পার্থক্য? — while শর্ত আগে যাচাই করে, ০ বারও চলতে পারে; do…while পরে যাচাই করে, অন্তত ১ বার চলে।
  • forEach থেকে বেরোনো যায় কীভাবে? — break করা যায় না; for…of + break, বা some / every / find ব্যবহার করুন।
  • for (var i…)-এ setTimeout দিয়ে i ছাপালে কেন সব একই সংখ্যা? — var-এ পুরো লুপে একটিই i; let প্রতি ঘোরায় নতুন বাঁধন দেয়।
  • নেস্টেড লুপ থেকে একবারে বেরোবেন কীভাবে? — বাইরের লুপে label দিয়ে break label, বা খোঁজাটি function-এ রেখে return.

৯ · AI-এর যুগে — AI-এর লেখা লুপ যাচাই

“একটা array-র সব দাম যোগ করার কোড দাও” — AI মুহূর্তে লুপ লিখে দেবে, আর সাধারণ পরীক্ষায় সেটি চলবেও। কিন্তু লুপের ভুল প্রায়ই সীমানায় লুকিয়ে থাকে — খালি array, শেষ উপাদান, বা যে ডেটায় সন্ধান মেলে না। পুরোনো টিউটোরিয়াল আর নানা ভাষার কোড থেকে শেখা AI-এর চেনা ভুলগুলো:

AI যা প্রায়ই দেয়সমস্যাযা চাইবেন
for (const i in arr) দিয়ে array ঘোরা (Python-এর অভ্যাস)string index, বাড়তি/উত্তরাধিকারের keyfor…of বা entries()
আগেভাগে থামার কাজে forEach + returnথামে না; বাকিগুলো চলতেই থাকেfor…of + break, বা find/some
i <= arr.lengthoff-by-one → undefined, NaNi < arr.length, বা index-ই বাদ
বেরোনোর পথ ছাড়া while (true), বা সীমাহীন retryপাতা জমে যায়, সার্ভারে অবিরাম অনুরোধস্পষ্ট break শর্ত + সর্বোচ্চ চেষ্টার সীমা
লুপের ভেতরে splice দিয়ে মোছাউপাদান বাদ পড়েfilter, বা পেছন থেকে ঘোরা
async কাজে forEach(async …)অপেক্ষা করে না, ক্রম ও ভুল-সামলানো হারায়for…of + await (পর্ব ৩৪)

AI-কে লুপ লিখতে বললে শর্তগুলো আগেই বলে দিন, আর সীমানার ক্ষেত্রগুলো নিজেই পরীক্ষা করতে বলুন। নিচের prompt কপি করে নিজের কাজে বদলে নিন:

আমি JavaScript শিখছি। একটি অনলাইন দোকানের কার্ট আছে: পণ্যের array, প্রতিটিতে name, price, stock. তিনটি কাজের কোড লিখে দাও: (১) সব দামের যোগফল, (২) প্রথম যে পণ্যের stock 0 সেটি খুঁজে পাওয়ামাত্র থামো, (৩) জেলাভিত্তিক বিক্রির একটি object ঘুরে প্রতিটি জেলা আর টাকা দেখাও। শর্ত: ১. array-তে for…in নয় — for…of বা array method. ২. আগেভাগে থামার জায়গায় forEach নয়। ৩. index লাগলে i < length; কোনো while (true) থাকলে স্পষ্ট বেরোনোর শর্ত দেখাও। ৪. object ঘুরতে Object.entries. ৫. let/const — var নয়। প্রতিটি লুপের পাশে সহজ বাংলায় লেখো: শুরু কোথায়, কখন থামবে, প্রতিবার কী বদলায়। শেষে বলো খালি কার্ট আর কোনো পণ্য স্টক-শেষ না হলে প্রতিটি কাজ কী ফল দেবে।

AI-এর লুপ হাতে পেলে — যাচাইয়ের চেকলিস্ট

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলোই আপনাকে কোড-লেখক থেকে সিস্টেম-চিন্তক বানাবে।

প্র ০১১৯৯৫ সালে for…in থাকতেও ২০১৫ সালে আলাদা করে for…of আনতে হলো কেন? পুরোনোটিকেই “ঠিক” করে দেওয়া গেল না কেন?

১৯৯৫ সালে JavaScript-এর জগৎ ছিল ছোট: object মানে কয়েকটি property-র ঝুলি, আর সাধারণ কাজ ছিল “এই object-এ কী কী আছে, দেখাও”। সেই কাজের জন্য for…in যথার্থ — property-র নাম গোনো, উত্তরাধিকারসহ। Array-ও যেহেতু ভেতরে object, for…in তার ওপরও “চলত”। সমস্যা ধরা পড়ল ধীরে ধীরে: key আসে string হিসেবে, বাড়তি property চলে আসে, আর ২০০০-এর দশকে Prototype.js-এর মতো লাইব্রেরি Array.prototype-এ method যোগ করা শুরু করলে প্রতিটি for…in লুপে সেই method-গুলোও দেখা দিল। মান ঘোরার একটি পরিষ্কার উপায়ের অভাব সবাই টের পাচ্ছিলেন। Mozilla নিজের browser-এ for each…in নামে একটি বিকল্পও এনেছিল, কিন্তু সেটি মানদণ্ডে ঢোকেনি এবং পরে সরিয়ে ফেলা হয়।

তাহলে for…in-কেই array-তে মান দিতে শেখানো গেল না কেন? কারণ পর্ব ০৪-এর সেই নীতি — “don't break the web”. কোটি কোটি পাতা for…in-এর পুরোনো আচরণের ওপর নির্ভর করে; অর্থ বদলালে সেগুলো রাতারাতি ভেঙে যেত। তাই TC39 নতুন একটি syntax বেছে নিল — for…of, ES2015 (ES6) থেকে।

কিন্তু for…of শুধু “array-র জন্য ভালো for…in” নয়; এর পেছনে আছে একটি সাধারণ চুক্তি — iteration protocol. যেকোনো object [Symbol.iterator] method দিলেই for…of, spread ([...x]), destructuring, Array.from, Promise.all — সবকিছুর সাথে কাজ করে। String, Map, Set, NodeList, generator — সবাই এই একটি চুক্তি মানে। এই নকশার অনুপ্রেরণা পুরোনো: Python-এর iterator protocol (২০০১, PEP 234) আর CLU (১৯৭০-এর দশক) ভাষার iterator একই চিন্তার পূর্বসূরি। একটি ভাষা-স্তরের চুক্তি মানে নতুন ডেটা-কাঠামো বানালে ভাষার সব বৈশিষ্ট্য তার সাথে বিনা খরচে কাজ করে।

লক্ষ করুন, Symbol নিজেও ES2015-এ এসেছিল, আংশিকভাবে এই কারণে: iterator নামে সাধারণ string property রাখলে পুরোনো কোনো object-এর একই নামের property-র সাথে সংঘাত হতে পারত। Symbol নিশ্চিত করে চাবিটি অনন্য (পর্ব ২৮)।

মূল উপলব্ধি: পুরোনো ভুল বদলানো যখন অসম্ভব, তখন পাশে একটি ভালো বিকল্প দাঁড় করানো, আর সেটিকে একটি সাধারণ চুক্তির ওপর দাঁড় করানো — এতে শুধু একটি লুপ নয়, পুরো ভাষার একটি নতুন স্তর তৈরি হয়।

প্র ০২forEach থেকে break করা যায় না কেন — এটি কি নকশার ভুল? some আর every কীভাবে “break”-এর বিকল্প হয়ে উঠল?

forEach, map, filter, some, every — এই “array extras” প্রথম আসে Mozilla-র JavaScript 1.6-এ (২০০৫ সালের দিকে, Firefox 1.5), পরে ES5 (২০০৯)-এ মানদণ্ডে ঢোকে। এদের সবার নকশা একই: আপনি একটি function দেন, আর method নিজে সেই function-কে প্রতিটি উপাদানের জন্য ডাকে। একে বলে internal iteration — ঘোরানোর নিয়ন্ত্রণ method-এর হাতে, আপনার হাতে নয়। উল্টো দিকে for…of হলো external iteration — আপনিই ঠিক করেন কখন পরের মান নেবেন, কখন থামবেন।

এই পার্থক্যই উত্তর। break একটি statement, যা সেই function-এর ভেতরের কোনো লুপ বা switch থামায়। forEach-কে দেওয়া callback-এর ভেতরে কোনো লুপ নেই — লুপটি আছে forEach-এর ভেতরে, অন্য function-এ। তাই break লিখলে SyntaxError. return শুধু callback-এর এই একটি ডাক শেষ করে। আর forEach-এর নকশায় callback-এর ফেরত মান উপেক্ষা করা হয় — “থামো” বলার কোনো চ্যানেলই নেই। একমাত্র “উপায়” ছিল exception ছুড়ে বাইরে ধরা, যা কদর্য আর ধীর।

এখানেই some আর every-এর চতুর ব্যবহার। some থামে প্রথম true পাওয়ামাত্র (“অন্তত একটি তো পেলাম, বাকি দেখার দরকার নেই”), every থামে প্রথম false-এ। অর্থাৎ এদের নকশায় callback-এর ফেরত মানই “থামো” সংকেত। তাই পুরোনো কোডে দেখবেন arr.some(x => { …; return found; }) — আসল উদ্দেশ্য প্রশ্ন করা নয়, শুধু আগেভাগে থামা। পরে ES2015-এ find আর findIndex এই কাজটিকে নাম দিয়েছে, আর ES2023-এ এসেছে findLast/findLastIndex.

তাহলে এটা কি নকশার ভুল? ঠিক তা নয় — এটি একটি সচেতন বিনিময়। Internal iteration-এ উদ্দেশ্য স্পষ্ট থাকে (map দেখলেই বোঝা যায় “রূপান্তর”), আর library ভেতরে অপ্টিমাইজ করার স্বাধীনতা পায়। দাম হলো নিয়ন্ত্রণ — break, await, yield কোনোটিই callback ভেদ করে বাইরের লুপে পৌঁছায় না। এজন্যই async কাজে forEach একটি চেনা ফাঁদ।

মূল উপলব্ধি: প্রতিটি লুপ-রূপ একটি প্রশ্নের উত্তর: “নিয়ন্ত্রণ কার হাতে?” থামা, অপেক্ষা বা মাঝপথে সিদ্ধান্ত দরকার হলে external iteration (for…of); শুধু “প্রতিটির জন্য এই রূপান্তর” হলে internal iteration (array method).

প্র ০৩Dijkstra ১৯৬৮ সালে goto-কে “ক্ষতিকর” বলেছিলেন। তাহলে labeled break কি ছদ্মবেশী goto? এটি কেন গ্রহণযোগ্য?

১৯৬৮ সালের মার্চে Communications of the ACM-এ Edsger W. Dijkstra-র একটি ছোট চিঠি ছাপা হয়, যার শিরোনাম সম্পাদক Niklaus Wirth দিয়েছিলেন: “Go To Statement Considered Harmful”. Dijkstra-র যুক্তি ছিল: প্রোগ্রাম পড়ার সময় আমরা মাথায় একটি “বর্তমান অবস্থান” ধরে রাখি। goto যেকোনো জায়গা থেকে যেকোনো জায়গায় লাফ দিতে পারে, তাই কোনো লাইনে কীভাবে পৌঁছানো গেল, তা বুঝতে পুরো প্রোগ্রাম মাথায় রাখতে হয় — “spaghetti code”. এই চিঠি থেকেই structured programming আন্দোলন জোর পায়: নিয়ন্ত্রণ চলবে শুধু ক্রম, শর্ত (if) আর পুনরাবৃত্তি (loop) দিয়ে।

কিন্তু বাস্তবে একটি সমস্যা থেকে গেল — নেস্টেড লুপ থেকে একসাথে বেরোনো। খাঁটি structured পদ্ধতিতে এর জন্য একটি বাড়তি flag ভ্যারিয়েবল লাগে, যা প্রতিটি স্তরে যাচাই করতে হয় — কোড লম্বা আর ভুলপ্রবণ। ১৯৭৪ সালে Donald Knuth “Structured Programming with go to Statements” প্রবন্ধে দেখান, এমন কিছু জায়গায় নিয়ন্ত্রিত লাফ আসলে কোডকে বেশি পরিষ্কার করে।

Labeled break এই দুই অবস্থানের মাঝের সমাধান — structured escape. পার্থক্যগুলো দেখুন: goto যেকোনো label-এ যেতে পারে, সামনে বা পেছনে, এমনকি একটি লুপের মাঝখানে ঢুকেও। break label শুধু একটি কাজ পারে — যে লুপের ভেতরে আপনি আছেন, তার ঠিক পরে বেরিয়ে যাওয়া। লাফের গন্তব্য কোডের গঠন দেখেই নিশ্চিত; ভেতরে ঢোকা অসম্ভব, পেছনে যাওয়া অসম্ভব। continue label-ও তেমনি সীমিত। Java এই নকশা নিয়েছিল, আর JavaScript-এ এটি ES3 (১৯৯৯)-এর মানদণ্ডে ছিল। মজার তথ্য: Java goto-কে reserved word রেখেছে কিন্তু কোনো কাজ দেয়নি, আর JavaScript-এর পুরোনো মানদণ্ডেও (ES3 পর্যন্ত) goto ভবিষ্যতের জন্য সংরক্ষিত শব্দের তালিকায় ছিল — কখনো ব্যবহার হয়নি, ES5-এ তালিকা থেকে বাদ পড়ে।

আজকের পেশাদার চর্চায় labeled break বিরল, কারণ আরও পরিষ্কার একটি structured escape সবসময় হাতে আছে: খোঁজার অংশটি একটি ছোট function-এ রেখে return. Function-এর একটি নামও থাকে, যা কোডকে নিজেই ব্যাখ্যা করে।

মূল উপলব্ধি: Dijkstra-র আপত্তি লাফ দেওয়া নিয়ে ছিল না, ছিল অনির্দেশ্য লাফ নিয়ে। যে লাফের গন্তব্য কোডের গঠন দেখেই বোঝা যায়, সেটি পাঠকের মাথার “বর্তমান অবস্থান” নষ্ট করে না — তাই break, continue, return, এমনকি exception-ও structured বলে গণ্য।

প্র ০৪একটি লম্বা লুপ কেন পুরো পাতাকে আটকে দেয়? “Long task”, ৫০ মিলিসেকেন্ডের হিসাব আর RAIL মডেল আসলে কী বলে?

পর্ব ০৫-এ দেখেছেন, একটি পাতার JavaScript, ক্লিক-সামলানো, layout আর আঁকা — সবই প্রধানত একটি main thread-এ ভাগাভাগি করে চলে। Event loop একটি করে task নেয় এবং সেটি শেষ হওয়া পর্যন্ত চালায় — মাঝপথে থামিয়ে অন্য কাজ ধরে না (run-to-completion). তাই আপনার লুপ ৩ সেকেন্ড চললে সেই ৩ সেকেন্ড ব্যবহারকারীর ক্লিক একটি সারিতে অপেক্ষা করে, পাতা নতুন করে আঁকা হয় না। ব্যবহারকারীর চোখে: “সাইট হ্যাং করেছে।”

কত সময় “বেশি”? এখানে কয়েকটি আলাদা সংখ্যা আছে, যেগুলো গুলিয়ে ফেলা সহজ। মানুষের উপলব্ধি নিয়ে পুরোনো গবেষণা (যেমন Jakob Nielsen-এর সারাংশ) বলে, প্রায় ১০০ মিলিসেকেন্ডের মধ্যে সাড়া এলে কাজটি “তাৎক্ষণিক” মনে হয়। ২০১৫ সালের দিকে Google-এর প্রকৌশলীরা RAIL মডেল প্রস্তাব করেন — Response, Animation, Idle, Load. এর একটি নির্দেশনা: ব্যবহারকারীর ইনপুটে ১০০ মিলিসেকেন্ডের মধ্যে দৃশ্যমান সাড়া দিতে হলে কাজগুলো মোটামুটি ৫০ মিলিসেকেন্ডের টুকরোয় রাখুন — কারণ ইনপুট আসার মুহূর্তে আরেকটি task চলমান থাকতে পারে, তার জন্য বাকি অর্ধেক সময় রেখে দেওয়া। Animation-এর জন্য প্রতি frame-এ আরও কম সময়।

এই ৫০ মিলিসেকেন্ডের সীমাই পরে W3C-র Long Tasks API-তে সংজ্ঞা হিসেবে বসে: ৫০ মিলিসেকেন্ডের বেশি main thread আটকে রাখা task-ই “long task”, আর browser এগুলো রিপোর্ট করতে পারে। আজকের Core Web Vitals-এর INP (Interaction to Next Paint) মাপকাঠিতে ২০০ মিলিসেকেন্ড বা কম “ভালো” ধরা হয় — এটি আলাদা সংখ্যা, পুরো ক্লিক থেকে পরের আঁকা পর্যন্ত সময় মাপে। সংখ্যাগুলো নির্দেশিকা, প্রকৃতির নিয়ম নয়; কিন্তু দিক একই — main thread-এ ছোট ছোট কাজ।

লুপের জন্য এর অর্থ কী? ১০ লাখ সারির হিসাব একবারে না করে টুকরো করুন, প্রতিটি টুকরোর পরে event loop-কে সুযোগ দিন (যেমন await দিয়ে একটি ছোট বিরতি — পর্ব ৩১–৩৪), বা কাজটি পুরোপুরি একটি Web Worker-এ পাঠান (পর্ব ৪২)। মোবাইলে হিসাবটি আরও কঠিন: একটি সস্তা ফোনের CPU ল্যাপটপের চেয়ে কয়েক গুণ ধীর হতে পারে, তাই আপনার ল্যাপটপে ৩০ মিলিসেকেন্ডের লুপ বাংলাদেশের অনেক ব্যবহারকারীর ফোনে long task হয়ে যেতে পারে।

মূল উপলব্ধি: Browser-এ একটি লুপের খরচ শুধু CPU সময় নয় — সেই সময়ে ব্যবহারকারীর সব কাজ আটকে থাকে। তাই প্রশ্ন “লুপটি কত দ্রুত?” নয়, “এক টানা কতক্ষণ main thread দখল করে রাখে?”

প্র ০৫“লুপ unrolling” কী, আর JIT compiler আপনার লুপকে ভেতরে ভেতরে কীভাবে বদলায়? তাহলে হাতে অপ্টিমাইজ করার পুরোনো কৌশল কেন বাদ দেওয়া উচিত?

একটি লুপের প্রতিটি ঘোরায় শুধু body-র কাজ হয় না, বাড়তি “ব্যবস্থাপনার খরচ” থাকে — শর্ত যাচাই, counter বাড়ানো, লুপের শুরুতে ফিরে যাওয়ার লাফ। Loop unrolling মানে body কয়েকবার পাশাপাশি লিখে ঘোরার সংখ্যা কমানো: ১০০ বার sum += a[i]-এর বদলে ২৫ বার, প্রতিবারে চারটি যোগ। শর্ত যাচাই আর লাফ চার ভাগের এক ভাগ, আর CPU একসাথে কয়েকটি নির্দেশ চালানোর সুযোগ পায়। এর বিখ্যাত হাতে-লেখা রূপ “Duff's device” — ১৯৮৩ সালে Lucasfilm-এ Tom Duff C-তে switch আর লুপ মিলিয়ে একটি অদ্ভুত কিন্তু বৈধ কৌশল লেখেন। GCC বা LLVM-এর মতো C compiler আজ নিজেই এমন রূপান্তর করে।

JavaScript-এ ব্যাপারটা আরও মজার, কারণ compile হয় চলার সময়। V8 প্রথমে কোড bytecode-এ interpreter দিয়ে চালায় (পর্ব ০৫)। একটি লুপ বারবার ঘুরলে engine সেটিকে “গরম” বলে চিহ্নিত করে আর optimizing compiler দিয়ে machine code বানায়। একটি বিশেষ কৌশল হলো on-stack replacement (OSR) — লুপ চলার মাঝখানেই ধীর সংস্করণ থেকে দ্রুত সংস্করণে স্থানান্তর, যাতে একটিমাত্র লম্বা লুপও উপকার পায়। Optimizer পর্যবেক্ষণের ভিত্তিতে অনুমান করে — “এই array-তে সবসময় পূর্ণসংখ্যা থাকে” — আর সেই অনুমানে অপ্রয়োজনীয় টাইপ-যাচাই ও সীমা-যাচাই সরিয়ে দেয়, লুপের ভেতরের অপরিবর্তিত হিসাব লুপের বাইরে তুলে আনে, কখনো প্রথম ঘোরাকে আলাদা করে (loop peeling). অনুমান ভাঙলে — হঠাৎ একটি string ঢুকে পড়লে — engine ধীর সংস্করণে ফিরে যায় (deoptimization).

এ থেকে বাস্তব শিক্ষা তিনটি। প্রথম, arr.length ভ্যারিয়েবলে রাখা বা উল্টো দিকে ঘোরার মতো পুরোনো কৌশল আধুনিক engine-এ সাধারণত কোনো লক্ষণীয় সুবিধা দেয় না, কিন্তু কোড পড়া কঠিন করে। দ্বিতীয়, engine সবচেয়ে ভালো অপ্টিমাইজ করে সরল ও স্থির কোড — একই ধরনের মানের array, একই আকারের object. এক array-তে number, string, object মেশালে optimizer-এর অনুমান খাটে না। তৃতীয়, মাইক্রো-বেঞ্চমার্ক বিভ্রান্তিকর হতে পারে: একটি লুপের ফল কোথাও ব্যবহার না হলে optimizer পুরো লুপটাই বাদ দিতে পারে, আর আপনি দেখবেন “অবিশ্বাস্য গতি”।

মূল উপলব্ধি: আধুনিক engine-এর যুগে মানুষের কাজ হলো সঠিক অ্যালগরিদম বাছা আর পরিষ্কার, ধারাবাহিক টাইপের কোড লেখা; নির্দেশ-স্তরের অপ্টিমাইজেশন compiler-এর হাতে ছেড়ে দিন, আর কিছু বদলানোর আগে মেপে দেখুন।

অনুশীলন

বেশিরভাগ কাজ দেখা আর ভাবার — কোডের পরিবর্তন খুব সামান্য।

  1. ঘোরা গুনুন: চালানোর আগে বলুন, প্রতিটি লুপের body কতবার চলবে — (ক) for (let i = 0; i < 5; i++) (খ) for (let i = 1; i <= 5; i++) (গ) for (let i = 10; i < 5; i++) (ঘ) let k = 10; do { k++; } while (k < 5);

    (ক) ৫ বার (0, 1, 2, 3, 4). (খ) ৫ বার (1 থেকে 5) — শুরু আর শর্ত দুটোই এক ঘর সরেছে, তাই সংখ্যা এক। (গ) ০ বার — প্রথম শর্তই 10 < 5 মিথ্যা। (ঘ) ১ বার — do…while আগে body চালায়, তারপর 11 < 5 মিথ্যা দেখে থামে। (গ) আর (ঘ)-এর পার্থক্যটাই while ও do…while-এর মূল পার্থক্য।

  2. বাগ শিকারি: একজন লিখেছেন const scores = [70, 80]; for (const i in scores) console.log(i + 1, scores[i]); — উদ্দেশ্য “1 70”, “2 80” ছাপানো। কী আসবে, কেন, আর ঠিক করবেন কীভাবে?

    আসবে 01 70 আর 11 80. for…in key দেয় string হিসেবে — "0", "1" — তাই i + 1 যোগ নয়, জোড়া দেয়। ঠিক করার উপায়: for (const [i, s] of scores.entries()) console.log(i + 1, s); — এখানে i আসল number. সাথে বোনাস: array-তে বাড়তি বা উত্তরাধিকারের property থাকলেও আর আসবে না।

  3. forEach কি থামে? [5, 0, 7].forEach(n => { if (n === 0) return; console.log(n); }); — কী ছাপা হবে? আর প্রথম 0 পেলেই পুরো কাজ থামাতে চাইলে কীভাবে লিখবেন?

    ছাপা হবে 5 আর 7. return শুধু 0-এর জন্য ডাকা callback-টি শেষ করেছে — continue-এর মতো; forEach 7-এর জন্য আবার ডেকেছে। থামাতে চাইলে: for (const n of [5, 0, 7]) { if (n === 0) break; console.log(n); } — ছাপবে শুধু 5. অথবা উদ্দেশ্য যদি “0 আছে কি না” জানা হয়: [5, 0, 7].some(n => n === 0), যা 0 পাওয়ামাত্র থামে।

  4. নিজের লুপ লিখুন (কোড লেখার কাজ): একটি object আছে — { 'জানুয়ারি': 1200, 'ফেব্রুয়ারি': 0, 'মার্চ': 1800 } (মাসভিত্তিক সঞ্চয়)। Object.entries + for…of দিয়ে মোট সঞ্চয় বের করুন, আর যে মাসে সঞ্চয় 0, সেটি পাওয়ামাত্র “প্রথম ফাঁকা মাস” হিসেবে মনে রাখুন (কিন্তু যোগফল যেন পুরো হয়)।

    খেয়াল করুন: যোগফল পুরো চাই, তাই break চলবে না — শুধু প্রথম ফাঁকা মাসটি একবার মনে রাখতে হবে:

    const plan = { 'জানুয়ারি': 1200, 'ফেব্রুয়ারি': 0, 'মার্চ': 1800 };
    let total = 0;
    let firstEmpty = null;
    for (const [month, amount] of Object.entries(plan)) {
      total += amount;
      if (amount === 0 && firstEmpty === null) firstEmpty = month;
    }
    console.log(total, firstEmpty);   // 3000 'ফেব্রুয়ারি'

    এখানে দুটি accumulator একই লুপে: total (যোগফল) আর firstEmpty (প্রথম মিল)। firstEmpty === null শর্তটি নিশ্চিত করে পরের কোনো ফাঁকা মাস প্রথমটিকে মুছে দেবে না। যাচাই: 1200 + 0 + 1800 = 3000.

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমএকটি for লুপ যে ক্রমে চলে, সেভাবে সাজান (▲▼ চেপে সরান)

  1. init অংশ চলে — শুধু একবার
  2. শর্ত যাচাই হয়
  3. শর্ত সত্য হলে body চলে
  4. update অংশ চলে (যেমন i++)
  5. আবার শর্ত যাচাই — মিথ্যা হলে লুপ শেষ

JavaScript-এ const a = ['x', 'y']; লিখে for (const k in a) console.log(k); চালালে কী ছাপা হয়?

  • x আর y
  • string হিসেবে 0 আর 1
  • 0, 1 আর length
  • TypeError, কারণ array-তে for…in চলে না

JavaScript-এ একটি সাধারণ object { a: 1 }-এর ওপর সরাসরি for (const v of obj) চালালে কী হয়?

  • 1 ছাপা হয়
  • a ছাপা হয়
  • TypeError — object টি iterable নয়
  • কিছুই হয় না, লুপ ০ বার চলে

let n = 5; do { n++; } while (n < 3); চলার পর n-এর মান কত?

  • 6
  • 5
  • 3
  • লুপ কখনো থামে না

JavaScript-এ arr.forEach(x => { if (x === 0) return; console.log(x); })-এ return কী করে?

  • পুরো forEach থামিয়ে দেয়, break-এর মতো
  • SyntaxError দেয়
  • forEach থেকে 0 ফেরত দেয়
  • শুধু বর্তমান উপাদানটি বাদ দেয়, continue-এর মতো

দুই স্তরের নেস্টেড লুপের ভেতরের লুপে লেখা সাধারণ break কী করে?

  • দুটি লুপই একসাথে থামায়
  • শুধু ভেতরের লুপ থামায়, বাইরের লুপ চলতে থাকে
  • পুরো প্রোগ্রাম থামিয়ে দেয়
  • ভেতরের লুপের শুধু বর্তমান ঘোরা বাদ দেয়

const m = [10, 20, 30]; let s = 0; for (let i = 0; i <= m.length; i++) s += m[i]; চলার পর s-এর মান কত?

  • 60
  • RangeError
  • NaN
  • 30

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. প্রতিটি লুপে তিনটি প্রশ্ন: শুরু কোথায়, কখন থামবে, প্রতিবার কী বদলায়। for-এর ক্রম: init → শর্ত → body → update → শর্ত …
  2. সংখ্যা জানা থাকলে for, না জানলে while, অন্তত একবার লাগলে do…while; retry লুপে সবসময় সর্বোচ্চ সীমা।
  3. for…of = মান (array, string, Map, Set); for…in = key, string, উত্তরাধিকারসহ — array-তে কখনো নয়। Object-এ Object.entries + for…of.
  4. break সবচেয়ে ভেতরের লুপ থামায়, continue একটি ঘোরা বাদ দেয়, break label কয়েক স্তর একসাথে; forEach থামে না।
  5. ফাঁদ তিনটি: অসীম লুপ (browser-এ পুরো পাতা জমে), off-by-one (< বনাম <=), আর ঘোরার মাঝে array বদলানো।

পরের পর্বে: এই পর্বে একাধিকবার বলেছি “খোঁজার অংশটি একটি function-এ রেখে return করুন”। এবার সেই function — declaration আর arrow function-এর পার্থক্য, default আর rest parameter, আর কেন function-ই JavaScript-এর সবচেয়ে শক্তিশালী গঠন।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান