April 10, 2025

Câu hỏi phỏng vấn JavaScript cho Middle
Tuyển tập các câu hỏi phỏng vấn JavaScript cơ bản dành cho Middle, kèm giải thích dễ hiểu và ví dụ minh hoạ.
Q: Hoisting là gì và ảnh hưởng ra sao?
A: Hoisting là hành vi JavaScript đẩy khai báo biến (var
) và hàm lên đầu scope. Điều này khiến bạn có thể gọi hàm trước khi khai báo, hoặc biến var
sẽ được khởi tạo undefined
nếu dùng trước khi gán. let
và const
cũng được hoist, nhưng ở trạng thái “temporal dead zone” nên không dùng được trước khi khai báo.
Q: JavaScript chạy đơn luồng, vậy xử lý bất đồng bộ thế nào?
A: JS dùng event loop kết hợp với Web APIs và task queue để xử lý async. Khi gặp tác vụ như setTimeout
, fetch
, nó đẩy vào Web APIs, sau đó callback sẽ được đẩy vào queue và xử lý khi call stack rảnh.
Q: Deep clone object phức tạp nên cẩn thận điều gì?
A: JSON.stringify
không clone được các kiểu như function
, undefined
, Date
, RegExp
, Map
, Set
. Nên dùng structuredClone()
hoặc thư viện như lodash.cloneDeep()
để đảm bảo.
Q: Sự khác biệt giữa call
, apply
, bind
là gì?
A: Cả ba dùng để thay đổi this
của function:
call(thisArg, arg1, arg2,...)
apply(thisArg, [arg1, arg2,...])
bind(thisArg)
trả về hàm mới vớithis
đã gán cố định.
function greet() {
console.log(`Hello ${this.name}`);
}
greet.call({ name: "Vu" }); // Hello Vu
Q: Làm sao để kiểm soát memory leak trong JS?
A: Tránh các reference không giải phóng: closure không rõ ràng, DOM không được gỡ bỏ, timer không clear, biến global. Dùng tool như Chrome DevTools Memory để kiểm tra heap và detect leak.
Q: Cách hoạt động của setTimeout
với thời gian bằng 0?
A: setTimeout(fn, 0)
không chạy ngay lập tức. Nó đẩy fn
vào macro task queue, và chỉ được thực hiện khi call stack trống. Vì vậy vẫn có delay nhỏ.
Q: Cách nào để debounce một input search hiệu quả?
A: Dùng setTimeout
và clearTimeout
để đợi người dùng ngừng gõ trước khi gửi request, tránh spam API.
let timer; input.addEventListener("input", (e) => { clearTimeout(timer); timer = setTimeout(() => { search(e.target.value); }, 300); });
Q: Destructuring hoạt động như thế nào với nested object?
A: Cho phép tách dữ liệu nhanh từ object lồng nhau. Nếu không kiểm tra kỹ có thể gây lỗi undefined
.
const user = { profile: { name: "Vu" } }; const { profile: { name } } = user;
Q: Symbol
dùng để làm gì?
A: Symbol
tạo ra giá trị unique, thường dùng để định nghĩa key không bị ghi đè trong object, hoặc custom behavior như Symbol.iterator
, Symbol.toPrimitive
.
Q: JavaScript Engine hoạt động như thế nào?
A: Engine như V8 dịch JS thành bytecode hoặc máy, tối ưu qua JIT compiler. Code được phân tích, optimize theo runtime profile. Hiểu điều này giúp viết code tối ưu hơn.
Q: ==
giữa object và primitive hoạt động thế nào?
A: Khi so sánh, object sẽ được gọi toPrimitive()
→ về dạng primitive, thường là chuỗi [object Object]
, nên dễ gây nhầm lẫn.
{} == "[object Object]" // true
Q: Khi nào dùng WeakMap và WeakSet?
A: Dùng khi cần lưu reference tới object mà không giữ cứng trong bộ nhớ – cho phép GC thu dọn. Thường dùng để lưu metadata, cache tạm.
Q: Object.create(null)
dùng để làm gì?
A: Tạo object không có prototype (__proto__
), rất sạch để dùng làm dictionary/map, tránh collision với key mặc định như toString
.
Q: requestAnimationFrame
khác gì setTimeout
?
A: requestAnimationFrame
tối ưu hiệu năng animation vì nó chạy trước frame kế tiếp (~60fps), không bị delay như setTimeout
. Nó sync tốt với repaint của trình duyệt.
Q: typeof null
là "object"
– tại sao?
A: Là bug từ thời JavaScript đầu tiên, do cách lưu trữ null dưới dạng tag pointer. Dù đã biết từ lâu, nhưng không sửa vì sẽ phá vỡ backward compatibility.
Q: this
trong arrow function và regular function khác gì?
A: Arrow function không có this
riêng, nó lấy this
từ scope bên ngoài. Regular function có this
động tùy thuộc vào cách gọi.
Q: Optional chaining khác nullish coalescing?
A: ?.
dùng để truy cập an toàn sâu trong object, còn ??
dùng để gán giá trị mặc định khi giá trị là null
hoặc undefined
.
user?.profile?.email ?? "Email không tồn tại";
Q: Xử lý bất đồng bộ tuần tự với for
và await
như thế nào?
A: Dùng for...of
thay vì forEach
vì forEach
không chờ được await
.
for (const id of ids) {
const data = await fetchData(id);
console.log(data);
}
Q: Memoization là gì?
A: Là kỹ thuật cache kết quả của hàm với đầu vào cụ thể, tránh tính toán lại nếu đầu vào giống nhau. Dùng để tăng hiệu năng.
const memo = {}; function fib(n) { if (n in memo) return memo[n]; return memo[n] = n <= 1 ? n : fib(n - 1) + fib(n - 2); }
Q: Làm sao để kiểm tra object rỗng một cách chính xác?
A: Dùng Object.keys(obj).length === 0
. Vì Object.keys()
trả về mảng các key của object. Nếu length = 0, nghĩa là không có key nào → object rỗng.
Q: Closures là gì và dùng làm gì?
A: Closure là function “ghi nhớ” được scope nơi nó được tạo ra, kể cả khi chạy ngoài scope đó. Rất hữu ích cho việc tạo biến private, debounce, hoặc giữ state.
function counter() { let count = 0; return () => ++count; } const inc = counter(); inc(); // 1 inc(); // 2
Q: Promise.all()
và Promise.allSettled()
khác gì nhau?
A:
Promise.all()
fail toàn bộ nếu 1 promise reject.Promise.allSettled()
chờ tất cả hoàn thành, cho kết quả cả thành công và thất bại. Dùng khi không muốn dừng cả tiến trình vì 1 lỗi.
Q: Tại sao forEach
không hoạt động tốt với async/await
?
A: Vì forEach
không chờ await
, nó không hỗ trợ async
. Thay vào đó dùng for...of
hoặc for
truyền thống nếu cần tuần tự.
Q: So sánh map
, filter
, reduce
trong array
A:
map
: tạo mảng mới từ mảng cũ với mỗi phần tử được biến đổifilter
: tạo mảng mới chỉ với phần tử đúng điều kiệnreduce
: gom mảng thành một giá trị duy nhất (số, object, v.v.)
[1, 2, 3].map(x => x * 2); // [2, 4, 6] [1, 2, 3].filter(x => x > 1); // [2, 3] [1, 2, 3].reduce((a, b) => a + b, 0); // 6
Q: Mutable và immutable khác nhau ra sao?
A:
- Mutable: có thể thay đổi sau khi tạo (
array
,object
) - Immutable: không thay đổi (string, số, boolean). Trong JS, bạn có thể xử lý bất biến bằng cách clone object thay vì sửa trực tiếp (dùng
spread
,map
, v.v.)
Q: Làm sao để tránh callback hell?
A: Dùng Promise
, async/await
để thay thế callback lồng nhau. Viết các hàm nhỏ, tách logic rõ ràng, tránh nesting sâu.
// bad
getData(id, (data) => {
save(data, (res) => {
notify(res, () => {});
});
});
// good
const data = await getData(id);
const res = await save(data);
await notify(res);
Q: this
trong class và arrow function khác nhau không?
A: Có. Trong class, this
của arrow function sẽ giữ nguyên theo scope bên ngoài (thường là instance). Regular function trong class cần bind thủ công hoặc gọi đúng ngữ cảnh.
Q: Khi nào nên dùng try...catch
trong async/await
?
A: Khi bạn muốn xử lý lỗi rõ ràng cho từng bước, hoặc gộp xử lý lỗi toàn bộ. Nên tránh bọc toàn bộ hàm lớn, thay vào đó bọc từng đoạn dễ lỗi để dễ debug.
try {
const res = await fetchData();
} catch (err) {
console.error("Fetch failed", err);
}
Q: Làm sao để lazy load module trong JS?
A: Dùng import()
dynamic import để tải module khi cần. Trả về Promise.
const module = await import('./math.js'); module.sum(1, 2);
Q: Cách hoạt động của optional parameters trong function?
A: Bạn có thể gán giá trị mặc định trong tham số. Nếu không truyền, giá trị đó được dùng.
function greet(name = "bạn") {
return `Chào ${name}`;
}
Q: Cách xử lý error trong .then()
và await
khác nhau không?
A: Có. Trong .then()
bạn dùng .catch()
. Với await
, bạn phải dùng try...catch
.
fetch(url) .then(res => res.json()) .catch(err => console.error(err)); // async/await try { const res = await fetch(url); } catch (err) { console.error(err); }
Q: Khi nào nên dùng Object.defineProperty()
?
A: Khi cần kiểm soát chi tiết property (read-only, hidden, computed…). Ví dụ:
Object.defineProperty(obj, "secret", {
value: "hidden",
writable: false,
enumerable: false,
});
Q: arguments
trong function khác ...rest
không?
A: Có. arguments
là pseudo-array có sẵn trong function thường, không có trong arrow function. ...rest
là array thực sự, hiện đại và dễ dùng hơn.
Q: Cách kiểm tra performance code JS?
A: Dùng performance.now()
để đo thời gian thực thi. Hoặc dùng Chrome DevTools > tab Performance để theo dõi bottleneck, FPS, memory.
Q: Làm sao để kiểm soát đồng bộ các request API trong JS?
A: Dùng Promise.all()
nếu muốn chạy song song, hoặc for await...of
nếu cần tuần tự.
Q: So sánh Map
và Object?
A:
Map
: key có thể là bất kỳ kiểu nào, giữ thứ tự key, hiệu suất tốt hơn cho thao tác lớnObject
: key chỉ là string/symbol, không đảm bảo thứ tự
Q: Cách xử lý lỗi mạng/phản hồi khi fetch API?
A:
- Kiểm tra
res.ok
- Kiểm tra mã lỗi HTTP
- Dùng
try/catch
- Gán timeout nếu cần
const res = await fetch(url); if (!res.ok) throw new Error("API failed");
JavaScript Interview Questions for Middle
A collection of basic JavaScript interview questions for Middle, with clear explanations and illustrative examples.
Q: What is hoisting and how does it affect code?
A: Hoisting is JavaScript's behavior of moving variable (var
) and function declarations to the top of their scope. This allows calling functions before they’re defined, and var
variables become undefined
if accessed before assignment. let
and const
are also hoisted but in a "temporal dead zone", so they can’t be used before declaration.
Q: JavaScript is single-threaded, so how does it handle async operations?
A: JavaScript uses the event loop, Web APIs, and the task queue to handle asynchronous tasks. Functions like setTimeout
or fetch
are handled by Web APIs, and their callbacks are pushed into the queue and executed when the call stack is clear.
Q: What should you be careful about when deep cloning complex objects?
A: JSON.stringify
doesn’t handle functions, undefined
, Date
, RegExp
, Map
, or Set
. Use structuredClone()
or libraries like lodash.cloneDeep()
for safe deep cloning.
Q: What’s the difference between call
, apply
, and bind
?
A: All three change a function’s this
context:
call(thisArg, arg1, arg2,...)
apply(thisArg, [arg1, arg2,...])
bind(thisArg)
returns a new function with boundthis
.
function greet() {
console.log(`Hello ${this.name}`);
}
greet.call({ name: "Vu" }); // Hello Vu
Q: How to avoid memory leaks in JS?
A: Watch out for lingering references: unclear closures, detached DOM elements, un-cleared timers, global variables. Use tools like Chrome DevTools Memory panel to inspect heap and detect leaks.
Q: What happens with setTimeout(fn, 0)
?
A: setTimeout(fn, 0)
doesn’t run immediately. It pushes fn
to the macro task queue and waits until the call stack is empty, so there's a slight delay.
Q: How to debounce an input search efficiently?
A: Use setTimeout
and clearTimeout
to wait until the user stops typing before sending requests.
let timer; input.addEventListener("input", (e) => { clearTimeout(timer); timer = setTimeout(() => { search(e.target.value); }, 300); });
Q: How does destructuring work with nested objects?
A: It allows quick access to deep object properties. But be careful—accessing non-existent paths can cause errors.
const user = { profile: { name: "Vu" } }; const { profile: { name } } = user;
Q: What is Symbol
used for?
A: Symbol
creates unique values, often used as object keys to avoid name collisions, or to define custom behavior like Symbol.iterator
, Symbol.toPrimitive
.
Q: How does the JavaScript engine work?
A: Engines like V8 compile JS into bytecode or machine code using JIT. They optimize based on runtime profiling, which helps improve performance.
Q: How does ==
work between object and primitive?
A: When comparing, the object gets coerced via toPrimitive()
—usually turning into [object Object]
, which can lead to confusing results.
{} == "[object Object]" // true
Q: When should you use WeakMap or WeakSet?
A: When you need to store references to objects without preventing garbage collection. Often used for metadata or temporary caches.
Q: What’s the purpose of Object.create(null)
?
A: Creates a prototype-less object—clean for use as a dictionary/map, avoiding conflicts with default keys like toString
.
Q: How is requestAnimationFrame
different from setTimeout
?
A: requestAnimationFrame
is optimized for animations and syncs with the browser’s repaint (~60fps). It’s more efficient than setTimeout
for smooth visuals.
Q: Why is typeof null
"object"
?
A: It’s a long-standing JavaScript bug caused by how null
was stored internally. It hasn't been fixed due to backward compatibility concerns.
Q: How does this
differ in arrow functions vs regular functions?
A: Arrow functions don’t have their own this
, they inherit it from the surrounding scope. Regular functions have dynamic this
based on how they're called.
Q: What's the difference between optional chaining and nullish coalescing?
A: ?.
safely accesses nested properties. ??
assigns a fallback value if the result is null
or undefined
.
user?.profile?.email ?? "Email not available";
Q: How do you sequentially process async tasks in a loop?
A: Use for...of
instead of forEach
, because forEach
doesn’t wait for await
.
for (const id of ids) {
const data = await fetchData(id);
console.log(data);
}
Q: What is memoization?
A: Caching a function’s results based on its inputs, to avoid recalculations and improve performance.
const memo = {}; function fib(n) { if (n in memo) return memo[n]; return memo[n] = n <= 1 ? n : fib(n - 1) + fib(n - 2); }
Q: How to check if an object is empty?
A: Use Object.keys(obj).length === 0
. If it returns 0
, the object has no properties.
Q: What are closures and what are they used for?
A: A closure is a function that remembers the scope it was created in, even when used outside of it. Great for private variables, debounce, or retaining state.
function counter() { let count = 0; return () => ++count; } const inc = counter(); inc(); // 1 inc(); // 2
Q: Difference between Promise.all()
and Promise.allSettled()
?
A:
Promise.all()
fails all if one promise rejects.Promise.allSettled()
waits for all to finish, returning results for both fulfilled and rejected promises.
Q: Why doesn’t forEach
work well with async/await
?
A: forEach
doesn’t await asynchronous operations. Use for...of
or a traditional loop if you need sequential async behavior.
Q: Compare map
, filter
, and reduce
A:
map
: creates a new array by transforming each elementfilter
: returns elements that meet a conditionreduce
: combines all elements into a single value
[1, 2, 3].map(x => x * 2); // [2, 4, 6] [1, 2, 3].filter(x => x > 1); // [2, 3] [1, 2, 3].reduce((a, b) => a + b, 0); // 6
Q: What’s the difference between mutable and immutable?
A:
- Mutable: can be changed (
array
,object
) - Immutable: can’t be changed (
string
,number
,boolean
). Use spread/cloning to keep immutability in objects.
Q: How to avoid callback hell?
A: Use Promise
or async/await
instead of nested callbacks. Write small, clear functions to separate logic.
// bad
getData(id, (data) => {
save(data, (res) => {
notify(res, () => {});
});
});
// good
const data = await getData(id);
const res = await save(data);
await notify(res);
Q: How does this
differ in class methods and arrow functions?
A: In classes, arrow functions inherit this
from the surrounding scope (usually the instance), while regular methods may need manual binding.
Q: When should you use try...catch
with async/await
?
A: When you want to handle errors for specific steps. Don’t wrap entire large functions—wrap only error-prone code for easier debugging.
try {
const res = await fetchData();
} catch (err) {
console.error("Fetch failed", err);
}
Q: How to lazy-load a JS module?
A: Use import()
for dynamic imports. It returns a Promise.
const module = await import('./math.js'); module.sum(1, 2);
Q: How do optional parameters work in functions?
A: Assign default values in the parameter list. If not passed, the default will be used.
function greet(name = "friend") {
return `Hello ${name}`;
}
Q: Error handling in .then()
vs await
— what’s different?
A: In .then()
use .catch()
. With await
, use try...catch
.
fetch(url) .then(res => res.json()) .catch(err => console.error(err)); // async/await try { const res = await fetch(url); } catch (err) { console.error(err); }
Q: When to use Object.defineProperty()
?
A: When you need precise control over property behavior (read-only, hidden, computed, etc.)
Object.defineProperty(obj, "secret", {
value: "hidden",
writable: false,
enumerable: false,
});
Q: Difference between arguments
and ...rest
?
A: arguments
is an array-like object available in regular functions but not in arrow functions. ...rest
is a real array and more modern.
Q: How to measure JavaScript performance?
A: Use performance.now()
for time measurement, or Chrome DevTools > Performance tab to inspect FPS, memory, and bottlenecks.
Q: How to synchronize multiple API requests in JS?
A: Use Promise.all()
for parallel execution, or for await...of
for sequential processing.
Q: Compare Map
and Object
A:
Map
: keys can be of any type, maintains insertion order, better for large dataObject
: keys are strings/symbols only, no guaranteed order
Q: How to handle network or API response errors?
A:
- Check
res.ok
- Handle HTTP error codes
- Use
try/catch
- Set timeout if needed
const res = await fetch(url); if (!res.ok) throw new Error("API failed");