এই পর্বে যা শিখবেন
- কোডিং ইন্টারভিউর সাধারণ ধাপ, আর ইন্টারভিউয়ার আসলে কী কী দেখেন
- যেকোনো নতুন সমস্যায় খাটে এমন সাত ধাপের পদ্ধতি, আর কথা বলতে বলতে কোড লেখা
- JavaScript-এর নিজস্ব ফাঁদ: sort-এর comparator, mutation, precision, recursion-এর গভীরতা — আর built-in কাজগুলোর জটিলতা
- hashing, sliding window, stack, binary search, interval, recursion আর BFS-এর ক্লাসিক সমস্যা
- LRU cache, EventEmitter, debounce/throttle, Promise.all, promise pool আর একটি typeahead — front-end ও Node ইন্টারভিউর নকশার প্রশ্ন
১ · JavaScript কোডিং ইন্টারভিউ কেমন হয়
পর্ব ৫৮-এ লিখিত ও মৌখিক প্রশ্নে গুছিয়ে উত্তর দেওয়ার অনুশীলন করেছেন। সফটওয়্যার প্রতিষ্ঠানের technical interview একটু আলাদা। এখানে শুধু “উত্তর জানেন কি না” দেখা হয় না। দেখা হয়, অচেনা একটি সমস্যা পেলে আপনি কীভাবে ভাবেন, কীভাবে কথা বলেন, আর কতটা নির্ভরযোগ্য কোড লেখেন।
দেশি সফটওয়্যার ফার্ম, স্টার্টআপ, ব্যাংক বা টেলিকমের আইটি বিভাগ, কিংবা বিদেশি প্রতিষ্ঠানের রিমোট পদ — প্রতিষ্ঠানভেদে ধাপের নাম, সংখ্যা আর ক্রম আলাদা। সব ধাপ সব জায়গায় থাকে না। JavaScript-এর পদে (front-end, Node.js back-end বা full-stack) সাধারণ কাঠামোটা মোটামুটি এমন:
| ধাপ | সাধারণত কী হয় | প্রস্তুতি |
|---|---|---|
| Phone / HR screen | ১৫–৩০ মিনিটের কথা: পড়াশোনা, প্রজেক্ট, আগ্রহ। কখনো দু-একটি সহজ প্রশ্ন, যেমন == আর ===-এর পার্থক্য। | নিজের প্রজেক্ট দুই মিনিটে গুছিয়ে বলা |
| Online assessment | সময় বাঁধা অনলাইন পরীক্ষা: ২–৪টি কোডিং সমস্যা, কখনো MCQ. লুকানো test case দিয়ে যন্ত্র নম্বর দেয়। | সময় ধরে সমাধান; MCQ-র জন্য পর্ব ৫৭, আউটপুট পড়ার জন্য পর্ব ৫৬ |
| Live coding | ৪৫–৬০ মিনিট, শেয়ার করা editor বা হোয়াইটবোর্ড। ১–২টি অ্যালগরিদম সমস্যা আর আলোচনা। | জোরে ভেবে সমাধান — এই পাতার মূল অনুশীলন |
| Machine coding | ৬০–৯০ মিনিটে চলমান একটি ছোট জিনিস বানানো: typeahead, todo, tabs, পেজিনেশন, ছোট REST endpoint. | DOM, event, async আর পরিষ্কার কাঠামো — অংশ ৭, পর্ব ৩৬–৩৯ |
| Take-home | কয়েক ঘণ্টা থেকে কয়েক দিনের একটি কাজ, যেমন একটি ছোট অ্যাপ বা API. পরে কোড নিয়ে আলোচনা। | পরিষ্কার কাঠামো, test আর README — পর্ব ৪৫–৫০, ৫৫ |
| Design ও behavioural | জুনিয়র পদে ছোট component বা API-এর নকশা; অভিজ্ঞ পদে বড় সিস্টেম। সাথে দলে কাজ, ভুল থেকে শেখা। | অংশ ৬–৭, পর্ব ৫৪–৫৫, আর ৪–৫টি সত্যি গল্প |
ইন্টারভিউয়ারের মূল্যায়নে কী থাকে
অনেকে ভাবেন, “সঠিক কোড লিখলেই পাস।” বাস্তবে মূল্যায়নে সাধারণত কয়েকটি আলাদা দিক থাকে। ঠিক কোড লিখেও কেউ পিছিয়ে পড়েন, আবার কোড পুরো শেষ না করেও কেউ ভালো করেন — যদি বাকি দিকগুলো শক্ত হয়।
সমস্যা বোঝা ও যোগাযোগ
লেখার আগে প্রশ্ন করে অস্পষ্টতা দূর করেছেন কি? ভাবনা জোরে বলেছেন কি? ইন্টারভিউয়ার আপনার মাথার ভেতর দেখতে পান না।
সঠিকতা, জটিলতা, edge case
খালি ইনপুট, এক উপাদান, ডুপ্লিকেট, বাংলা বা emoji ভেবেছেন কি? সময় ও মেমরির Big-O নিজে থেকে বলেছেন কি?
কোডের মান
পরিষ্কার নাম, ছোট function, ইনপুট না বদলানো, স্পষ্ট ফেরত মান — প্রোডাকশনের মতো কোড, কনটেস্টের এক-অক্ষরের ভ্যারিয়েবল নয়।
JavaScript-এ সাবলীলতা
Map/Set, array method, closure, Promise, async/await, DOM — স্বাভাবিকভাবে আর ঠিক জায়গায় ব্যবহার করেন কি? ভাষার ফাঁদ চেনেন কি?
ইন্টারভিউ একটি কাজের নমুনা। ইন্টারভিউয়ার মনে মনে ভাবেন: “এই মানুষটির সাথে একটি কঠিন বাগ নিয়ে এক ঘণ্টা বসলে কেমন লাগবে?” তাই চুপচাপ লেখা নিখুঁত কোডের চেয়ে কথা বলে ধাপে ধাপে এগোনো ভালো কোড প্রায়ই বেশি মূল্য পায়।
এই পাতার ১৬টি সমস্যা
প্রতিটি গোত্রের পাশে রিভিশনের পর্ব দেওয়া আছে — সমাধান পড়ার আগে সেই পর্বের মূল ধারণা একবার ঝালিয়ে নিন।
| গোত্র | সমস্যা | রিভিশনের পর্ব |
|---|---|---|
| পদ্ধতি (অংশ ২) | ১. Two sum | ১৮, ১২ |
| অ্যারে, স্ট্রিং, hashing (অংশ ৪) | ২. Valid anagram · ৩. পুনরাবৃত্তিহীন দীর্ঘতম substring · ৪. Group anagrams · ৫. সবচেয়ে বেশি আসা k টি শব্দ | ০৯, ১৫, ১৬, ১৮ |
| Stack, search, interval, recursion (অংশ ৫) | ৬. বন্ধনী মেলানো · ৭. Binary search · ৮. Interval জোড়া লাগানো · ৯. Nested array সমান করা · ১০. Deep clone ও deep equal | ১৩, ১৪, ১৭, ১৯ |
| গ্রাফ ও নকশা (অংশ ৬) | ১১. গ্রিডে সবচেয়ে ছোট পথ (BFS) · ১২. LRU cache · ১৩. EventEmitter | ১৮, ২৬, ৩৮ |
| Async ও machine coding (অংশ ৭) | ১৪. Debounce ও throttle · ১৫. Promise.all ও promise pool · ১৬. Typeahead — debounce, abort আর race-safety | ২২, ৩৩, ৩৪, ৩৬, ৩৭ |
২ · সমস্যা থেকে সমাধান — সাত ধাপের পদ্ধতি
ইন্টারভিউর চাপে মাথা ফাঁকা হয়ে যাওয়া খুব স্বাভাবিক। এর সবচেয়ে ভালো ওষুধ একটি বাঁধা পদ্ধতি — যাতে “এখন কী করব” ভাবতে না হয়, শুধু পরের ধাপে যেতে হয়। ক্রিকেটে ব্যাটসম্যান যেমন প্রতিটি বলের আগে একই রুটিন মানেন — গার্ড নেওয়া, ফিল্ডিং দেখা, তারপর স্টান্স — এটাও তেমন।
পদ্ধতিটি একটি বিখ্যাত সমস্যায় প্রয়োগ করি — সমস্যা ১ · Two sum (সহজ): “সংখ্যার একটি array আর একটি target দেওয়া আছে। এমন দুটি ভিন্ন index বের করুন, যাদের মানের যোগফল target.” নিচের ডায়াগ্রামে “পরে” চাপুন:
- ১ · বুঝুনপ্রশ্ন করে অস্পষ্টতা দূর
- ২ · উদাহরণছোট ইনপুট ও edge case
- ৩ · Brute forceআগে একটি সঠিক সমাধান
- ৪ · উন্নত পদ্ধতিবারবার হওয়া কাজ কমান
- ৫ · কোডপরিষ্কার আধুনিক JS
- ৬ · পরীক্ষাউদাহরণ দিয়ে dry run
- ৭ · জটিলতা ও ফলো-আপBig-O আর নতুন শর্ত
- উত্তর সবসময় থাকবে, নাকি নাও থাকতে পারে? - একই index দুবার নেওয়া যাবে? (না) - array কি সাজানো? (না) - n কত বড়? ঋণাত্মক বা দশমিক আছে? - index ফেরত দেব, নাকি মান?
[2, 7, 11, 15], target 9 -> [0, 1] [3, 3], target 6 -> [0, 1] একই মান, ভিন্ন index [-3, 4, 3, 90], target 0 -> [0, 2] ঋণাত্মক [5], target 10 -> null 5+5 চলবে না [0.1, 0.2], target 0.3 -> ? দশমিক!
for i in 0..n-1:
for j in i+1..n-1:
if a[i] + a[j] === target: return [i, j]
সময় O(n²), বাড়তি মেমরি O(1)
ভেতরের লুপ শুধু একটা প্রশ্ন করে: "target - a[j] কি আগে দেখেছি?" -> দেখা মানগুলো একটি Map-এ রাখি (মান -> index), খোঁজা গড়ে O(1)
for j of indices:
need = target - a[j]
if seen.has(need): return [seen.get(need), j]
seen.set(a[j], j) // আগে খুঁজি, পরে রাখি
return null // "নেই" স্পষ্ট করে বলা
j=0: need=3, seen-এ নেই -> seen {3 => 0}
j=1: need=3, seen-এ আছে(0) -> [0, 1]
উল্টো ক্রমে (আগে রাখি, পরে খুঁজি) [5], 10
-> নিজের সাথেই জোড়া [0, 0] — ভুল!
Map: সময় গড়ে O(n), মেমরি O(n)
- array সাজানো হলে? -> two pointers, O(1) মেমরি
- সব জোড়া চাই? 3-sum? -> sort + two pointers, O(n²)
- দশমিক হলে? -> === ভাঙে, tolerance বা পয়সায় হিসাব
- বুঝুন। সমস্যার বিবরণ প্রায়ই ইচ্ছা করেই অসম্পূর্ণ রাখা হয় — আপনি প্রশ্ন করেন কি না, সেটাই দেখা হয়। এই প্রশ্নগুলোর উত্তর পুরো সমাধান বদলে দিতে পারে। যেমন array সাজানো থাকলে একদম অন্য, আরও কম মেমরির পদ্ধতি চলে।
- উদাহরণ। দেওয়া উদাহরণ ছাড়াও নিজে কয়েকটি লিখুন: একই মান দুবার, ঋণাত্মক, উত্তর নেই, একটিমাত্র উপাদান — আর JavaScript-এ বিশেষ করে দশমিক। এগুলোই পরে আপনার test case হবে। Edge case আগে লিখলে কোডে সেগুলো ভুলে যাওয়ার সম্ভাবনা কমে।
- Brute force. সবচেয়ে সরল brute force সমাধানটি মুখে বলুন, যদিও সেটি ধীর। এতে দেখান যে অন্তত একটি সঠিক উত্তর আপনার হাতে আছে। সময় না থাকলে এটাই কোড করবেন — কিছু না থাকার চেয়ে সঠিক ধীর সমাধান অনেক ভালো।
- উন্নত পদ্ধতি। জিজ্ঞেস করুন: “কোন কাজটি বারবার হচ্ছে?” এখানে ভেতরের লুপ শুধু একটি মান খোঁজে। খোঁজা দ্রুত করার যন্ত্র Map (পর্ব ১৮)। O(n²) থেকে গড়ে O(n) — বিনিময়ে O(n) বাড়তি মেমরি, অর্থাৎ space complexity বাড়ল।
- কোড। ইন্টারভিউয়ার রাজি হলে তবেই কোড লিখুন। প্রথমে function-এর নাম আর ফেরত মান ঠিক করুন: পেলে
[i, j], না পেলেnull. একটি খুঁটিনাটি: আগে খুঁজি, তারপর রাখি — এতে একটি উপাদান নিজের সাথে জোড়া হয় না। - পরীক্ষা। “শেষ” বলার আগে নিজের উদাহরণ দিয়ে কোডটি লাইন ধরে চালান — এটাই dry run, যা পর্ব ৫৬-এ অনুশীলন করেছেন। এখানে দেখা যাচ্ছে, ক্রম উল্টালে
[5], target 10-এ ভুল উত্তর আসত। - জটিলতা ও ফলো-আপ। শেষে সময় ও মেমরি আবার পরিষ্কার করে বলুন। Map-এর O(1) “গড়ে” — এটা hash table-এর বৈশিষ্ট্য। এরপর ইন্টারভিউয়ার শর্ত বদলান; প্রতিটি ফলো-আপে আবার একই সাত ধাপ, শুধু দ্রুত।
function twoSum(nums, target) {
const seen = new Map(); // value -> index
for (let j = 0; j < nums.length; j++) {
const need = target - nums[j];
if (seen.has(need)) return [seen.get(need), j]; // look first ...
if (!seen.has(nums[j])) seen.set(nums[j], j); // ... then store
}
return null; // "no pair", said clearly
}
const tests = [
[[2, 7, 11, 15], 9],
[[3, 3], 6],
[[-3, 4, 3, 90], 0],
[[5], 10],
[[0.1, 0.2], 0.3],
];
for (const [nums, target] of tests) {
console.log(JSON.stringify(nums), target, "->", twoSum(nums, target));
}আউটপুট: প্রথম তিনটিতে [ 0, 1 ], [ 0, 1 ], [ 0, 2 ]; তারপর [5]-এ null — আর শেষেরটিতেও null! কারণ 0.3 - 0.2 হয় 0.09999999999999998, যা 0.1-এর সমান নয় (পর্ব ১০-এর floating-point). ইন্টারভিউতে এই edge case নিজে থেকে তুললে বোঝা যায় আপনি JavaScript-এর সংখ্যা চেনেন। সমাধান: টাকার হিসাব পয়সায় (পূর্ণসংখ্যায়) রাখা, বা প্রশ্নকর্তার সাথে ঠিক করে নেওয়া যে ইনপুট পূর্ণসংখ্যা।
যা নিজে থেকে বলবেন: plain object {}-এর বদলে Map নিয়েছি, কারণ object-এর key সবসময় string হয়ে যায় (-3 আর "-3" একই key), আর "constructor"-এর মতো key prototype থেকে এসে গোলমাল করতে পারে। if (!seen.has(nums[j])) ডুপ্লিকেটে প্রথম index রাখে। ফেরত টাইপ স্পষ্ট: হয় দুটি index-এর array, নয় null — -1 বা undefined-এর মতো দ্ব্যর্থক মান নয়।
কথা বলতে বলতে কোড লেখা
অনেকে কোড লেখার সময় চুপ হয়ে যান। অথচ ইন্টারভিউয়ারের কাছে সবচেয়ে মূল্যবান তথ্য আপনার ভাবনার পথ। Think aloud মানে সব কিছু বলা নয় — সিদ্ধান্তগুলো বলা। কয়েকটি কাজের বাক্য:
- শুরুতে: “আমি ধরে নিচ্ছি array খালি হতে পারে, আর উত্তর না থাকলে
nullদেব — ঠিক আছে?” - পদ্ধতি বাছাইয়ে: “দুটি লুপে O(n²) হয়; n এক লাখ হলে সেটা প্রায় পাঁচশো কোটি তুলনা। তাই Map ভাবছি — মেমরি বাড়বে, সময় কমবে।”
- লেখার মাঝে: “এখানে আগে খুঁজছি, পরে রাখছি, যাতে একই উপাদান নিজের সাথে না মেলে।”
- আটকে গেলে: “এই অংশে একটু আটকেছি — প্রথমে ছোট একটি উদাহরণ হাতে চালিয়ে দেখি।” নীরবতার চেয়ে এটা অনেক ভালো সংকেত।
- শেষে: “সময় গড়ে O(n), মেমরি O(n). দশমিক ইনপুটে
===তুলনা ভাঙতে পারে।”
৩ · JavaScript-এর নিজস্ব ফাঁদ আর built-in কাজের জটিলতা
অ্যালগরিদম যেকোনো ভাষায় একই। কিন্তু JavaScript-এ লিখলে ইন্টারভিউয়ার ভাষার নিজস্ব কয়েকটি বিষয়ও খেয়াল করেন — বিশেষ করে যেগুলো চুপচাপ ভুল ফল দেয়, error দেয় না। প্রথমে সবচেয়ে কাজের টেবিল — বহুল ব্যবহৃত built-in কাজের জটিলতা (পর্ব ১৫, ১৬, ১৮)। মনে রাখবেন, ECMAScript মানদণ্ড বেশিরভাগ কাজের জটিলতা বেঁধে দেয় না; নিচের সংখ্যাগুলো আধুনিক engine-এর স্বাভাবিক আচরণ, যা ইন্টারভিউতে ধরে নেওয়া হয়।
| কাজ | সাধারণ জটিলতা | মন্তব্য |
|---|---|---|
arr[i], arr.length, push, pop | O(1) (push গড়ে) | stack হিসেবে array চমৎকার |
shift, unshift, মাঝে splice | O(n) | বাকি সব উপাদান এক ঘর সরে — BFS-এর queue-তে shift লুপে দিলে মোট O(n²) হতে পারে |
includes, indexOf, find | O(n) | লুপের ভেতরে দিলে O(n²) — তখন Set/Map |
Map/Set-এর get, set, has, delete | গড়ে O(1) | ঢোকানোর ক্রম মনে রাখে (ES2015 থেকে মানদণ্ডে নিশ্চিত) |
sort, toSorted | O(n log n) | ES2019 থেকে stable হওয়া বাধ্যতামূলক; sort মূল array বদলায় |
slice, spread [...a], concat, map, filter | O(n) | প্রতিবার নতুন array — লুপের ভেতরে copy করলে খরচ গুণ হয় |
Object.keys, JSON.stringify, flat | O(আকার) | “এক লাইন” মানেই O(1) নয় |
string-এর slice, split, join | O(দৈর্ঘ্য) | string immutable; বড় লেখা জোড়ায় parts-এর array বানিয়ে শেষে join পরিষ্কার |
ছয়টি ফাঁদ, যা ইন্টারভিউয়ার প্রায়ই খোঁজেন
- comparator ছাড়া sort: ডিফল্ট
sort()উপাদানগুলোকে string বানিয়ে তুলনা করে, তাই[10, 1, 5]হয়[1, 10, 5]. সংখ্যায় সবসময় comparator দিন:(a, b) => a - b(পর্ব ১৬)। - ইনপুট বদলে ফেলা (mutation):
sort,reverse,spliceমূল array বদলায় আর একই array ফেরত দেয়। ফাংশনের ইনপুট বদলানো ইন্টারভিউতে খারাপ সংকেত। ES2023-এরtoSorted,toReversed,toSplicedনতুন array দেয়। - overflow নয়, precision: JavaScript-এর
numberএকটি 64-bit double. C বা Java-র মতো চুপচাপ ঋণাত্মক হয়ে যাওয়া int overflow নেই — কিন্তু 253-এর পরে পাশাপাশি পূর্ণসংখ্যা আলাদা থাকে না (safe integer-এর সীমা), আর দশমিক হিসাব আনুমানিক (floating-point). বড় পূর্ণসংখ্যায় BigInt. ব্যতিক্রম: bitwise অপারেটর (|,&,>>) মানকে 32-bit int বানায় — সেখানে overflow আছে। fill-এর শেয়ার করা object:Array(3).fill([])তিনটি ঘরে একই array রাখে — গ্রিড বানাতে খুব চেনা বাগ।Array.from({ length: 3 }, () => [])প্রতি ঘরে নতুন array দেয়।- object-কে Map ভাবা: object-এর key string হয়ে যায়, আর integer-এর মতো দেখতে key-গুলো আগে, ছোট থেকে বড় ক্রমে আসে — ঢোকানোর ক্রমে নয়। গণনা বা ক্রম-নির্ভর কাজে Map নিন (পর্ব ১৪, ১৮)।
- recursion-এর গভীরতা: প্রতিটি কলে call stack-এ একটি frame বসে; engine-এর stack সীমিত। কয়েক হাজার থেকে কয়েক দশ হাজার স্তরের পরে
RangeError— সঠিক সংখ্যা engine ও frame-এর আকারভেদে আলাদা। গভীর ইনপুটে নিজের stack দিয়ে iterative সমাধান (সমস্যা ৯)।
সবগুলো একসাথে চালিয়ে দেখুন:
// 1) default sort compares strings
const nums = [10, 1, 5, 100, 25];
console.log(nums.toSorted(), nums.toSorted((a, b) => a - b));
// 2) sort() mutates and returns the same array; toSorted() (ES2023) copies
const scores = [3, 1, 2];
const sorted = scores.sort();
console.log(sorted === scores, scores);
// 3) no integer overflow, but precision runs out after 2^53
console.log(0.1 + 0.2 === 0.3, 2 ** 53 === 2 ** 53 + 1);
console.log(Number.MAX_SAFE_INTEGER + 2, 9007199254740991n + 2n);
console.log((2 ** 31) | 0); // bitwise ops work on 32-bit ints
// 4) fill() puts the SAME inner array in every row
const grid = Array(2).fill([]);
grid[0].push("x");
console.log(JSON.stringify(grid), Array.from({ length: 2 }, () => []).length);
// 5) integer-like object keys come first, in ascending order
console.log(Object.keys({ b: 1, 10: 1, a: 1, 2: 1 }));
// 6) very deep recursion -> RangeError (caught here)
const depth = (n) => (n === 0 ? 0 : 1 + depth(n - 1));
try { depth(1e6); } catch (e) { console.log(e.name + ": " + e.message); }আউটপুট (Edge): [ 1, 10, 100, 25, 5 ] [ 1, 5, 10, 25, 100 ] — প্রথমটি string-ক্রম ("100" আসে "25"-এর আগে, কারণ '1' < '2'). তারপর true [ 1, 2, 3 ]: sort একই array ফেরত দিয়েছে আর মূলটিই বদলে গেছে। false true: 253 আর 253 + 1 একই double. 9007199254740992 9007199254740993n: number-এ সঠিক উত্তর (…993) রাখা যায়নি, BigInt-এ গেছে। -2147483648: 32-bit-এ overflow.
[["x"],["x"]] 2 — এক ঘরে push করলে দুই ঘরেই দেখা যায়। [ '2', '10', 'b', 'a' ] — integer-এর মতো key আগে, তারপর বাকিগুলো ঢোকানোর ক্রমে। শেষে RangeError: Maximum call stack size exceeded — V8-এর (Chrome, Edge, Node) বার্তা; Firefox-এ টাইপ আর বার্তা দুটোই আলাদা: InternalError: too much recursion (মানদণ্ডের বাইরের, Firefox-নিজস্ব error টাইপ)। তাই error-এর লেখার ওপর নির্ভর করে কোড লিখবেন না।
Python-এর dict/set আর Java-র HashMap/HashSet-এর জায়গায় এখানে Map/Set; কিন্তু JavaScript-এ কোনো built-in priority queue (heap) নেই — Python-এ heapq, Java-য় PriorityQueue, C++-এ std::priority_queue আছে (ভাবনার প্রশ্ন ২)। C++/Java-র int overflow-এর বদলে এখানে precision-এর সমস্যা। একই ধরনের সমস্যার অন্য ভাষার সংস্করণ দেখুন Java, Python আর C++ কোর্সের কোডিং ইন্টারভিউ পর্বে — পাশাপাশি রাখলে বোঝা যায় কোন ফাঁদ ভাষার, আর কোনটা অ্যালগরিদমের।
৪ · অ্যারে, স্ট্রিং ও hashing
ইন্টারভিউর প্রথম সারির প্রশ্ন প্রায়ই এই গোত্রের। মূল প্রশ্ন একটাই: “কোন তথ্য মনে রাখলে বারবার খুঁজতে হয় না?” রিভিশনের জন্য: পর্ব ০৯ · string আর বাংলা Unicode, পর্ব ১৫ · array, পর্ব ১৬ · map, filter, reduce আর সাজানো, পর্ব ১৮ · Map আর Set.
সমস্যা ২ · Valid anagram — বাংলাতেও নিরাপদ (সহজ)
প্রশ্ন: দুটি string কি একে অপরের anagram — একই অক্ষর, একই সংখ্যায়, শুধু ক্রম আলাদা? “listen” আর “silent” → true, “rat” আর “car” → false.
জিজ্ঞেস করে নিন: বড়-ছোট হাতের অক্ষর কি আলাদা? space গোনা হবে? শুধু ইংরেজি, নাকি বাংলা বা emoji-ও? — শেষ প্রশ্নটাই JavaScript-এ সবচেয়ে গুরুত্বপূর্ণ। s.length আর s.split("") কাজ করে UTF-16 code unit ধরে; একটি emoji দুটি unit (surrogate pair), তাই split("") তাকে ভেঙে ফেলে। Array.from(s) বা [...s] ভাঙে code point ধরে। আর মানুষের চোখে এক “অক্ষর” — যেমন “লা” (ল + া-কার) — একটি grapheme cluster, যা ধরতে লাগে Intl.Segmenter (পর্ব ০৯)।
পদ্ধতি ও জটিলতা: brute force: দুটিকেই সাজিয়ে তুলনা — O(n log n). উন্নত: একটি Map-এ প্রথমটির অক্ষর গুনুন, দ্বিতীয়টির জন্য কমান; কোথাও শূন্যের নিচে নামলে false. সময় O(n), মেমরি O(ভিন্ন অক্ষর). একই কোডে সমস্যা ৪ · Group anagrams (মাঝারি)-ও আছে: প্রতিটি শব্দের অক্ষর সাজিয়ে একটি “পরিচয়” বানান (“tea” → “aet”), সেই পরিচয়কে key করে Map-এ দল বাঁধুন — O(n · k log k), k = শব্দের দৈর্ঘ্য।
function isAnagram(s, t, split = (x) => Array.from(x)) {
const a = split(s), b = split(t);
if (a.length !== b.length) return false;
const freq = new Map();
for (const ch of a) freq.set(ch, (freq.get(ch) ?? 0) + 1);
for (const ch of b) {
const n = freq.get(ch);
if (!n) return false; // undefined or 0
freq.set(ch, n - 1);
}
return true;
}
const seg = new Intl.Segmenter("bn", { granularity: "grapheme" });
const graphemes = (x) => Array.from(seg.segment(x), (p) => p.segment);
console.log(isAnagram("listen", "silent"), isAnagram("rat", "car"));
console.log("😀a".split("").length, Array.from("😀a").length);
console.log(isAnagram("কলা", "কাল"), isAnagram("কলা", "কাল", graphemes));
console.log(graphemes("কলা"), graphemes("কাল"));
function groupAnagrams(words) {
const groups = new Map(); // sorted letters -> words
for (const w of words) {
const key = Array.from(w).sort().join("");
if (!groups.has(key)) groups.set(key, []);
groups.get(key).push(w);
}
return [...groups.values()];
}
console.log(JSON.stringify(groupAnagrams(["eat", "tea", "tan", "ate", "nat", "bat"])));আউটপুট: true false; তারপর 3 2 — split("") emoji-কে দুই টুকরো করেছে, Array.from করেনি। সবচেয়ে মজার লাইন true false: code point ধরে “কলা” (ক, ল, া) আর “কাল” (ক, া, ল) anagram — একই তিনটি code point. কিন্তু grapheme ধরে [ 'ক', 'লা' ] আর [ 'কা', 'ল' ] আলাদা, তাই anagram নয়। কোনটা “ঠিক”? সেটা সমস্যার সংজ্ঞা — ঠিক এ জন্যই শুরুতে প্রশ্ন করতে হয়।
Group anagrams-এর ফল [["eat","tea","ate"],["tan","nat"],["bat"]] — Map ঢোকানোর ক্রম রাখে, তাই দলগুলোর ক্রম প্রথম দেখার ক্রমে, প্রতিবার একই। ফলো-আপ: শুধু ছোট হাতের ইংরেজি অক্ষর হলে সাজানোর বদলে ২৬ ঘরের গণনা থেকে key বানান (যেমন "1#0#0#…") — প্রতি শব্দে O(k). Map-এর freq.get(ch) ?? 0-এ ?? (পর্ব ০৮) দরকার, কারণ না-থাকা key-তে get দেয় undefined.
সমস্যা ৩ · পুনরাবৃত্তিহীন দীর্ঘতম substring (মাঝারি)
প্রশ্ন: একটি string-এর এমন দীর্ঘতম টানা অংশ বের করুন, যেখানে কোনো অক্ষর দুবার নেই। “abcabcbb” → 3 (“abc”), “pwwkew” → 3 (“wke”).
জিজ্ঞেস করে নিন: substring (টানা অংশ) নাকি subsequence (মাঝে বাদ দেওয়া চলে)? শুধু দৈর্ঘ্য, নাকি অংশটাও? খালি string-এ উত্তর 0 তো?
Brute force: প্রতিটি শুরু থেকে যতদূর ডুপ্লিকেট ছাড়া যাওয়া যায় — একটি Set দিয়ে O(n²). উন্নত: sliding window. একটি জানালা [left, right] ডানে বাড়ান; নতুন অক্ষরটি জানালার ভেতরে আগে থাকলে left-কে তার আগের অবস্থানের ঠিক পরে সরান। প্রতিটি অক্ষরের শেষ দেখা index একটি Map-এ রাখুন। সময় O(n), মেমরি O(ভিন্ন অক্ষর). নিচে “abba” দিয়ে ধাপে ধাপে দেখুন — এখানে একটি সূক্ষ্ম ফাঁদ আছে:
1function longestUnique(s) {2 const last = new Map();3 let best = 0, left = 0;4 for (let right = 0; right < s.length; right++) {5 const ch = s[right];6 if (last.has(ch)) left = Math.max(left, last.get(ch) + 1);7 last.set(ch, right);8 best = Math.max(best, right - left + 1);9 }10 return best;11}
| right | |
| ch | |
| last.get(ch) | |
| left | |
| last | |
| best |
- প্রস্তুতি। Map খালি — কোনো অক্ষর এখনো দেখা হয়নি। জানালা খালি,
best= 0. - right = 0, 'a'. 'a' আগে দেখা হয়নি, তাই
leftবদলায় না। জানালা “a”, দৈর্ঘ্য ১,best= 1. - right = 1, 'b'. 'b' আগে দেখা হয়নি। জানালা “ab”,
best= 2. - right = 2, আবার 'b'. আগের 'b' ছিল index 1-এ, তাই
leftলাফিয়ে 2-এ যায় — জানালা এখন শুধু “b”. প্রতিটি অক্ষর জানালায় একবার ঢোকে,leftশুধু সামনে যায় — তাই মোট কাজ O(n). - right = 3, আবার 'a' — ফাঁদ। আগের 'a' ছিল index 0-এ, অর্থাৎ জানালার বাইরে।
Math.maxনা থাকলেleftপিছিয়ে 1-এ যেত, আর জানালা হতো “bba” — যেখানে 'b' দুবার!maxনিশ্চিত করেleftকখনো পেছায় না। জানালা “ba”, দৈর্ঘ্য ২। - শেষ। উত্তর 2. এমন ছোট, চতুর test (“abba”) নিজে থেকে বানানো ইন্টারভিউতে খুব ভালো সংকেত — এটি দেখায় আপনি কোডের নিয়ম (“জানালায় কোনো ডুপ্লিকেট নেই”) নিয়ে ভাবছেন, শুধু উদাহরণ মেলাচ্ছেন না।
function longestUnique(s) {
const chars = Array.from(s); // code points, not UTF-16 units
const last = new Map(); // char -> last index seen
let best = 0, start = 0, left = 0;
chars.forEach((ch, right) => {
if (last.has(ch)) left = Math.max(left, last.get(ch) + 1); // never moves back
last.set(ch, right);
if (right - left + 1 > best) [best, start] = [right - left + 1, left];
});
return [best, chars.slice(start, start + best).join("")];
}
for (const s of ["abcabcbb", "bbbbb", "pwwkew", "", "abba", "বাবা"]) {
console.log(JSON.stringify(s), "->", longestUnique(s));
}আউটপুট: [ 3, 'abc' ], [ 1, 'b' ], [ 3, 'wke' ], [ 0, '' ], [ 2, 'ab' ], আর “বাবা”-তে [ 2, 'বা' ]. খালি string-এ লুপ একবারও চলে না, তাই আলাদা if লাগে না — এ কথাটা মুখে বলুন। স্টেপারের সংস্করণ শুধু দৈর্ঘ্য দেয়; এখানে start মনে রেখে অংশটাও ফেরত দেওয়া হয়েছে — একটি সাধারণ ফলো-আপ।
JavaScript-এর খুঁটিনাটি: s[right] একটি UTF-16 unit দেয়; emoji থাকলে অর্ধেক অক্ষর আসত, তাই Array.from. এটা O(n) বাড়তি মেমরি নেয় — বিনিময়টা বলুন। grapheme ধরে কাজ করতে চাইলে সমস্যা ২-এর graphemes function বসালেই চলে। ফলো-আপ: “সর্বোচ্চ k টি ভিন্ন অক্ষর” রূপভেদে একই জানালা — গণনার Map রেখে, ভিন্ন অক্ষর k ছাড়ালে বাম প্রান্ত সরান।
সমস্যা ৫ · সবচেয়ে বেশি আসা k টি শব্দ (মাঝারি)
প্রশ্ন: শব্দের তালিকা থেকে সবচেয়ে বেশিবার আসা k টি শব্দ, বেশি থেকে কম ক্রমে। সমান হলে বর্ণানুক্রমে আগেরটি আগে।
জিজ্ঞেস করে নিন: সমান গণনায় কী হবে? k কি ভিন্ন শব্দের সংখ্যার চেয়ে বড় হতে পারে? তালিকা কি এত বড় যে মেমরিতে ধরে না?
পদ্ধতি ও জটিলতা: প্রথমে Map-এ গণনা — O(n). তারপর দুটি পথ। (ক) সব ভিন্ন শব্দ (m টি) গণনা অনুযায়ী সাজানো: O(n + m log m) — সরল, ইন্টারভিউতে প্রায়ই যথেষ্ট। (খ) bucket: কোনো গণনা n-এর বেশি হতে পারে না, তাই buckets[c]-এ “ঠিক c বার আসা শব্দ” রাখুন, তারপর বড় c থেকে নিচে নামুন — গণনার অংশ O(n), শুধু সমান গণনার শব্দগুলো সাজাতে বাড়তি খরচ। অন্য ভাষায় এখানে k আকারের min-heap-ও (O(n + m log k)) জনপ্রিয়, কিন্তু JavaScript-এ heap নিজে লিখতে হয় — ভাবনার প্রশ্ন ২ দেখুন।
function countWords(words) {
const freq = new Map();
for (const w of words) freq.set(w, (freq.get(w) ?? 0) + 1);
return freq;
}
// A) sort all distinct words: O(n + m log m)
function topKBySort(words, k) {
return [...countWords(words)]
.sort(([wa, ca], [wb, cb]) => cb - ca || wa.localeCompare(wb))
.slice(0, k)
.map(([w]) => w);
}
// B) bucket by count (a count is never more than n): O(n) plus tie sorting
function topKByBucket(words, k) {
const buckets = Array.from({ length: words.length + 1 }, () => []);
for (const [w, c] of countWords(words)) buckets[c].push(w);
const out = [];
for (let c = words.length; c > 0 && out.length < k; c--) {
const tied = buckets[c].sort((a, b) => a.localeCompare(b));
out.push(...tied.slice(0, k - out.length));
}
return out;
}
const words = ["js", "go", "js", "py", "go", "js", "rust"];
console.log(topKBySort(words, 2), topKByBucket(words, 2));
console.log(topKBySort(words, 3), topKByBucket(words, 3));
console.log(topKByBucket(words, 10), topKByBucket([], 2));আউটপুট: [ 'js', 'go' ] [ 'js', 'go' ], [ 'js', 'go', 'py' ] [ 'js', 'go', 'py' ], আর [ 'js', 'go', 'py', 'rust' ] []. py আর rust দুটোই একবার, কিন্তু বর্ণানুক্রমে py আগে — তাই k = 3-এ rust বাদ। k ভিন্ন শব্দের চেয়ে বড় হলে সব শব্দ আসে, error নয়। দুটি পদ্ধতির ফল একই — এভাবে দুটি সমাধান মিলিয়ে দেখাও এক ধরনের test.
comparator-এ cb - ca || wa.localeCompare(wb): গণনা আলাদা হলে প্রথম অংশ শূন্য নয়, তাই সেটাই ফল; সমান হলে 0 || … দ্বিতীয় তুলনায় যায়। Map-কে spread করলে [key, value] জোড়ার array পাওয়া যায়, আর destructuring (পর্ব ১৭) দিয়ে সরাসরি নাম দেওয়া যায়। ফলো-আপ: শব্দ অবিরাম stream-এ এলে বা মেমরিতে না ধরলে আনুমানিক গণনার কাঠামো (যেমন Count-Min Sketch) নিয়ে আলোচনা হতে পারে — নাম আর মূল ভাব জানলেই যথেষ্ট।
৫ · Stack, binary search, interval আর recursion
এই গোত্রে দুটি জিনিস একসাথে যাচাই হয়: ঠিক data structure চেনা, আর সীমানায় (প্রথম ঘর, শেষ ঘর, খালি ইনপুট, খুব গভীর ইনপুট) নির্ভুল থাকা। রিভিশনের জন্য: পর্ব ১৩ · function, পর্ব ১৪ · object আর reference, পর্ব ১৭ · destructuring আর spread, পর্ব ১৯ · JSON আর structuredClone, পর্ব ০৫ · call stack.
সমস্যা ৬ · বন্ধনী ঠিকঠাক মেলানো কি না (সহজ)
প্রশ্ন: (), [], {} মেশানো একটি string-এ প্রতিটি খোলা বন্ধনী কি সঠিক ক্রমে সঠিক ধরনের বন্ধনী দিয়ে বন্ধ হয়েছে? “([]{})” → true, “([)]” → false.
পদ্ধতি ও জটিলতা: শুধু গণনা রাখলে “([)]” ধরা পড়ে না — ক্রম দরকার। সবার শেষে খোলা বন্ধনীটিকেই সবার আগে বন্ধ হতে হয়: LIFO, অর্থাৎ stack. JavaScript-এ সাধারণ array-ই stack: push আর pop দুটোই O(1). সময় O(n), মেমরি O(n).
function isValid(s) {
const pairs = new Map([[")", "("], ["]", "["], ["}", "{"]]);
const stack = [];
for (const ch of s) {
if (ch === "(" || ch === "[" || ch === "{") stack.push(ch);
else if (pairs.has(ch) && stack.pop() !== pairs.get(ch)) return false;
} // other characters: ignored
return stack.length === 0; // "((" : something left open
}
// ["([]{})", "([)]", "((", "", "a(b)c", ")("].map(isValid)
// -> [ true, false, false, true, true, false ]যা খেয়াল করবেন: খালি array-এ pop() error দেয় না, undefined দেয় — তাই “)(”-এ undefined !== "(" হয়ে false আসে, আলাদা খালি-যাচাই লাগে না। তবে এটা বলুন, নইলে ইন্টারভিউয়ার ভাববেন আপনি কেসটা ভুলে গেছেন। pairs হিসেবে plain object-ও চলত, কিন্তু ch in pairs লিখলে prototype-এর key-ও ("toString") “আছে” ধরা হয় — Map বা Object.hasOwn নিরাপদ। ফলো-আপ: শুধু এক ধরনের বন্ধনী হলে stack-ও লাগে না — একটি গণনা যথেষ্ট, যা কখনো ঋণাত্মক হবে না। সমস্যাটি আসলে পর্ব ০৫-এর call stack-এর ছোট মডেল: function কলও এভাবেই খোলে আর উল্টো ক্রমে বন্ধ হয়।
সমস্যা ৭ · Binary search — প্রথম অবস্থান খোঁজা (মাঝারি)
প্রশ্ন: ছোট থেকে বড় সাজানো একটি array-এ x প্রথম কোন index-এ আছে? না থাকলে −1. [1, 3, 3, 3, 5]-এ 3 → 1.
পদ্ধতি ও জটিলতা: indexOf O(n) — সাজানো থাকার সুবিধা নেয় না। Binary search প্রতিবার অর্ধেক বাদ দেয়: O(log n) — দশ লাখ উপাদানেও প্রায় ২০টি তুলনা। সবচেয়ে নিরাপদ রূপ “lower bound”: প্রথম index, যেখানে মান x-এর সমান বা বড়। অর্ধ-খোলা পরিসর [lo, hi) নিলে সীমানার ভুল সবচেয়ে কম হয়।
// first index i with a[i] >= x (a sorted ascending); a.length if none
function lowerBound(a, x) {
let lo = 0, hi = a.length; // answer is inside [lo, hi]
while (lo < hi) {
const mid = lo + Math.floor((hi - lo) / 2); // or (lo + hi) >>> 1
if (a[mid] < x) lo = mid + 1;
else hi = mid;
}
return lo;
}
const firstIndex = (a, x) => {
const i = lowerBound(a, x);
return a[i] === x ? i : -1;
};
// lowerBound([1,3,3,3,5], 3) -> 1 (4 -> 4, 6 -> 5, [] -> 0)
// last index of 3 = lowerBound(a, 3 + 1) - 1 = 3 (for integers)JavaScript-এর খুঁটিনাটি: (lo + hi) / 2-এর ফল দশমিক হতে পারে, আর array-এ a[1.5] undefined দেয় — error নয়! তাই Math.floor বাধ্যতামূলক। Java/C-তে lo + hi overflow-এর ভয় থাকে; JavaScript-এ number-এ সেই ভয় নেই, কিন্তু (lo + hi) >> 1 লিখলে 32-bit-এ নামে — array-এর index-এ >>> 1 (unsigned) নিরাপদ। i শেষ ঘরের বাইরে গেলে a[i] undefined, তাই a[i] === x নিজেই false — আলাদা সীমা-যাচাই লাগে না। সংখ্যা ছাড়া অন্য কিছু (তারিখ, string) সাজানো থাকলে তুলনার অংশটা comparator হিসেবে নিন। ফলো-আপ: “উত্তরের ওপর binary search” — যেমন “সবচেয়ে কম কত গতিতে সব কাজ সময়মতো শেষ হয়?” — যেখানে array নেই, কিন্তু “এই মানে চলে কি না” প্রশ্নটা একমুখী।
সমস্যা ৮ · Interval জোড়া লাগানো (মাঝারি)
প্রশ্ন: [শুরু, শেষ] জোড়ার একটি তালিকা — যেমন একটি মিটিং রুমের বুকিং। যেগুলো একটি আরেকটির ওপর পড়ে, সেগুলো জোড়া লাগিয়ে ফেরত দিন। [[1,3],[2,6],[8,10],[15,18]] → [[1,6],[8,10],[15,18]].
জিজ্ঞেস করে নিন: [1,4] আর [4,5] কি জোড়া লাগবে (ছুঁয়ে থাকা)? ইনপুট কি সাজানো? ইনপুট array বদলানো চলবে?
পদ্ধতি ও জটিলতা: brute force: প্রতিটি জোড়া তুলনা করে বারবার মেলানো — O(n²) বা তার বেশি। উন্নত: শুরু অনুযায়ী সাজান; তারপর একবার বাঁ থেকে ডানে যান — বর্তমানটি আগেরটির শেষের আগে বা ঠিক শেষে শুরু হলে শেষটা বাড়ান, নইলে নতুন interval শুরু। সময় O(n log n) (সাজানোয়), মেমরি O(n).
function merge(intervals) {
const sorted = intervals.toSorted((a, b) => a[0] - b[0]); // input stays untouched
const out = [];
for (const [s, e] of sorted) {
const prev = out.at(-1);
if (prev && s <= prev[1]) prev[1] = Math.max(prev[1], e); // overlap or touch
else out.push([s, e]); // a new array, not an alias
}
return out;
}
const input = [[8, 10], [1, 3], [2, 6], [15, 18], [6, 7]];
console.log(JSON.stringify(merge(input)));
console.log(JSON.stringify(input));
console.log(JSON.stringify(merge([[1, 4], [4, 5]])), JSON.stringify(merge([])));
console.log(JSON.stringify(merge([[1, 10], [2, 3], [4, 5]])));আউটপুট: [[1,7],[8,10],[15,18]], তারপর মূল ইনপুট অপরিবর্তিত [[8,10],[1,3],[2,6],[15,18],[6,7]], [[1,5]] [], আর [[1,10]]. শেষ test-টি একটি ক্লাসিক বাগ ধরে: prev[1] = e লিখলে [1,10]-এর পরে [2,3] এসে শেষটা 3-এ নামিয়ে দিত — তাই Math.max.
দুটি সূক্ষ্ম mutation-ফাঁদ: (১) intervals.sort(...) লিখলে কলকারীর array বদলে যেত — toSorted (ES2023) বা [...intervals].sort(...). (২) out.push(interval) লিখলে out-এর ভেতরের array আর ইনপুটের ভেতরের array একই object হতো; পরে prev[1] = … করলে ইনপুটও বদলাত। [s, e] দিয়ে নতুন array বানানোয় সেটা হয়নি — দ্বিতীয় লাইনের আউটপুট তার প্রমাণ (পর্ব ১৪-এর reference). ফলো-আপ: “সবচেয়ে কম কয়টি রুম লাগবে?” — শুরু ও শেষ আলাদা সাজিয়ে দুই pointer দিয়ে গোনা।
সমস্যা ৯ · Nested array সমান করা — recursion বনাম iteration (মাঝারি)
প্রশ্ন: Array.prototype.flat ব্যবহার না করে যেকোনো গভীরতার nested array-কে এক স্তরে নামান: [1, [2, [3, [4]]], 5] → [1, 2, 3, 4, 5].
পদ্ধতি ও জটিলতা: স্বাভাবিক সমাধান recursion: উপাদান array হলে function নিজেকেই আবার ডাকে। সময় O(মোট উপাদান), কিন্তু call stack-এর গভীরতা = nesting-এর গভীরতা। ফলো-আপ প্রায় নিশ্চিত: “nesting এক লাখ স্তর হলে?” — তখন heap-এ নিজের stack রেখে iterative সমাধান।
// 1) recursive: short and clear, but call depth = nesting depth
function flatRec(arr, out = []) {
for (const x of arr) {
if (Array.isArray(x)) flatRec(x, out);
else out.push(x);
}
return out;
}
// 2) iterative: our own stack on the heap, no call-stack limit
function flatIter(arr) {
const stack = [...arr].reverse(), out = [];
while (stack.length) {
const x = stack.pop();
if (Array.isArray(x)) {
for (let i = x.length - 1; i >= 0; i--) stack.push(x[i]);
} else out.push(x);
}
return out;
}
const nested = [1, [2, [3, [4, [5]]]], 6, []];
console.log(flatRec(nested), flatIter(nested), nested.flat(Infinity));
let deep = [0];
for (let i = 1; i <= 100000; i++) deep = [deep, i]; // 100000 levels
try { flatRec(deep); } catch (e) { console.log("flatRec:", e.name); }
try { deep.flat(Infinity); } catch (e) { console.log("flat(Infinity):", e.name); }
const flat = flatIter(deep);
console.log("flatIter:", flat.length, flat.slice(0, 3), flat.at(-1));আউটপুট: প্রথম লাইনে তিনবার [ 1, 2, 3, 4, 5, 6 ] — খালি [] কিছুই যোগ করে না। তারপর flatRec: RangeError, আর flat(Infinity): RangeError — built-in flat-ও V8-এ এত গভীর ইনপুটে stack ফুরিয়ে ফেলে। শেষে flatIter: 100001 [ 0, 1, 2 ] 100000 — নিজের stack heap-এ থাকে, তাই এক লাখ স্তরেও ঠিক চলে।
iterative সংস্করণে উপাদানগুলো উল্টো ক্রমে push করা হয়, যাতে pop সবার বাঁয়েরটা আগে দেয় — ক্রম ঠিক থাকে। এই কৌশল (recursion → নিজের stack) যেকোনো DFS-এ খাটে। ফলো-আপ: “শুধু d স্তর পর্যন্ত সমান করুন” — stack-এ প্রতিটি উপাদানের সাথে তার গভীরতাও রাখুন। sparse array-এর ফাঁকা ঘর (hole) flat বাদ দেয়, এই কোড undefined বসায় — প্রশ্নকর্তার সাথে ঠিক করে নিন। JavaScript-এ ES2015 মানদণ্ডে proper tail call থাকলেও কার্যত শুধু Safari তা চালু করেছে, তাই tail recursion লিখে stack বাঁচানোর ভরসা করবেন না।
সমস্যা ১০ · Deep clone আর deep equal (মাঝারি → কঠিন)
প্রশ্ন: (ক) দুটি মান গভীরভাবে সমান কি না বলুন — === দুটি object-এর শুধু reference তুলনা করে। (খ) একটি object-এর deep copy বানান, যাতে কপি বদলালে মূলটি না বদলায়।
জিজ্ঞেস করে নিন: কোন টাইপ সামলাতে হবে — শুধু plain object আর array, নাকি Date, Map, Set, class instance-ও? cycle (নিজের দিকে ফেরা reference) থাকতে পারে? NaN কি NaN-এর সমান ধরব? {a: 1} আর {a: 1, b: undefined} কি সমান? এই প্রশ্নগুলোই সমস্যার আসল অংশ — ইন্টারভিউয়ার দেখেন আপনি কতগুলো কেস নিজে তোলেন।
পদ্ধতি: দুটোই recursion. cycle-এর জন্য একটি WeakMap-এ “কোন object ইতিমধ্যে দেখা হয়েছে” রাখুন — clone-এ মূল → কপি, equal-এ a → b. জটিলতা O(মোট property), মেমরি O(মোট object).
function deepEqual(a, b, seen = new WeakMap()) {
if (a === b || (Number.isNaN(a) && Number.isNaN(b))) return true;
if (typeof a !== "object" || typeof b !== "object" || !a || !b) return false;
if (Object.getPrototypeOf(a) !== Object.getPrototypeOf(b)) return false;
if (a instanceof Date) return a.getTime() === b.getTime();
if (seen.get(a) === b) return true; // cycle: pair already in progress
seen.set(a, b);
const ka = Object.keys(a), kb = Object.keys(b);
if (ka.length !== kb.length) return false;
return ka.every((k) => Object.hasOwn(b, k) && deepEqual(a[k], b[k], seen));
}
function deepClone(x, seen = new WeakMap()) {
if (typeof x !== "object" || x === null) return x;
if (x instanceof Date) return new Date(x);
if (seen.has(x)) return seen.get(x); // cycles and shared objects
const copy = Array.isArray(x) ? [] : Object.create(Object.getPrototypeOf(x));
seen.set(x, copy);
for (const k of Object.keys(x)) copy[k] = deepClone(x[k], seen);
return copy;
}
const order = { id: 7, items: [{ name: "চা", qty: 2 }], at: new Date(0), score: NaN };
order.self = order; // a cycle
const clone = deepClone(order);
console.log(deepEqual(order, clone), clone !== order, clone.self === clone);
clone.items[0].qty = 5;
console.log(order.items[0].qty, deepEqual(order, clone));
console.log(deepEqual([1, [2, 3]], [1, [2, 3]]), deepEqual({ a: 1 }, { a: 1, b: undefined }));
console.log(JSON.parse(JSON.stringify({ at: new Date(0), n: NaN, u: undefined })));
const sc = structuredClone(order);
console.log(sc.self === sc, sc.at instanceof Date);আউটপুট: true true true — কপি গভীরভাবে সমান, কিন্তু আলাদা object, আর cycle-টাও কপিতে নিজের দিকেই ফিরেছে। 2 false — কপির ভেতরের qty বদলালেও মূলটি 2-ই আছে। true false — array-এর তুলনা ঠিক; আর b: undefined-কে এই সংজ্ঞায় আলাদা ধরা হয়েছে (key-এর সংখ্যা আলাদা)। { at: '1970-01-01T00:00:00.000Z', n: null } — JSON দিয়ে কপি করলে Date string হয়ে যায়, NaN হয় null, undefined-এর key হারিয়ে যায়, আর cycle থাকলে TypeError. শেষে true true: built-in structuredClone cycle আর Date দুটোই ঠিক রাখে।
বাস্তবে কী বলবেন: “প্রোডাকশনে আমি structuredClone নিতাম (পর্ব ১৯) — তবে সেটা function কপি করে না (DataCloneError), আর class instance-এর prototype হারায়। নিজে লেখা সংস্করণে আমি prototype রেখেছি, কিন্তু Map, Set, getter, symbol key সামলাইনি — দরকার হলে যোগ করব।” কোন সীমা জেনেশুনে রেখেছেন, সেটা বলাই ভালো উত্তরের লক্ষণ। Object.is(0, -0) false, তাই NaN-এর জন্য আলাদা যাচাই রেখে === নেওয়া হয়েছে — এতে 0 আর −0 সমান ধরা হয়।
৬ · গ্রাফ আর নকশার প্রশ্ন — BFS, LRU cache, EventEmitter
এই গোত্রে কোড ছোট, কিন্তু সিদ্ধান্ত বেশি: কোন কাঠামো, কোন ক্রম, কোন ফেরত মান। রিভিশনের জন্য: পর্ব ১৮ · Map, Set, WeakMap, পর্ব ২৬ · class আর private field, পর্ব ৩২ · callback আর EventEmitter, পর্ব ৩৮ · event আর custom event, পর্ব ৫৩ · পারফরম্যান্স আর memory leak.
সমস্যা ১১ · গ্রিডে S থেকে E-তে সবচেয়ে ছোট পথ (মাঝারি)
প্রশ্ন: string-এর array দিয়ে একটি গ্রিড — S শুরু, E শেষ, # দেয়াল, . খালি ঘর। ওপর-নিচ-ডান-বাঁ এক ঘর করে গেলে সবচেয়ে কম কয় ধাপে E-তে পৌঁছানো যায়? না গেলে −1. ধরুন একটি গুদামের রোবট, বা মেট্রো স্টেশনের ম্যাপে সবচেয়ে কম মোড়ের পথ।
পদ্ধতি ও জটিলতা: প্রতিটি ধাপের খরচ সমান — তাই BFS: শুরু থেকে ১ ধাপের সব ঘর, তারপর ২ ধাপের সব ঘর… প্রথমবার যখন E-তে পৌঁছাই, সেটাই সবচেয়ে ছোট পথ। DFS এখানে ভুল উত্তর দিতে পারে — সে প্রথমে যে পথ পায়, সেটা দীর্ঘ হতে পারে। সময় ও মেমরি O(R × C).
function shortestPath(grid) {
const R = grid.length, C = grid[0]?.length ?? 0;
let start = -1, end = -1;
grid.forEach((row, r) => [...row].forEach((ch, c) => {
if (ch === "S") start = r * C + c;
if (ch === "E") end = r * C + c;
}));
if (start < 0 || end < 0) return -1;
const dist = new Array(R * C).fill(-1); // -1 = not visited yet
const queue = [start];
dist[start] = 0;
for (let head = 0; head < queue.length; head++) { // index, not shift()
const cell = queue[head], r = Math.floor(cell / C), c = cell % C;
if (cell === end) return dist[cell];
for (const [dr, dc] of [[1, 0], [-1, 0], [0, 1], [0, -1]]) {
const nr = r + dr, nc = c + dc, next = nr * C + nc;
if (nr < 0 || nc < 0 || nr >= R || nc >= C) continue;
if (grid[nr][nc] === "#" || dist[next] !== -1) continue;
dist[next] = dist[cell] + 1; // mark when queued
queue.push(next);
}
}
return -1;
}
console.log(shortestPath(["S.#.", "..#E", "...."]));
console.log(shortestPath(["S#E"]), shortestPath(["SE"]), shortestPath([]));আউটপুট: 6 — দেয়ালের নিচ দিয়ে ঘুরে যেতে হয়; তারপর -1 1 -1: দেয়ালে আটকানো, পাশাপাশি, আর খালি গ্রিড (grid[0]?.length ?? 0 খালি গ্রিডেও error দেয় না)।
তিনটি JavaScript-সিদ্ধান্ত, যা মুখে বলবেন: (১) queue-তে shift() নয় — সেটা O(n), তাই head index এগিয়ে নেওয়া; প্রতিটি ঘর একবার ঢোকে, একবার বের হয়। (২) ঘরের পরিচয় r * C + c একটি সংখ্যা — [r, c] array Set-এর key হিসেবে চলে না, কারণ দুটি আলাদা array reference-এ কখনো সমান নয়; `${r},${c}` string চলে, কিন্তু প্রতিবার নতুন string বানায়। (৩) ঘর queue-তে ঢোকানোর সময় চিহ্নিত করা — বের করার সময় করলে একই ঘর অনেকবার queue-তে ঢুকতে পারে। dist একসাথে “দেখা হয়েছে” আর “কত দূরে” দুটোই রাখে। ফলো-আপ: পথটাও চাই — প্রতিটি ঘরের “কোথা থেকে এলাম” রেখে শেষ থেকে পেছনে হাঁটুন। ধাপের খরচ আলাদা হলে BFS আর চলে না — Dijkstra, যার জন্য একটি heap লাগে।
সমস্যা ১২ · LRU cache — get আর put দুটোই O(1) (মাঝারি → কঠিন)
প্রশ্ন: নির্দিষ্ট ধারণক্ষমতার একটি LRU cache বানান: get(key) মান দেয় (না থাকলে −1), put(key, value) রাখে; ভরে গেলে সবচেয়ে আগে ব্যবহৃত entry বাদ যায়। দুটো কাজই গড়ে O(1).
পদ্ধতি: অন্য ভাষায় ক্লাসিক উত্তর hash map + doubly linked list. JavaScript-এ সুবিধা: Map ঢোকানোর ক্রম মনে রাখে, আর কোনো key মুছে আবার রাখলে সে ক্রমের শেষে যায়। তাই Map-এর প্রথম key = সবচেয়ে পুরোনো ব্যবহার। ইন্টারভিউয়ার “linked list দিয়ে নিজে লিখুন” বলতেও পারেন — তখন মূল ভাব একই, শুধু ক্রমটা নিজে রাখতে হয়।
class LRUCache {
#capacity;
#map = new Map(); // Map remembers insertion order
constructor(capacity) { this.#capacity = capacity; }
get(key) {
if (!this.#map.has(key)) return -1;
const value = this.#map.get(key);
this.#map.delete(key); // re-insert = now the newest
this.#map.set(key, value);
return value;
}
put(key, value) {
this.#map.delete(key);
this.#map.set(key, value);
if (this.#map.size > this.#capacity) {
const oldest = this.#map.keys().next().value; // first key = least recent
this.#map.delete(oldest);
}
}
keys() { return [...this.#map.keys()]; }
}
const cache = new LRUCache(2);
cache.put("a", 1);
cache.put("b", 2);
console.log(cache.get("a"), cache.keys()); // "a" becomes the newest
cache.put("c", 3); // evicts "b", not "a"
console.log(cache.get("b"), cache.keys());
cache.put("a", 10); // an update also refreshes
cache.put("d", 4); // evicts "c"
console.log(cache.get("a"), cache.get("c"), cache.keys());আউটপুট: 1 [ 'b', 'a' ] — get করার পর "a" শেষে (নতুন) চলে গেছে। -1 [ 'a', 'c' ] — "c" ঢোকায় সবচেয়ে পুরোনো "b" বাদ পড়েছে, "a" নয়। এটাই LRU আর FIFO-র পার্থক্য: FIFO হলে "a" বাদ যেত। শেষে 10 -1 [ 'd', 'a' ].
#capacity আর #map private field (পর্ব ২৬) — বাইরে থেকে কেউ ক্রম নষ্ট করতে পারে না। map.keys().next().value iterator থেকে প্রথম key নেয় — পুরো array না বানিয়ে, O(1)-এ (পর্ব ২৭)। মান হিসেবে undefined রাখা গেলে “নেই” আর “undefined আছে” গুলিয়ে না যেতে has দিয়ে যাচাই করা হয়েছে। ফলো-আপ: প্রতিটি entry-র মেয়াদ (TTL); একাধিক request একসাথে একই key চাইলে একটিমাত্র fetch (চলমান Promise-টাই cache-এ রাখা); আর memory leak: সীমাহীন cache আসলে একটি leak (পর্ব ৫৩)।
সমস্যা ১৩ · নিজের EventEmitter (মাঝারি)
প্রশ্ন: on, off, once, emit সহ একটি ছোট EventEmitter লিখুন — Node.js-এর node:events বা ব্রাউজারের EventTarget-এর মূল ভাবের মতো (observer pattern). front-end ও Node দুই ধরনের ইন্টারভিউতেই এ ধরনের প্রশ্ন প্রায়ই আসে।
class Emitter {
#handlers = new Map(); // event -> array of functions
on(event, fn) {
if (!this.#handlers.has(event)) this.#handlers.set(event, []);
this.#handlers.get(event).push(fn);
return () => this.off(event, fn); // handy "unsubscribe" function
}
off(event, fn) {
const list = this.#handlers.get(event) ?? [];
const i = list.indexOf(fn);
if (i !== -1) list.splice(i, 1);
}
once(event, fn) {
const off = this.on(event, (...args) => { off(); fn(...args); });
return off;
}
emit(event, ...args) {
const list = [...(this.#handlers.get(event) ?? [])]; // copy first!
for (const fn of list) fn(...args);
return list.length > 0;
}
}
// const bus = new Emitter();
// const stop = bus.on("pay", (amt) => console.log("log", amt));
// bus.once("pay", (amt) => console.log("sms once", amt));
// bus.emit("pay", 500) -> prints "log 500", "sms once 500", returns true
// stop(); bus.emit("pay", 700) -> false (no handlers left)যে সিদ্ধান্তগুলো মুখে বলবেন: (১) emit চালানোর আগে তালিকার কপি — once-এর handler চলার মাঝেই নিজেকে তালিকা থেকে সরায়; মূল array-এর ওপর লুপ চললে পরের handler বাদ পড়ে যেত। (২) handler-এর তালিকা array, Set নয় — Node-এর EventEmitter-এর মতো একই function দুবার লাগালে দুবার চলে; Set নিলে ডুপ্লিকেট আপনাআপনি বাদ যেত। trade-off-টা বলুন। (৩) on একটি unsubscribe function ফেরত দেয় — closure (পর্ব ২২)-এর চমৎকার ব্যবহার, React-এর effect cleanup-এর মতো। ফলো-আপ: once দিয়ে লাগানো handler মূল fn দিয়ে off করা যায় না (তালিকায় আছে wrapper) — Node এ জন্য wrapper-এ মূল function-এর reference রাখে। একটি handler error ছুড়লে বাকিরা চলবে কি? আর বহু listener জমে গেলে memory leak — Node এ জন্য একটি event-এ ডিফল্টে ১০টির বেশি listener হলে সতর্কবার্তা দেয়। মন্তব্যে দেখানো আউটপুটটি প্রত্যাশিত আউটপুট — কোডটি console-এ কপি করে চালিয়ে মিলিয়ে নিতে পারেন।
৭ · Async আর front-end machine coding
JavaScript ইন্টারভিউর সবচেয়ে “নিজস্ব” অংশ এটি — অন্য ভাষার ইন্টারভিউতে এ ধরনের প্রশ্ন কম আসে। এখানে দেখা হয় আপনি event loop, timer, Promise আর DOM আসলে বোঝেন কি না। রিভিশনের জন্য: পর্ব ৩১ · event loop, পর্ব ২২ · closure, পর্ব ৩৩ · Promise আর combinator, পর্ব ৩৪ · async/await, পর্ব ৩৬ · AbortController, debounce আর throttle, পর্ব ৩৭ · DOM, পর্ব ৩৯ · form, পর্ব ৪৩ · XSS.
সমস্যা ১৪ · Debounce আর throttle লিখুন (সহজ → মাঝারি)
প্রশ্ন: (ক) debounce: দ্রুত আসতে থাকা কলের মধ্যে শুধু শেষটি চলবে, শেষ কলের wait ms পরে — search box-এ টাইপ থামলে খোঁজা। (খ) throttle: প্রতি wait ms-এ বড়জোর একবার — scroll বা resize-এ। পর্ব ৩৬-এ দুটোই চালিয়ে দেখেছেন; ইন্টারভিউতে এগুলো ২–৩ মিনিটে, ব্যাখ্যাসহ লেখার অভ্যাস চাই।
function debounce(fn, wait) {
let timer; // lives in the closure
return function (...args) {
clearTimeout(timer); // a new call cancels the old one
timer = setTimeout(() => fn.apply(this, args), wait);
};
}
function throttle(fn, wait) {
let last = -Infinity; // leading edge only
return function (...args) {
const now = performance.now();
if (now - last >= wait) {
last = now;
fn.apply(this, args);
}
};
}যা দেখা হয়: (১) timer আর last closure-এ থাকে — প্রতিটি debounced function-এর নিজস্ব অবস্থা। (২) সাধারণ function আর fn.apply(this, args) — যাতে method হিসেবে ব্যবহার করলে this আর সব argument ঠিক যায় (পর্ব ২৪); arrow function ফেরত দিলে বাইরের this আটকে যেত। (৩) এই throttle শুধু “leading edge”: থামার পরের শেষ কলটি হারায়। ফলো-আপে প্রায়ই আসে: trailing কলও চাই, cancel() আর flush() চাই, প্রথম কলেই চালাতে চাই (leading: true). সময় মাপতে Date.now()-এর বদলে performance.now() — ঘড়ি পেছালে বা এগোলেও এটা একমুখী বাড়ে। সবচেয়ে সাধারণ ভুল: প্রতি render-এ নতুন করে debounce(fn) ডাকা — তাহলে প্রতিবার নতুন closure, নতুন timer, আর debounce কাজই করে না।
সমস্যা ১৫ · Promise.all আর promise pool নিজে লিখুন (মাঝারি → কঠিন)
প্রশ্ন: (ক) Promise.all-এর মতো একটি function লিখুন: সব সফল হলে ফলের array মূল ক্রমে, একটি ব্যর্থ হলেই সাথে সাথে reject. (খ) ৫০০টি ছবি আপলোড করতে হবে, কিন্তু একসাথে বড়জোর ৩টি — একটি concurrency limit-সহ “promise pool” লিখুন।
পদ্ধতি: (ক) একটি গণনা রাখুন — কয়টি বাকি; প্রতিটি সফল হলে ফলটা তার index-এ বসান, বাকি শূন্য হলে resolve. খালি ইনপুটে সাথে সাথে []. (খ) limit সংখ্যক “worker” চালান; প্রত্যেকে একটি ভাগ করা next index থেকে পরের কাজ তুলে নেয়, শেষ হলে আবার তোলে। JavaScript-এ main thread একটাই, তাই next++-এ দুই worker একই index পাবে না — দুটি await-এর মাঝের কোড একটানা চলে, অন্য কোনো worker মাঝপথে ঢুকতে পারে না।
function promiseAll(items) {
return new Promise((resolve, reject) => {
let left = items.length; const results = new Array(left);
if (left === 0) return resolve(results);
items.forEach((item, i) => {
Promise.resolve(item).then((v) => {
results[i] = v; // input order, not finish order
if (--left === 0) resolve(results);
}, reject); // the first rejection wins
});
});
}
async function promisePool(tasks, limit) {
const results = new Array(tasks.length);
let next = 0, running = 0, peak = 0;
async function worker() {
while (next < tasks.length) {
const i = next++; // no race: one callback at a time
peak = Math.max(peak, ++running);
try { results[i] = await tasks[i](); } finally { running--; }
}
}
await Promise.all(Array.from({ length: Math.min(limit, tasks.length) }, () => worker()));
return { results, peak };
}
const wait = (ms, v) => new Promise((r) => setTimeout(() => r(v), ms));
promiseAll([wait(30, "a"), 2, wait(10, "c")]).then((r) => console.log("all:", r));
promiseAll([wait(10, 1), Promise.reject(new Error("boom"))]).catch((e) => console.log("rejected:", e.message));
const tasks = [50, 10, 30, 20, 40].map((ms, i) => () => wait(ms, `t${i}`));
promisePool(tasks, 2).then(({ results, peak }) => console.log("pool:", results, "peak:", peak));আউটপুট: rejected: boom সবার আগে — reject হওয়া promise-এর handler microtask-এ চলে, কোনো timer-এর অপেক্ষা নেই (পর্ব ৩১)। তারপর ৩০ ms-এ all: [ 'a', 2, 'c' ] — "c" আগে শেষ হলেও ফল মূল ক্রমে, আর সাধারণ মান 2-ও Promise.resolve দিয়ে চলে। শেষে pool: [ 't0', 't1', 't2', 't3', 't4' ] peak: 2 — কাজগুলো ভিন্ন সময়ে শেষ হলেও ফল index অনুযায়ী, আর একসাথে কখনো ২টির বেশি চলেনি।
সূক্ষ্ম বিষয়: pool-এ tasks হলো function-এর array, promise-এর নয় — promise তৈরি হওয়া মাত্রই কাজ শুরু হয়ে যায়, তাই সীমা মানতে হলে কাজ শুরু করাটা আমাদের হাতে রাখতে হয়। এটা না বললে উত্তর অর্ধেক। reject হলে মূল Promise.all বাকি কাজ থামায় না — ফল শুধু উপেক্ষা করে; থামাতে চাইলে AbortController (পরের সমস্যা)। ফলো-আপ: allSettled-এর মতো সব ফল চাই; ব্যর্থ কাজ retry with backoff; চলতে চলতে নতুন কাজ যোগ — পর্ব ৩৩ আর ৩৬।
সমস্যা ১৬ · Machine coding: typeahead — debounce, abort আর race-safety (মাঝারি → কঠিন)
প্রশ্ন: একটি search box বানান, যেখানে টাইপ করলে জেলার নামের প্রস্তাব দেখায়। শর্ত: প্রতিটি অক্ষরে request নয় (debounce); নতুন request গেলে পুরোনোটা বাতিল (AbortController); আর পুরোনো উত্তর দেরিতে এলে সেটা যেন নতুন ফলকে মুছে না দেয় — একটি ক্লাসিক race condition. screen reader ব্যবহারকারীর জন্যও যেন কাজ করে।
নিচের কোডে নকল API ইচ্ছা করেই ছোট query-তে ধীরে উত্তর দেয় — তাই সুরক্ষা না থাকলে “ra”-র উত্তর “ran”-এর পরে এসে ভুল তালিকা দেখাত। টাইপ করাটাও কোড দিয়ে নকল করা হয়েছে, যাতে প্রতিবার একই আউটপুট আসে; চালানোর পর box-এ নিজে টাইপ করেও দেখতে পারেন।
const DISTRICTS = ["Dhaka", "Dinajpur", "Rajshahi", "Rangamati", "Rangpur", "Sylhet"];
function searchApi(q, signal) { // fake API: shorter query = SLOWER reply
return new Promise((resolve, reject) => {
const hits = DISTRICTS.filter((d) => d.toLowerCase().startsWith(q.toLowerCase()));
const t = setTimeout(() => resolve(hits), 600 - q.length * 100);
signal.addEventListener("abort", () => { clearTimeout(t); reject(signal.reason); });
});
}
const debounce = (fn, ms) => { let id; return () => { clearTimeout(id); id = setTimeout(fn, ms); }; };
const box = document.querySelector("#q"), list = document.querySelector("#list");
let controller = null, latest = 0;
box.addEventListener("input", debounce(async () => {
const q = box.value.trim(), my = ++latest;
controller?.abort(); controller = new AbortController(); // cancel the older one
if (!q) { list.replaceChildren(); box.setAttribute("aria-expanded", "false"); return console.log("cleared"); }
console.log(`request "${q}"`);
try {
const items = await searchApi(q, controller.signal);
if (my !== latest) return; // only the newest may render
list.replaceChildren(...items.map((d) => Object.assign(document.createElement("li"), { textContent: d, role: "option" })));
box.setAttribute("aria-expanded", String(items.length > 0));
console.log(`render "${q}": ${items.join(", ") || "(none)"}`);
} catch (e) { if (e.name === "AbortError") console.log(`aborted "${q}"`); else throw e; }
}, 150));
const typeAt = (ms, text) => setTimeout(() => { box.value = text; box.dispatchEvent(new Event("input")); }, ms);
typeAt(0, "r"); typeAt(50, "ra"); typeAt(300, "ran"); typeAt(900, "");আউটপুট: request "ra" — "r" আর "ra" ৫০ ms-এর ব্যবধানে এসেছিল, debounce দুটোকে একটি request বানিয়েছে। তারপর request "ran", আর ঠিক পরে aborted "ra" — নতুন request যাওয়ার মুহূর্তে পুরোনোটা বাতিল (reject-এর handler microtask-এ চলে বলে বার্তাটা এক লাইন পরে আসে)। তারপর render "ran": Rangamati, Rangpur, আর box খালি হলে cleared. ছবিতে তালিকাটি শেষে খালি, কারণ শেষ ধাপে box মুছে ফেলা হয়েছে।
দুই স্তরের সুরক্ষা কেন: AbortController request বাতিল করে, কিন্তু সব API সাথে সাথে থামে না, আর কিছু কাজ (যেমন cache থেকে উত্তর) বাতিলই করা যায় না। তাই my !== latest — “শুধু সবশেষ request-এর উত্তর দেখানো যাবে” — দ্বিতীয় পাহারা। লেখা বসানো হয়েছে textContent দিয়ে, innerHTML নয় — সার্ভারের ডেটায় HTML থাকলেও XSS হবে না (পর্ব ৪৩)। ARIA-র role="combobox", aria-expanded, aria-controls screen reader-কে বলে এটা প্রস্তাবসহ একটি ইনপুট। ফলো-আপ: ↑↓ দিয়ে প্রস্তাবে যাওয়া আর aria-activedescendant; Enter-এ বাছাই, Escape-এ বন্ধ; ফলাফল cache (সমস্যা ১২-এর LRU!); “লোড হচ্ছে” আর error অবস্থা; ম্যাচ করা অংশ <mark> দিয়ে চিহ্নিত করা।
Machine coding round-এর কৌশল
- প্রথম ৫ মিনিট প্রশ্ন আর পরিকল্পনা: কোন feature বাধ্যতামূলক, কোনটা “সময় থাকলে”? framework চলবে, নাকি plain JavaScript? মুখে অবস্থা (state) আর ধাপগুলো সাজিয়ে নিন: ডেটা → অবস্থা → render → event.
- আগে চলমান, পরে সুন্দর: সবচেয়ে সরল একটি সংস্করণ ১৫–২০ মিনিটে চালু করুন, তারপর ধাপে ধাপে যোগ করুন। শেষ মুহূর্তে কিছুই না চলার চেয়ে অর্ধেক feature-এর চলমান অ্যাপ অনেক ভালো।
- অবস্থা এক জায়গায়: DOM থেকে অবস্থা পড়বেন না; একটি state object রাখুন, আর একটি
render()function সেটা থেকে DOM বানাক। পরে React-এ গেলে এই চিন্তাটাই কাজে লাগবে (পর্ব ৫৪)। - event delegation, textContent, cleanup: তালিকার প্রতিটি আইটেমে আলাদা listener নয় — parent-এ একটি (পর্ব ৩৮); ব্যবহারকারীর লেখা
textContent-এ; timer আর listener সরানোর ব্যবস্থা। - accessibility ভুলবেন না:
<button>-এর জায়গায়<div onclick>নয়; label, keyboard দিয়ে চালানো, focus দেখা যাওয়া। tabs বা accordion-এrole="tab",aria-selected,aria-expanded— ছোট জিনিস, কিন্তু অভিজ্ঞ ইন্টারভিউয়ার খেয়াল করেন। - শেষ ৫ মিনিট: কী বানিয়েছেন, কী বাদ রেখেছেন আর কেন, সময় পেলে কী করতেন — নিজে থেকে বলুন।
Take-home assignment — কোডের মান কীভাবে দেখাবেন
take-home-এ সময়ের চাপ কম, তাই প্রত্যাশা বেশি: এখানে দেখা হয় আপনার “স্বাভাবিক” কাজের মান। কয়েকটি বিষয় প্রায় সব মূল্যায়নকারী খেয়াল করেন:
- README: কীভাবে চালাবেন (একটি বা দুটি কমান্ড), কী কী করেছেন, কোন অনুমান করেছেন, কী বাদ রেখেছেন আর কেন। মূল্যায়নকারী প্রথমে এটাই পড়েন।
- পরিষ্কার কাঠামো: ছোট module (পর্ব ৪৫), অর্থপূর্ণ নাম, ESLint ও Prettier (পর্ব ৪৬), কোনো পড়ে থাকা
console.logবা মন্তব্য করে রাখা কোড নয়। - test: মূল যুক্তির কয়েকটি unit test, আর একটি-দুটি edge case-এর test (পর্ব ৪৮)। শত test নয় — ঠিক জায়গায় অল্প test.
- ভুল সামলানো: নেটওয়ার্ক ব্যর্থ হলে, খালি ডেটা এলে, ভুল ইনপুট দিলে অ্যাপ কী করে (পর্ব ২৯, ৩৯)? “happy path”-এর বাইরের এই অংশই অভিজ্ঞতার পরিচয়।
- ছোট, অর্থপূর্ণ commit: Git ইতিহাস দেখায় আপনি কীভাবে কাজ ভাগ করেন। একটিমাত্র “final” commit দুর্বল সংকেত।
- সময়সীমা মানুন: বলা হয়েছে ৪ ঘণ্টা — তিন দিন খেটে “নিখুঁত” করার চেয়ে সীমার মধ্যে গুছিয়ে কাজ আর README-তে “আরও সময় পেলে যা করতাম” লেখা ভালো। AI ব্যবহারের নিয়ম আগে জেনে নিন, আর জমা দেওয়া প্রতিটি লাইন ব্যাখ্যা করতে পারবেন কি না নিশ্চিত হোন — পরের ধাপে সেটাই জিজ্ঞেস করা হয়।
৮ · ভাবনার প্রশ্ন — অচেনা সমস্যা আর trade-off
কোড লেখার পর ইন্টারভিউয়ার প্রায়ই জিজ্ঞেস করেন, “এটা কেন এভাবে লিখলেন?” — আর কিছু প্রশ্ন আছে, যা প্রস্তুতির ধরনই ঠিক করে দেয়। ভালো উত্তরের কাঠামো: এক লাইনে সারকথা → কারণ → একটি ফাঁদ বা বাস্তব উদাহরণ। প্রতিটি আগে নিজে ভাবুন, তারপর খুলুন।
প্র ০১একদম অচেনা একটি সমস্যা পেলে কোথা থেকে শুরু করবেন? “ছাঁচ চেনা” আসলে কী? (মাঝারি)
প্রথম কথা: অচেনা সমস্যায় আটকে যাওয়া ব্যর্থতা নয় — ইন্টারভিউয়ার প্রায়ই ইচ্ছা করেই এমন সমস্যা দেন, যাতে দেখা যায় আপনি না-জানা অবস্থায় কীভাবে এগোন। মুখস্থ সমাধান মনে পড়া আর সমস্যা সমাধান করা আলাদা দক্ষতা; দ্বিতীয়টিই মাপা হয়।
শুরু করুন সাত ধাপের প্রথম দুটি দিয়ে: প্রশ্ন করে সীমা জানুন (n কত বড়, ইনপুট সাজানো কি না), তারপর হাতে ছোট ছোট উদাহরণ চালান। দুই-তিনটি উদাহরণ হাতে সমাধান করলে প্রায়ই নিজের মাথার “অ্যালগরিদম” ধরা পড়ে — সেটাকে কথায় বলুন, তারপর সেটাই brute force. ইনপুটের আকারও একটি সংকেত: n ২০-এর মতো ছোট হলে সব সম্ভাবনা দেখা (2n) চলতে পারে; কয়েক হাজার হলে O(n²) হয়তো চলে; লাখের ঘরে হলে O(n log n) বা O(n) লাগবে।
এরপর ছাঁচের সংকেত খুঁজুন। “সাজানো array” শুনলে binary search বা two pointers; “টানা অংশ (substring/subarray)” শুনলে sliding window বা prefix sum; “আগে দেখেছি কি না / জোড়া খোঁজা” শুনলে Map/Set; “সবচেয়ে বেশি/কম k টি” শুনলে sort, bucket বা heap; “ন্যূনতম ধাপ, সমান খরচ” শুনলে BFS; “সব সমাবেশ/বিন্যাস” শুনলে backtracking; “একই ছোট প্রশ্ন বারবার” শুনলে memoization বা DP; “বন্ধনী, undo, nesting” শুনলে stack. এই পাতার ১৬টি সমস্যা আসলে প্রায় দশটি ছাঁচ। প্রতিটি ছাঁচের ৫–১০টি সমস্যা নিজে করলে নতুন সমস্যায় “এটা কোন ছাঁচ?” প্রশ্নটা প্রায় নিজে থেকেই আসে।
আটকে গেলে কৌশল: সমস্যাটি সহজ করুন (একমাত্রিক করুন, সীমা তুলে দিন, ছোট n ধরুন), সহজটি সমাধান করে তারপর মূলটিতে ফিরুন। আর নীরব থাকবেন না — “আমি ভাবছি সাজালে কিছু সহজ হয় কি না” বলাও তথ্য; ভালো ইন্টারভিউয়ার তখন ছোট একটি hint দেন, আর hint নিয়ে এগোনোও মূল্যায়নের অংশ।
মূল উপলব্ধি: অচেনা সমস্যায় পথ দেখায় পদ্ধতি — প্রশ্ন, হাতে উদাহরণ, brute force, তারপর ইনপুটের আকার আর ছাঁচের সংকেত মিলিয়ে উন্নতি।
প্র ০২JavaScript-এ কোনো built-in heap বা priority queue নেই — ইন্টারভিউতে “top-k” বা Dijkstra এলে কী করবেন? (কঠিন)
সত্যিই, ECMAScript মানদণ্ডে কোনো priority queue নেই — Python-এ heapq, Java-য় PriorityQueue, C++-এ std::priority_queue থাকলেও JavaScript-এ নেই। কিছু অনলাইন judge প্ল্যাটফর্ম JavaScript-এর জন্য একটি তৈরি priority queue লাইব্রেরি রাখে, কিন্তু সব জায়গায় নয় — তাই আগে জিজ্ঞেস করুন।
সিদ্ধান্তটা আসলে trade-off, আর সেটা মুখে বলাই ভালো উত্তর। পথ ১: সাজানো। top-k-তে সব উপাদান সাজালে O(n log n); ইন্টারভিউয়ের ইনপুটে এটা প্রায়ই যথেষ্ট আর লেখা সবচেয়ে সহজ, ভুলের সম্ভাবনা কম। বলুন: “heap দিয়ে O(n log k) হতো; আমি আগে সরল সংস্করণ লিখছি, সময় থাকলে heap লিখব।” পথ ২: সমস্যার বিশেষ গঠন কাজে লাগানো — যেমন সমস্যা ৫-এর bucket, কারণ গণনা n-এর বেশি হয় না; বা মানের পরিসর ছোট হলে counting sort. পথ ৩: নিজে binary heap লেখা — একটি array, যেখানে index i-এর সন্তান 2i+1 আর 2i+2, অভিভাবক (i - 1) >> 1; push-এ নতুন উপাদান নিচ থেকে ওপরে ওঠে (sift up), pop-এ শেষ উপাদান মাথায় বসিয়ে নিচে নামে (sift down). দুটোই O(log n), পুরো class ২৫–৩০ লাইন। অভ্যাস না থাকলে চাপের মধ্যে এখানেই index-এর ভুল হয়, তাই আগে থেকে কয়েকবার লিখে রাখুন।
Dijkstra-য় আরেকটি পরিচিত কৌশল: heap-এ “decrease key” না করে একই node নতুন দূরত্বে আবার ঢোকানো, আর বের করার সময় পুরোনো (বড়) দূরত্বের entry বাদ দেওয়া (“lazy deletion”). ইন্টারভিউতে এটা স্বীকৃত পদ্ধতি। গ্রিড ছোট হলে প্রতিবার সবচেয়ে ছোট দূরত্ব লিনিয়ার খুঁজে নেওয়া O(V²) সংস্করণও চলতে পারে — এবং সেটা স্পষ্ট করে বলুন।
এই প্রশ্ন আসলে একটি বড় শিক্ষা দেয়: ভাষার standard library জানা ইন্টারভিউর অংশ। কোন কাঠামো built-in আছে (Map, Set, array-as-stack), কোনটার খরচ লুকানো (shift), আর কোনটা নেই (heap, deque, sorted map) — এগুলো আগে থেকে জানলে চাপের মধ্যে সময় নষ্ট হয় না।
মূল উপলব্ধি: heap না থাকলে আগে সরল সমাধান (sort বা bucket) আর তার খরচ বলুন, তারপর দরকার হলে নিজের binary heap — আর এই বিনিময়ের যুক্তিটাই ইন্টারভিউয়ার শুনতে চান।
প্র ০৩ইন্টারভিউতে built-in (sort, flat, structuredClone, Promise.all) ব্যবহার করবেন, নাকি নিজে লিখবেন? (মাঝারি)
সংক্ষেপে: সমস্যার মূল যদি সেই কাজটাই হয়, তাহলে নিজে লিখুন; না হলে built-in ব্যবহার করুন, আর তার খরচ জানুন। “Promise.all লিখুন” প্রশ্নে Promise.all ডাকা চলবে না — কিন্তু promise pool-এর ভেতরে worker-দের অপেক্ষার জন্য Promise.all একদম স্বাভাবিক, যেমনটি সমস্যা ১৫-এ করা হয়েছে। interval জোড়া লাগাতে নিজের merge sort লেখার দরকার নেই; toSorted আর comparator যথেষ্ট, আর জটিলতায় শুধু বলুন “sort O(n log n)”.
দ্বিধা থাকলে জিজ্ঞেস করুন: “এখানে কি built-in flat ব্যবহার করতে পারি, নাকি আপনি নিজের সংস্করণ দেখতে চান?” এই একটি প্রশ্ন দুটো জিনিস দেখায় — আপনি built-in চেনেন, আর আপনি নির্দেশ মেনে চলেন। অনেক সময় উত্তর হয়, “আগে built-in দিয়ে লিখুন, পরে নিজে লিখে দেখান” — তখন দুটোই প্রস্তুত থাকা ভালো।
built-in ব্যবহারেরও দায় আছে। এক লাইনের কোড মানেই O(1) নয়: arr.includes লুপের ভেতরে দিলে O(n²); [...a, x] দিয়ে প্রতিবার নতুন array বানালে লুপে O(n²); JSON.parse(JSON.stringify(x)) দিয়ে কপি করলে Date, undefined, NaN, Map হারায়। আবার built-in-এর সূক্ষ্ম আচরণও জানতে হয়: ডিফল্ট sort string-ক্রমে, sort মূল array বদলায়, flat() ডিফল্টে মাত্র এক স্তর নামে, structuredClone function কপি করে না। ইন্টারভিউয়ার প্রায়ই ঠিক এগুলো নিয়েই ফলো-আপ করেন — “আপনার কোডে includes-এর খরচ কত?”
বাস্তব কাজে নিয়মটা আরও পরিষ্কার: যাচাই করা, পরীক্ষিত built-in আর মানসম্মত লাইব্রেরি নিজের লেখা সংস্করণের চেয়ে প্রায় সবসময় নিরাপদ। ইন্টারভিউতে নিজে লেখানো হয় শুধু ভেতরের যন্ত্রটা আপনি বোঝেন কি না দেখতে — যাতে built-in ভুল আচরণ করলে আপনি বুঝতে পারেন কেন।
মূল উপলব্ধি: মূল কাজটি নিজে লিখুন, চারপাশের কাজে built-in নিন — আর প্রতিটি built-in-এর জটিলতা ও ফাঁদ জেনে রাখুন, কারণ ফলো-আপ প্রশ্ন সেখান থেকেই আসে।
প্র ০৪Recursion না iteration — JavaScript-এ কোনটা কখন? tail call কি সমাধান? (কঠিন)
Recursion অনেক সমস্যায় স্বাভাবিক ভাষা: গাছ ঘোরা, nested ডেটা (JSON, DOM, ফাইলের ফোল্ডার), divide and conquer, backtracking. কোড ছোট, প্রায়ই সমস্যার সংজ্ঞার মতোই পড়া যায় — যেমন deep clone-এর সংজ্ঞা: “প্রতিটি property-কে আবার clone করা”। ইন্টারভিউতে প্রথম সংস্করণ হিসেবে recursion প্রায়ই সবচেয়ে পরিষ্কার পছন্দ।
কিন্তু JavaScript-এ এর একটা বাস্তব সীমা আছে: প্রতিটি কলে call stack-এ একটি frame, আর engine-এর stack সীমিত। কয়েক হাজার থেকে কয়েক দশ হাজার স্তরের পরে RangeError (V8-এ “Maximum call stack size exceeded”); সঠিক সংখ্যা engine, frame-এর আকার আর পরিবেশভেদে আলাদা, তাই সেই সংখ্যার ওপর নির্ভর করা যায় না। সমস্যা ৯-এ দেখেছেন, এক লাখ স্তরের nested array-এ নিজের recursion আর built-in flat(Infinity) দুটোই ভেঙেছে, আর heap-এ নিজের stack রাখা iterative সংস্করণ চলেছে। ব্যবহারকারীর দেওয়া ডেটার গভীরতা আপনার নিয়ন্ত্রণে নেই — তাই সার্ভারে গভীর nested JSON-এর মতো ইনপুটে recursion একটি সম্ভাব্য দুর্বলতাও।
Tail call কি সমাধান? ES2015 মানদণ্ডে strict mode-এ proper tail call আছে — অর্থাৎ function-এর শেষ কাজ যদি আরেকটি কল হয়, পুরোনো frame ফেলে দেওয়া যায়, stack বাড়ে না। কিন্তু কার্যত শুধু Safari-র engine এটি চালু করেছে; V8 (Chrome, Edge, Node) একসময় পরীক্ষামূলকভাবে চেষ্টা করে পরে সরিয়ে নেয় — debugging-এ stack trace হারানো আর কার্যক্ষমতা নিয়ে আপত্তির কারণে। তাই ইন্টারভিউতে “tail recursive করে দিলাম, এখন নিরাপদ” বলা ভুল তথ্য; বরং এটা জানা থাকলে বাড়তি নম্বর।
ব্যবহারিক নিয়ম: গভীরতা লগারিদমিক বা ছোট (ভারসাম্যপূর্ণ গাছ, বাইনারি সার্চ, ছোট গ্রিড) — recursion নিশ্চিন্তে। গভীরতা ইনপুটের আকারের সমান হতে পারে (linked list, একদিকে হেলে থাকা গাছ, বড় গ্রিডের DFS, অবিশ্বস্ত nested ডেটা) — নিজের stack দিয়ে iterative লিখুন, বা অন্তত ঝুঁকিটা বলুন। আর memoization-সহ recursion-এ (DP) একই কথা — বড় n-এ নিচ থেকে ওপরে লুপ (bottom-up) নিরাপদ।
মূল উপলব্ধি: recursion পরিষ্কার, কিন্তু JavaScript-এ গভীরতা সীমিত আর tail call-এর ভরসা নেই — গভীর বা অবিশ্বস্ত ইনপুটে নিজের stack দিয়ে iterative সমাধান লিখতে পারা চাই।
প্র ০৫LeetCode-ধাঁচের অ্যালগরিদম প্রশ্ন, নাকি machine coding — কোনটা আসল কাজের দক্ষতা বেশি মাপে? প্রস্তুতি কীভাবে ভাগ করবেন? (মাঝারি)
এটা সফটওয়্যার শিল্পের পুরোনো বিতর্ক। সমালোচকদের যুক্তি স্পষ্ট: প্রতিদিনের front-end বা Node কাজে খুব কম মানুষ নিজে হাতে sliding window বা Dijkstra লেখেন। তাঁরা বেশি সময় দেন পুরোনো কোড পড়ায়, বাগ খোঁজায়, API আর component নকশায়, দলের সাথে আলোচনায়। শুধু অ্যালগরিদম-ধাঁধা দিয়ে মাপলে এগিয়ে যান তাঁরা, যাঁরা কয়েকশো সমস্যা অনুশীলনের সময় পেয়েছেন — যা সবার জন্য সমান সুযোগ নয়।
অন্য পক্ষের যুক্তিও দুর্বল নয়। ৪৫ মিনিটে কারও আসল কাজ দেখা কঠিন; ছোট, স্পষ্ট সমস্যা অন্তত কয়েকটি জিনিস তুলনাযোগ্যভাবে দেখায় — সমস্যা ভেঙে ভাবা, জটিলতার বোধ, edge case, চাপের মধ্যে যোগাযোগ। আর জটিলতার বোধ front-end-এও কাজে লাগে: হাজার সারির তালিকায় প্রতিটি render-এ includes দিয়ে খোঁজা বা লুপে DOM পড়া-লেখা আসলেই অ্যাপ ধীর করে (পর্ব ৫৩)। এ কারণেই অনেক প্রতিষ্ঠান দুটো মিশিয়ে নেয়: একটি অ্যালগরিদম ধাপ, আর একটি বাস্তবধর্মী ধাপ — machine coding, পুরোনো কোডে বাগ খোঁজা, বা code review.
প্রস্তুতির ব্যবহারিক ভাগ: (১) অ্যালগরিদম — মুখস্থ নয়, ছাঁচ; প্রতিটি ছাঁচের অল্প কয়েকটি সমস্যা, পুরো সাত ধাপে, জোরে বলে। (২) JavaScript-এর গভীর ধারণা — event loop, closure, this, prototype, Promise; এগুলো পর্ব ৫৬–৫৮-এর প্রশ্নব্যাংকে আছে, আর machine coding-এর ভিত্তি এগুলোই। (৩) machine coding — typeahead, tabs, modal, infinite scroll, todo with filter, পেজিনেশন; প্রতিটি ৬০ মিনিটে plain JavaScript-এ, accessibility সহ। (৪) একটি সত্যিকারের প্রজেক্ট — পর্ব ৫৫-এর ক্যাপস্টোন বড় করে; design আর behavioural ধাপে সেটাই আপনার কথা বলার উপাদান। পদের ধরন দেখে ভাগ বদলান: front-end পদে (৩)-এ বেশি সময়, back-end পদে API নকশা, ডেটাবেস আর async-এ।
মূল উপলব্ধি: কোনো একটি ধরন নিখুঁত মাপকাঠি নয় — তাই ছাঁচ-ভিত্তিক অ্যালগরিদম, ভাষার গভীর ধারণা, machine coding আর একটি নিজের প্রজেক্ট মিলিয়ে প্রস্তুতি নিন।
প্র ০৬“সময় আর মেমরির বিনিময়” — ইন্টারভিউতে trade-off কীভাবে আলোচনা করবেন? (সহজ → মাঝারি)
প্রায় প্রতিটি উন্নত সমাধান কিছু একটা খরচ করে কিছু একটা কেনে। two sum-এ O(n) বাড়তি মেমরি দিয়ে সময় O(n²) থেকে O(n); LRU cache-এ মেমরি দিয়ে বারবার fetch-এর সময় বাঁচানো; longest substring-এ Array.from দিয়ে O(n) মেমরি খরচ করে বাংলা আর emoji-তে সঠিকতা। ইন্টারভিউয়ার শুনতে চান আপনি এই খরচ দেখতে পান আর পরিস্থিতি অনুযায়ী বাছতে পারেন — একটিমাত্র “সেরা” উত্তর প্রায়ই থাকে না।
আলোচনার একটি সহজ কাঠামো: “বিকল্প ক — এই খরচ, এই সুবিধা; বিকল্প খ — এই খরচ, এই সুবিধা; ইনপুট যদি এমন হয়, আমি ক নেব।” উদাহরণ: ইনপুট সাজানো থাকলে two sum-এ two pointers (O(1) মেমরি) ভালো; সাজানো না থাকলে সাজাতে O(n log n), আর মূল index-ও হারায় — তখন Map. top-k-তে k ছোট আর n বিশাল হলে heap; k প্রায় n-এর সমান হলে সোজা সাজানোই ভালো। ব্রাউজারে মেমরি কম এমন সস্তা ফোনও আছে — সেখানে বিশাল cache বা পুরো ডেটা মেমরিতে রাখার বিপক্ষে যুক্তিও বৈধ।
সময় আর মেমরির বাইরেও trade-off আছে, আর অভিজ্ঞ ইন্টারভিউয়ার সেগুলোও শুনতে ভালোবাসেন: পাঠযোগ্যতা বনাম ক্ষুদ্র গতির লাভ (bitwise কৌশল দিয়ে কয়েক ন্যানোসেকেন্ড বাঁচানো প্রায়ই মূল্যহীন); mutation বনাম কপি (ইনপুট বদলালে মেমরি বাঁচে, কিন্তু কলকারীর ডেটা নষ্ট হয় — React-এর মতো framework-এ এটা সরাসরি বাগ); সঠিকতার সংজ্ঞা (code point বনাম grapheme); আর সাধারণীকরণ বনাম সরলতা (deep clone-এ সব টাইপ সামলানো, নাকি শুধু যা দরকার)।
সবশেষে, জটিলতা বলার সময় “গড়ে” আর “খারাপ ক্ষেত্রে” আলাদা করুন: Map-এ খোঁজা গড়ে O(1); array-এর push amortized O(1) — মাঝে মাঝে পুরো array নতুন জায়গায় কপি হয়। এই সতর্ক ভাষা দেখায় আপনি Big-O-কে মুখস্থ লেবেল নয়, যুক্তি হিসেবে দেখেন।
মূল উপলব্ধি: সমাধান বাছাই মানে খরচ বাছাই — বিকল্পগুলো, তাদের খরচ আর কোন ইনপুটে কোনটা নেবেন, তা এক-দুই বাক্যে বলার অভ্যাস করুন।
৯ · AI দিয়ে প্রস্তুতি — সৎ ব্যবহার আর test দিয়ে যাচাই
ইন্টারভিউ চলাকালীন AI-এর সাহায্য অনেক প্রতিষ্ঠানেই নিষিদ্ধ — live coding-এ, online assessment-এ, অনেক সময় take-home-এও। কিছু প্রতিষ্ঠান আবার AI ব্যবহারের অনুমতি দিয়ে দেখতে চায় আপনি সেটি কীভাবে যাচাই করেন। নিয়ম প্রতিষ্ঠানভেদে আলাদা আর সময়ের সাথে বদলায় — তাই আগে জিজ্ঞেস করে নিন, আর অনুমতি না থাকলে ব্যবহার করবেন না। লুকিয়ে ব্যবহার অসততা; ধরা পড়লে শুধু ওই ইন্টারভিউ নয়, ভবিষ্যতের সুযোগও নষ্ট হতে পারে — আর ধরা না পড়লেও চাকরিতে গিয়ে সেই দক্ষতার ঘাটতি আপনাকেই সামলাতে হবে।
প্রস্তুতির সময় অবশ্য AI হতে পারে চমৎকার mock interview-এর সঙ্গী আর code review-এর সাহায্যকারী — শর্ত একটাই: উত্তর নয়, প্রশ্ন আর মূল্যায়ন চাইবেন। সমাধান সরাসরি চাইলে আপনি পড়ে “বুঝলাম” ভাববেন, কিন্তু নিজে বের করার পেশিটা তৈরি হবে না। নিচের prompt দিয়ে একটি পূর্ণ mock interview চালান:
AI-কে স্পষ্ট বলা হয়েছে সমাধান না দিতে, JavaScript-নির্দিষ্ট প্রশ্ন করতে, আর শেষে কঠোরভাবে মূল্যায়ন করতে:
নিজের লেখা সমাধান যাচাই করাতে আলাদা prompt দিন: “এই JavaScript সমাধানের বাগ, বাদ পড়া edge case আর জটিলতার ভুল খুঁজে দাও; ঠিক করা কোড লিখবে না, শুধু কোন লাইনে কী সমস্যা বলবে।” এতে ঠিক করার কাজটা আপনার হাতেই থাকে।
AI-এর সমাধান test দিয়ে যাচাই
AI-এর দেওয়া সমাধান আত্মবিশ্বাসী শোনায়, কিন্তু প্রায়ই সূক্ষ্ম ভুল থাকে: comparator ছাড়া sort(), “abba”-র মতো কেসে left পিছিয়ে যাওয়া, BFS-এ shift(), বা LRU-তে get করার পর ক্রম না বদলানো (তখন সেটা FIFO). সবচেয়ে কার্যকর যাচাই stress testing: একটি ধীর কিন্তু নিশ্চিতভাবে সঠিক brute force লিখুন, তারপর দুটিকে শত শত ছোট এলোমেলো ইনপুটে চালিয়ে ফল মেলান। brute force সহজে ঠিক লেখা যায়, তাই সে-ই বিচারক।
// a tiny seeded generator, so a failing case can be reproduced
function rng(seed) {
return () => (seed = (seed * 16807) % 2147483647) / 2147483647; // Park–Miller
}
const brute = (s) => { // O(n^3) but obviously correct
let best = 0;
for (let i = 0; i < s.length; i++)
for (let j = i; j < s.length; j++)
if (new Set(s.slice(i, j + 1)).size === j - i + 1) best = Math.max(best, j - i + 1);
return best;
};
const rand = rng(42);
for (let t = 0; t < 500; t++) {
const s = Array.from({ length: Math.floor(rand() * 8) }, () => "abc"[Math.floor(rand() * 3)]).join("");
if (brute(s) !== longestUnique(s)[0]) { console.log("MISMATCH:", s); break; }
}ছোট বর্ণমালা (“abc”) আর ছোট দৈর্ঘ্য ইচ্ছা করেই — এতে ডুপ্লিকেট বেশি হয়, আর “abba”-র মতো চতুর কেস নিজে থেকেই তৈরি হয়। seed দেওয়া generator থাকায় ভুল পেলে একই ইনপুট আবার বানানো যায় (গুণক 16807 আর মডিউলাস 231 − 1 হওয়ায় গুণফল 253-এর অনেক নিচে থাকে, তাই number-এর হিসাব নির্ভুল)। সমস্যা ৩-এর longestUnique পাশে রেখে চালালে কোনো MISMATCH আসে না; Math.max সরিয়ে দিলে কয়েক ধাপেই একটি ভুল ইনপুট ধরা পড়ে।
AI দিয়ে অনুশীলনের চেকলিস্ট
১০ · কোর্স সমাপ্তি — ৫৯টি পর্বের পরে আপনি কোথায়
এটি ৮টি মডিউল আর ৫৯টি পর্বের এই কোর্সের শেষ পর্ব। একবার পেছনে তাকান। পর্ব ১-এ প্রশ্ন ছিল, “JavaScript কেন?” পর্ব ৪-এ প্রথম console.log লিখেছিলেন, পর্ব ৫-এ দেখেছিলেন engine কীভাবে কোড চালায়। আজ আপনি Map দিয়ে একটি LRU cache বানাতে পারেন, একটি typeahead-এর race condition ধরতে পারেন, আর একটি অচেনা সমস্যাকে সাত ধাপে ভেঙে সমাধান করতে পারেন। আন্তরিক অভিনন্দন — এটা সত্যিই বড় অর্জন।
এখন আপনি যা পারেন
- ভিত্তি (পর্ব ০১–০৫): ব্রাউজার আর Node-এ JavaScript-এর জায়গা, HTML-CSS-JS-এর ভাগ, টুল সেটআপ, আর engine, JIT ও call stack-এর ভেতরের ছবি।
- মূল ভাষা (পর্ব ০৬–১৩): টাইপ ও coercion-এর ফাঁদ, scope আর TDZ, অপারেটর, বাংলা Unicode, floating-point ও BigInt, সিদ্ধান্ত, লুপ আর function.
- ডেটা স্ট্রাকচার (পর্ব ১৪–২১): object, array ও তার method, destructuring, Map/Set/WeakMap, JSON ও structuredClone, তারিখ ও Intl — আর মাঝপথের প্রজেক্টে সব একসাথে।
- ফাংশন ও অবজেক্ট গভীরে (পর্ব ২২–৩০): closure, functional programming, this, prototype, class, iterator ও generator, Proxy, error handling আর regular expression.
- Asynchronous JavaScript (পর্ব ৩১–৩৬): event loop, callback, Promise, async/await, fetch আর বাস্তব async নকশা — abort, retry, debounce, throttle.
- ব্রাউজার ও DOM (পর্ব ৩৭–৪৪): DOM, event, form, storage, Web API, Web Worker, নিরাপত্তা (XSS, CSP, CORS) আর PWA.
- পেশাদার JavaScript (পর্ব ৪৫–৫৫): module, npm ও tooling, TypeScript-এর শুরু, টেস্টিং, Node.js, Express API, real-time, ডেটাবেস, পারফরম্যান্স, framework — আর একটি full-stack ক্যাপস্টোন।
- পরীক্ষা ও ইন্টারভিউ (পর্ব ৫৬–৫৯): আউটপুট পড়া, MCQ, লিখিত ও ভাইভা প্রশ্ন, আর এই পর্বের কোডিং ইন্টারভিউ।
পরীক্ষা ও সার্টিফিকেট
লগইন করলে ড্যাশবোর্ডে শেখার সাতটি মডিউলের (পর্ব ০১–৫৫) প্রতিটির একটি টেস্ট আর একটি ফাইনাল পরীক্ষা পাবেন। শেখার এই সব পর্ব পড়া শেষ করে ফাইনালে ৬০% বা তার বেশি পেলে একটি ফ্রি কোর্স-সমাপ্তির সার্টিফিকেট পাবেন। প্রতিটি পর্বের “পড়েছি” বোতাম চাপা থাকলে আপনার অগ্রগতি সেখানে দেখা যাবে — অগ্রগতি সেভ হয় শুধু লগইন করা অবস্থায়। পরীক্ষার আগে পর্ব ৫৬–৫৮-এর প্রশ্নব্যাংক একবার ঘুরে আসুন।
অনুশীলন চালিয়ে যাওয়ার উপায়
- প্রতিদিন অল্প: দিনে একটি সমস্যা, পুরো সাত ধাপে — সপ্তাহে একদিন বড় সেশনের চেয়ে বেশি কাজে দেয়। LeetCode, HackerRank, Codeforces আর বাংলাদেশি প্ল্যাটফর্ম Toph-এর মতো জায়গায় এ ধরনের অনেক সমস্যা পাবেন; বেশিরভাগই JavaScript-এ জমা নেয়।
- একটি ভুলের খাতা: প্রতিটি ভুলের পর তিন লাইন — কোন ধারণা মিস করেছিলাম, কোন edge case ভুলেছিলাম, পরেরবার কোন সংকেত দেখে চিনব।
- সপ্তাহে একটি machine coding: ৬০ মিনিটের টাইমার ধরে একটি ছোট UI — tabs, modal, star rating, infinite scroll — plain JavaScript-এ, keyboard আর screen reader মাথায় রেখে।
- বন্ধুর সাথে mock interview: একজন প্রশ্ন করবেন, অন্যজন সমাধান করবেন, তারপর ভূমিকা বদল — মানুষের সামনে কথা বলার অভ্যাস AI-এর চেয়েও কাজে দেয়।
- মাঝে মাঝে ফিরে আসুন: কোর্স হোম থেকে যে পর্ব দুর্বল লাগে সেটি আবার পড়ুন।
এরপর কোন পথে
TypeScript আরও গভীরে
পর্ব ৪৭-এর পরের ধাপ: generics, narrowing, utility type — আজকাল বেশিরভাগ বড় JavaScript প্রজেক্ট TypeScript-এ লেখা হয়, তাই এটি সবচেয়ে কাজের পরের দক্ষতা।
React, Node ও full-stack
পর্ব ৫০ ও ৫৪-এর পরের ধাপ: component, state, routing, server, authentication আর deploy — পুরো ওয়েব-স্ট্যাকের ছবি।
Data Structures & Algorithms
heap, গাছ, গ্রাফ, DP-র গভীর ধরন — ইন্টারভিউর অ্যালগরিদম ধাপের জন্য সবচেয়ে সরাসরি প্রস্তুতি।
নিজের প্রজেক্ট আর portfolio
ক্যাপস্টোনে নতুন feature, আরও test, পরিষ্কার README — GitHub-এ রাখলে portfolio নিজে থেকেই বড় হয়, আর ইন্টারভিউতে বলার মতো গল্প জমে।
বোঝা, যাচাই করা আর ব্যাখ্যা করতে পারা — এগুলোই আপনাকে ইঞ্জিনিয়ার বানায়।
৫৯টি পর্ব শেষ করা সহজ কাজ নয় — আপনার ধৈর্য আর পরিশ্রমকে আন্তরিক অভিনন্দন। প্রথম undefined is not a function, প্রথম “কেন এই Promise আগে চলল?”, প্রথম CORS error — সেগুলোর সামনে যে বিভ্রান্তি ছিল, তা-ই আজ আপনার শক্তি। ইন্টারভিউতে একবার বা দুবার ব্যর্থ হওয়া খুব স্বাভাবিক; প্রতিটি থেকে একটি শিক্ষা খাতায় লিখে নিন, আর পরেরটিতে আরও প্রস্তুত হয়ে যান। শুভকামনা।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমকোডিং ইন্টারভিউতে একটি সমস্যা পেলে ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- ❓ প্রশ্ন করে সমস্যা ও সীমা পরিষ্কার করা
- 📝 নিজে উদাহরণ ও edge case লেখা
- 🐢 একটি সঠিক brute force সমাধান মুখে বলা
- 🧭 বারবার হওয়া কাজ খুঁজে উন্নত পদ্ধতি বাছা
- ⌨️ ইন্টারভিউয়ারের সম্মতি নিয়ে কোড লেখা
- 🔍 নিজের উদাহরণ দিয়ে dry run, তারপর জটিলতা বলা
(সহজ) JavaScript-এ [10, 1, 5, 100].sort() কী দেয়?
- [1, 5, 10, 100]
- [100, 10, 5, 1]
- [1, 10, 100, 5]
- TypeError
(সহজ) Map দিয়ে two sum সমাধানে (প্রতিটি উপাদানে “target - মান আগে দেখেছি কি?” যাচাই) সময় ও বাড়তি মেমরি কত?
- সময় O(n²), মেমরি O(1)
- সময় গড়ে O(n), মেমরি O(n)
- সময় O(n log n), মেমরি O(1)
- সময় O(1), মেমরি O(n)
(মাঝারি) JavaScript-এ গ্রিডের BFS-এ queue হিসেবে array নিয়ে প্রতিবার queue.shift() দিয়ে সামনের ঘর বের করলে মূল সমস্যা কী?
- shift() খালি array-এ TypeError ছোড়ে
- shift() শেষের উপাদান বের করে, তাই BFS ভুল উত্তর দেয়
- array-এ ১০০০-এর বেশি উপাদান রাখা যায় না
- প্রতিটি shift() O(n), তাই বড় গ্রিডে মোট কাজ O(n²) পর্যন্ত বাড়ে
(মাঝারি) JavaScript-এর Map দিয়ে LRU cache বানালে সবচেয়ে কম সাম্প্রতিক (least recently used) key কীভাবে পাওয়া যায়?
- map.keys().next().value — প্রতিবার ব্যবহারে key মুছে আবার set করলে
- key-গুলো সাজিয়ে সবচেয়ে ছোটটি
- [...map.keys()].at(-1)
- map.size - 1 index-এর key, বা map.oldest
(মাঝারি) Promise.all-এ দেওয়া তিনটি promise-এর দ্বিতীয়টি সবার আগে reject হলে কী ঘটে?
- বাকি দুটি শেষ হওয়ার পর সবার অবস্থার array দেয়
- বাকি দুটির ফল দিয়ে fulfilled হয়
- সাথে সাথে সেই কারণ দিয়ে reject হয়; বাকি দুটি থামে না, শুধু তাদের ফল উপেক্ষিত হয়
- বাকি দুটিকে বাতিল করে, তারপর reject হয়
(মাঝারি) একটি search box-এ টাইপ থামার ৩০০ ms পরে একবার খোঁজার request পাঠাতে কোন কৌশল ঠিক?
- Throttle
- Debounce
- setInterval দিয়ে প্রতি ৩০০ ms-এ
- Promise.race
(মাঝারি) ১০ লাখ (প্রায় 220) উপাদানের একটি সাজানো array-এ binary search সর্বোচ্চ প্রায় কয়টি তুলনায় শেষ হয়?
- প্রায় ২০টি
- প্রায় ১০০০টি
- প্রায় ৫ লাখ
- ১০ লাখ
(কঠিন) JavaScript-এ Number.MAX_SAFE_INTEGER + 2 (অর্থাৎ 9007199254740991 + 2) কী দেয়?
- একটি ঋণাত্মক সংখ্যা (overflow)
- RangeError
- 9007199254740993
- 9007199254740992
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব শব্দ এখানে একসাথে:
মনে রাখুন
- ইন্টারভিউয়ার দেখেন বোঝা, যোগাযোগ, সঠিকতা, জটিলতা, টেস্টিং আর কোডের মান — শুধু শেষ কোড নয়।
- প্রতিটি সমস্যায় সাত ধাপ: বুঝুন → উদাহরণ → brute force → উন্নত পদ্ধতি → কোড → পরীক্ষা → জটিলতা ও ফলো-আপ, আর পুরো সময় জোরে ভাবুন।
- “কোন কাজ বারবার হচ্ছে?” — Map/Set, sliding window, stack, binary search, sort + sweep, BFS দিয়ে সেটি দ্রুত করুন, আর trade-off বলুন।
- JavaScript-এর ফাঁদ: comparator ছাড়া sort, ইনপুট mutation, overflow নয় precision, object বনাম Map, shift-এর O(n), recursion-এর গভীরতা, UTF-16 বনাম code point বনাম grapheme.
- Async ও machine coding-এ closure, debounce, AbortController, “শুধু সবশেষ request render” আর accessibility; প্রস্তুতিতে AI-কে প্রশ্নকর্তা বানান, ইন্টারভিউর সময় নিয়ম মানুন — আর পরীক্ষা ও সার্টিফিকেটের জন্য ড্যাশবোর্ডে যান।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- JavaScript প্রোগ্রামিং · কোর্স হোম৫৯টি পর্বপুরো কোর্সের রোডম্যাপ — দুর্বল বিষয় খুঁজে আবার পড়ুন, পরীক্ষা ও সার্টিফিকেটের তথ্য।
- পর্ব ৫৮ · লিখিত ও মৌখিক (ভাইভা) প্রশ্নআগের পর্বধারণার প্রশ্নে গুছিয়ে উত্তর দেওয়ার অনুশীলন।
- পর্ব ৫৬ · আউটপুট কী হবে?রিভিশনDry run-এর অভ্যাস, যা ইন্টারভিউর “পরীক্ষা” ধাপে সরাসরি কাজে লাগে।
- পর্ব ৩৬ · AbortController, retry, debounce আর throttleরিভিশনmachine coding round-এর async ভিত্তি।
- Data Structures & Algorithmsপরের কোর্সইন্টারভিউর জন্য আরও গভীর ডেটা স্ট্রাকচার, গ্রাফ আর DP-র ধরন।
- Design and Analysis of Algorithmsগভীরে যানজটিলতা বিশ্লেষণ, greedy, divide and conquer আর DP-র তাত্ত্বিক ভিত্তি।