April 10, 2025

Câu hỏi phỏng vấn JavaScript cho Junior
Tuyển tập các câu hỏi phỏng vấn JavaScript cơ bản dành cho Junior, kèm giải thích dễ hiểu và ví dụ minh hoạ.
Q: Callback hell là gì?
A: Callback hell xảy ra khi bạn lồng nhiều hàm callback bên trong nhau, dẫn đến mã khó đọc, khó bảo trì. Đây là vấn đề phổ biến khi xử lý bất đồng bộ trước khi có Promise
và async/await
.
getUser(id, (user) => {
getPosts(user.id, (posts) => {
getComments(posts[0].id, (comments) => {
console.log(comments);
});
});
});
Q: Promise là gì và giúp gì?
A: Promise
là cách quản lý tác vụ bất đồng bộ. Nó đại diện cho một giá trị sẽ có trong tương lai và cho phép xử lý kết quả bằng .then()
và .catch()
, giúp tránh callback hell.
fetchData()
.then(data => console.log(data))
.catch(error => console.error(error));
Q: Sự khác nhau giữa null
và undefined
trong thực tế là gì?
A: undefined
thường xuất hiện khi biến chưa được gán, trong khi null
là khi ta chủ động đặt giá trị là “rỗng”. Trong thực tế, bạn dùng null
để reset giá trị, undefined
là dấu hiệu thiếu sót.
Q: async/await
hoạt động như thế nào?
A: async
biến một hàm thành hàm bất đồng bộ và luôn trả về Promise
. Từ khóa await
tạm dừng hàm cho đến khi Promise
hoàn thành, giúp code dễ đọc như code đồng bộ.
async function load() {
const res = await fetch("/data");
const data = await res.json();
console.log(data);
}
Q: Closure là gì?
A: Closure là khi một hàm “ghi nhớ” biến trong scope cha của nó, ngay cả khi scope đó đã kết thúc. Closure cho phép bạn tạo private variable hoặc lưu state trong hàm.
function counter() { let count = 0; return () => ++count; } const next = counter(); next(); // 1 next(); // 2
Q: Event loop trong JavaScript là gì?
A: Event loop là cơ chế giúp JavaScript xử lý tác vụ bất đồng bộ bằng cách quản lý call stack và task queue. Nó đảm bảo code đồng bộ chạy trước, rồi mới xử lý các callback bất đồng bộ.
Q: this
trong JavaScript là gì?
A: this
là từ khóa đại diện cho ngữ cảnh gọi hàm. Trong object method, this
là object đó. Trong function thông thường, this
là undefined
(strict mode) hoặc window
(non-strict). Arrow function không có this
riêng.
Q: Debounce và Throttle khác nhau như thế nào?
A: Debounce chỉ gọi hàm sau khi người dùng ngưng thao tác một khoảng thời gian. Throttle giới hạn gọi hàm mỗi khoảng thời gian cố định. Cả hai đều dùng để tối ưu hiệu năng khi xử lý sự kiện như scroll
hoặc input
.
Q: DOM là gì?
A: DOM (Document Object Model) là mô hình cấu trúc tài liệu HTML dưới dạng cây. JavaScript dùng DOM để truy cập, thay đổi nội dung, thuộc tính và style của trang web.
Q: map()
khác gì forEach()
?
A: map()
trả về mảng mới chứa kết quả sau xử lý từng phần tử, còn forEach()
chỉ duyệt qua mảng mà không trả về gì. Dùng map()
khi cần chuyển đổi dữ liệu.
const nums = [1, 2, 3];
const doubled = nums.map(n => n * 2); // [2, 4, 6]
Q: Toán tử spread và rest khác nhau như thế nào?
A: Cả hai dùng ...
, nhưng spread
dùng để tách phần tử từ mảng/object, còn rest
dùng để gom phần tử vào một mảng.
// spread
const arr = [1, 2, 3];
const copy = [...arr];
// rest
function sum(...args) {
return args.reduce((a, b) => a + b);
}
Q: Tại sao nên tránh ==
khi so sánh?
A: Vì ==
có thể gây ra những so sánh bất ngờ do ép kiểu ngầm. Ví dụ: '' == 0
là true
. Dùng ===
giúp đảm bảo kiểm tra chặt chẽ hơn và ít lỗi hơn.
Q: Arrow function có được làm constructor không?
A: Không. Arrow function không có this
, arguments
, và cũng không có prototype
, nên không dùng để tạo instance như constructor function bình thường.
Q: Optional chaining (?.
) dùng để làm gì?
A: Giúp truy cập thuộc tính lồng nhau mà không bị lỗi nếu một trong các phần tử trung gian là null
hoặc undefined
.
const user = {}; console.log(user.profile?.email); // undefined, không lỗi
Q: JSON.stringify()
và JSON.parse()
để làm gì?
A: stringify()
biến object thành chuỗi JSON để lưu trữ/ gửi đi. parse()
biến chuỗi JSON thành object. Dùng khi làm việc với API hoặc localStorage.
Q: Tại sao typeof NaN
lại là "number"
?
A: Đây là một “quirk” của JavaScript. NaN
vẫn thuộc loại number
, chỉ là nó không đại diện cho một giá trị hợp lệ.
Q: Sự khác nhau giữa == undefined
và === undefined
là gì?
A: == undefined
trả về true
cho cả null
và undefined
vì ép kiểu. === undefined
thì chỉ true nếu thực sự là undefined
.
Q: DOMContentLoaded khác gì window.onload?
A: DOMContentLoaded
được gọi khi DOM sẵn sàng (trước khi ảnh tải xong), còn window.onload
đợi mọi tài nguyên (ảnh, script) tải xong mới gọi. Dùng DOMContentLoaded
để init sớm.
Q: Hàm nào trong JS giúp clone sâu object?
A: Dùng structuredClone(obj)
hoặc JSON.parse(JSON.stringify(obj))
(có giới hạn). Thư viện như lodash.cloneDeep
cũng hỗ trợ tốt clone sâu.
Q: ==
giữa []
và false
trả về gì? Vì sao?
A: Trả về true
vì JavaScript thực hiện ép kiểu phức tạp: []
được ép sang chuỗi ""
, rồi sang số 0
, và false
cũng thành 0
. Vì vậy [] == false
→ true
. Tránh dùng ==
vì dễ gây lỗi logic.
Q: Khi nào nên dùng try...catch
?
A: Dùng khi có khả năng lỗi xảy ra, ví dụ: gọi API, parse JSON, đọc dữ liệu chưa chắc chắn. try...catch
giúp không làm sập chương trình và xử lý lỗi một cách an toàn.
try { const data = JSON.parse('{"key": 123}'); console.log(data.key); } catch (err) { console.error("JSON lỗi:", err); }
Q: IIFE (Immediately Invoked Function Expression) là gì?
A: Là hàm được định nghĩa và chạy ngay lập tức. Dùng để tạo scope riêng, tránh làm bẩn global scope.
(function () { const msg = "Hello!"; console.log(msg); })();
Q: Sự khác biệt giữa synchronous và asynchronous là gì?
A: Synchronous: tác vụ chạy theo thứ tự, tác vụ sau phải đợi cái trước. Asynchronous: có thể tạm dừng và tiếp tục khi hoàn thành, không chặn dòng xử lý chính (non-blocking). JS dùng async để giữ UI mượt.
Q: localStorage
, sessionStorage
, cookie
khác nhau gì?
A:
localStorage
: lưu dữ liệu lâu dài, kể cả khi tắt trình duyệt.sessionStorage
: mất khi tab/trình duyệt đóng.cookie
: gửi theo mỗi request HTTP, nhỏ gọn, có thể cấu hình thời gian sống, bảo mật hơn.
Q: Làm sao để kiểm tra một biến là array?
A: Dùng Array.isArray(value)
là cách đúng nhất. Tránh dùng typeof
vì typeof []
cũng trả về "object"
.
Q: Làm sao để tránh mutate object trong JS?
A: Clone object trước khi sửa. Dùng spread operator
, Object.assign()
, hoặc thư viện như lodash
.
const newUser = { ...user, name: "Vu Nguyen" };
Q: setTimeout
trong vòng lặp for
thường sai vì sao?
A: Do closure giữ lại giá trị cuối của biến lặp. Cách giải quyết là dùng let
, hoặc tạo closure riêng cho mỗi vòng.
for (let i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100); } // In ra: 0 1 2
Q: Làm sao để biết code có đang chạy async không?
A: Xem có dùng Promise
, async/await
, setTimeout
, hoặc API bất đồng bộ như fetch
. Các hàm async thường không trả kết quả ngay mà cần .then()
hoặc await
.
Q: Object.freeze()
dùng để làm gì?
A: Làm cho object không thể thay đổi: không thêm, xoá, hay sửa property. Rất hữu ích khi muốn bảo vệ dữ liệu không bị thay đổi ngoài ý muốn.
const config = Object.freeze({ mode: "prod" }); config.mode = "dev"; // Không thay đổi được
JavaScript Interview Questions for Junior
A collection of basic JavaScript interview questions for Junior, with clear explanations and illustrative examples.
Q: What is callback hell?
A: Callback hell happens when you nest many callbacks inside each other, making the code hard to read and maintain. It was a common issue when handling async tasks before Promise
and async/await
existed.
getUser(id, (user) => {
getPosts(user.id, (posts) => {
getComments(posts[0].id, (comments) => {
console.log(comments);
});
});
});
Q: What is a Promise and how does it help?
A: A Promise
is a way to manage asynchronous tasks. It represents a future value and allows handling results using .then()
and .catch()
, helping avoid callback hell.
fetchData()
.then(data => console.log(data))
.catch(error => console.error(error));
Q: What’s the difference between null
and undefined
in practice?
A: undefined
usually means a variable hasn't been assigned a value, while null
is intentionally set to indicate "no value." Use null
when you want to reset or clear a value, undefined
signals missing data.
Q: How does async/await
work?
A: async
turns a function into one that always returns a Promise
. await
pauses the function until the Promise
resolves. It makes async code look more like synchronous code.
async function load() {
const res = await fetch("/data");
const data = await res.json();
console.log(data);
}
Q: What is a closure?
A: A closure is when a function "remembers" variables from its outer scope, even after the outer function has finished. Closures are useful for creating private variables or preserving state.
function counter() { let count = 0; return () => ++count; } const next = counter(); next(); // 1 next(); // 2
Q: What is the event loop in JavaScript?
A: The event loop is a mechanism that handles asynchronous tasks in JavaScript by managing the call stack and task queue. It ensures synchronous code runs first, then handles async callbacks.
Q: What is this
in JavaScript?
A: this
refers to the context of a function call. In object methods, this
refers to the object. In regular functions, it’s undefined
in strict mode or window
in non-strict mode. Arrow functions do not have their own this
.
Q: What’s the difference between debounce and throttle?
A: Debounce delays a function until after user stops triggering it for a set time. Throttle limits how often a function runs in a given period. Both are useful for optimizing performance during events like scroll
or input
.
Q: What is the DOM?
A: The DOM (Document Object Model) represents HTML as a tree structure. JavaScript uses the DOM to access and change page content, attributes, and styles.
Q: How is map()
different from forEach()
?
A: map()
returns a new array with transformed values, while forEach()
simply iterates through the array without returning anything. Use map()
when you need to transform data.
const nums = [1, 2, 3];
const doubled = nums.map(n => n * 2); // [2, 4, 6]
Q: What's the difference between spread and rest operators?
A: Both use ...
, but spread
is for unpacking elements, and rest
is for gathering elements into an array.
// spread
const arr = [1, 2, 3];
const copy = [...arr];
// rest
function sum(...args) {
return args.reduce((a, b) => a + b);
}
Q: Why avoid using ==
for comparison?
A: Because ==
does type coercion and may produce unexpected results. For example: '' == 0
is true
. Use ===
for safer and more predictable comparisons.
Q: Can arrow functions be used as constructors?
A: No. Arrow functions do not have their own this
, arguments
, or prototype
, so they cannot be used to create instances like regular constructor functions.
Q: What is optional chaining (?.
) used for?
A: It allows safely accessing deeply nested properties without throwing an error if an intermediate value is null
or undefined
.
const user = {}; console.log(user.profile?.email); // undefined, no error
Q: What are JSON.stringify()
and JSON.parse()
used for?
A: stringify()
converts an object into a JSON string, useful for storing or sending data. parse()
converts a JSON string back to an object. They're often used with APIs and localStorage.
Q: Why is typeof NaN
equal to "number"
?
A: It's a known quirk in JavaScript. NaN
is still considered a number type, even though it’s not a valid number.
Q: What’s the difference between == undefined
and === undefined
?
A: == undefined
returns true
for both null
and undefined
due to type coercion. === undefined
is strict and only returns true
for actual undefined
.
Q: What’s the difference between DOMContentLoaded
and window.onload
?
A: DOMContentLoaded
fires when the DOM is ready, before images and other resources are loaded. window.onload
waits for everything (images, scripts) to load. Use DOMContentLoaded
for faster initialization.
Q: How do you deep clone an object in JS?
A: Use structuredClone(obj)
or JSON.parse(JSON.stringify(obj))
(with some limitations). Libraries like lodash.cloneDeep
are also helpful.
Q: What does [] == false
return, and why?
A: It returns true
due to JavaScript's coercion rules: []
becomes ""
, then 0
, and false
is also 0
, so [] == false
→ true
. Avoid ==
to prevent such logic errors.
Q: When should you use try...catch
?
A: Use it when something might fail, like API calls, JSON parsing, or reading uncertain data. try...catch
prevents your app from crashing and lets you handle errors safely.
try { const data = JSON.parse('{"key": 123}'); console.log(data.key); } catch (err) { console.error("JSON error:", err); }
Q: What is an IIFE (Immediately Invoked Function Expression)?
A: It’s a function that runs immediately after it's defined. It's useful for creating private scope and avoiding global pollution.
(function () { const msg = "Hello!"; console.log(msg); })();
Q: What's the difference between synchronous and asynchronous?
A: Synchronous code runs step by step, blocking the next task. Asynchronous code can pause and resume without blocking the main thread, which helps keep the UI responsive.
Q: What’s the difference between localStorage
, sessionStorage
, and cookie
?
A:
localStorage
: persists data even after browser is closed.sessionStorage
: cleared when tab/browser is closed.cookie
: sent with every HTTP request, smaller size, more suitable for auth/data with expiration and security needs.
Q: How to check if a variable is an array?
A: Use Array.isArray(value)
— it's the most reliable. Avoid typeof
since typeof []
is "object"
.
Q: How to avoid mutating objects in JavaScript?
A: Clone objects before modifying. Use the spread operator, Object.assign()
, or libraries like lodash
.
const newUser = { ...user, name: "Vu Nguyen" };
Q: Why does setTimeout
in a for
loop often behave unexpectedly?
A: It’s due to closures keeping the last loop value. Use let
or create a closure for each iteration to fix it.
for (let i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100); } // Logs: 0 1 2
Q: How to know if code is running asynchronously?
A: Check if it uses Promise
, async/await
, setTimeout
, or async APIs like fetch
. Async functions don’t return immediate results — they need .then()
or await
.
Q: What does Object.freeze()
do?
A: It makes an object immutable — you can’t add, delete, or change its properties. It's useful for protecting data from accidental modification.
const config = Object.freeze({ mode: "prod" }); config.mode = "dev"; // Won’t change