Phỏng vấn JavaScript năm 2026 không còn dừng lại ở việc hỏi var hay let, hay giải thích == với ===. Các công ty từ startup đến Big Tech Việt Nam và quốc tế đang expect ứng viên hiểu sâu về runtime behavior, async patterns, performance, và cả những gotcha mà chỉ người thực sự code production JS mới biết. Bài viết này tổng hợp 40+ câu hỏi phỏng vấn JavaScript thường gặp nhất năm 2026, được phân loại theo level, có code ví dụ chạy được, và quan trọng nhất là giải thích tại sao câu trả lời là như vậy, không chỉ là câu trả lời là gì. Đây là thứ phân biệt ứng viên được offer với ứng viên bị từ chối.
Phần 1: Câu Hỏi JavaScript Cơ Bản - Junior Level
1. Sự khác biệt giữa var, let, và const là gì?
var có function scope hoặc global scope, bị hoisted lên đầu scope và được initialized với undefined. let và const có block scope, bị hoisted nhưng không được initialized (Temporal Dead Zone). const không cho phép reassign nhưng object/array được declare bằng const vẫn có thể mutate.
// var: function scope, có thể redeclare
var x = 1;
var x = 2; // Không lỗi
console.log(x); // 2
// Hoisting của var
console.log(y); // undefined (không lỗi vì hoisted)
var y = 5;
// let: block scope, Temporal Dead Zone
console.log(z); // ReferenceError: Cannot access 'z' before initialization
let z = 10;
// const: không thể reassign
const obj = { name: "Trung" };
obj.name = "Minh"; // OK - mutate thuộc tính
obj = {}; // TypeError - không thể reassign
// Block scope của let và const
{
let blockVar = "inside";
const blockConst = "also inside";
}
console.log(blockVar); // ReferenceError
console.log(blockConst); // ReferenceError
Gotcha phỏng vấn: Interviewer thường hỏi về Temporal Dead Zone (TDZ). TDZ là khoảng thời gian từ khi block bắt đầu đến khi declaration được thực thi. Truy cập biến trong TDZ throw ReferenceError, khác với var chỉ return undefined.
2. Hoisting hoạt động như thế nào trong JavaScript?
Hoisting là cơ chế JavaScript engine "di chuyển" declarations (không phải assignments) lên đầu scope của chúng trong giai đoạn compilation. Function declarations được hoisted hoàn toàn (cả body). Variable declarations với var được hoisted nhưng chỉ declaration, không phải value.
// Function declaration: hoisted hoàn toàn
sayHello(); // "Hello!" - hoạt động dù gọi trước declaration
function sayHello() {
console.log("Hello!");
}
// Function expression: KHÔNG hoisted
sayBye(); // TypeError: sayBye is not a function
var sayBye = function() {
console.log("Bye!");
};
// Arrow function: KHÔNG hoisted
greet(); // TypeError
var greet = () => console.log("Hi!");
// Class declaration: hoisted nhưng không initialized (TDZ)
const obj = new MyClass(); // ReferenceError
class MyClass {
constructor() {}
}
3. Closures là gì? Cho ví dụ thực tế.
Closure là function có khả năng truy cập variables từ outer scope của nó ngay cả sau khi outer function đã return. Closure "closes over" các biến trong lexical environment của nó, không phải giá trị tại thời điểm closure được tạo.
// Ví dụ cơ bản
function createCounter() {
let count = 0; // Biến này được "closed over"
return {
increment() { count++; },
decrement() { count--; },
getCount() { return count; }
};
}
const counter = createCounter();
counter.increment();
counter.increment();
counter.increment();
counter.decrement();
console.log(counter.getCount()); // 2
// count không thể truy cập từ bên ngoài
// Ví dụ thực tế: Factory function với private state
function createUser(name) {
let loginCount = 0; // Private
return {
login() {
loginCount++;
console.log(`${name} đã đăng nhập (lần ${loginCount})`);
},
getStats() {
return { name, loginCount };
}
};
}
const user = createUser("Trung");
user.login(); // "Trung đã đăng nhập (lần 1)"
user.login(); // "Trung đã đăng nhập (lần 2)"
console.log(user.getStats()); // { name: 'Trung', loginCount: 2 }
// Classic closure gotcha trong phỏng vấn
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Output: 3, 3, 3 (không phải 0, 1, 2)
// Tại sao? var không có block scope, tất cả callbacks share cùng i
// Fix với let (block scope)
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Output: 0, 1, 2
// Fix với IIFE (cách cũ)
for (var i = 0; i < 3; i++) {
((j) => {
setTimeout(() => console.log(j), 100);
})(i);
}
// Output: 0, 1, 2
4. this keyword hoạt động như thế nào?
this trong JavaScript không phải về nơi function được định nghĩa mà là về cách function được gọi. Giá trị của this được xác định ở runtime, không phải ở compile time (ngoại trừ arrow functions).
// 1. Global context
console.log(this); // window (browser) / global (Node.js) / undefined (strict mode)
// 2. Object method
const obj = {
name: "Trung",
greet() {
console.log(this.name); // "Trung"
}
};
obj.greet();
// 3. Mất context khi extract method
const greetFn = obj.greet;
greetFn(); // undefined (strict mode) hoặc window.name
// 4. Arrow function: this từ lexical scope
const obj2 = {
name: "Trung",
greet: () => {
console.log(this.name); // undefined (arrow function không có this riêng)
},
greetWithTimeout() {
setTimeout(() => {
console.log(this.name); // "Trung" (inherit từ greetWithTimeout)
}, 100);
}
};
// 5. bind, call, apply
function introduce(greeting, punctuation) {
console.log(`${greeting}, tôi là ${this.name}${punctuation}`);
}
const person = { name: "Minh" };
introduce.call(person, "Xin chào", "!"); // "Xin chào, tôi là Minh!"
introduce.apply(person, ["Chào", "."]); // "Chào, tôi là Minh."
const boundFn = introduce.bind(person, "Hi");
boundFn("?"); // "Hi, tôi là Minh?"
// 6. new keyword tạo this mới
function Person(name) {
this.name = name;
}
const p = new Person("Trung");
console.log(p.name); // "Trung"
5. Event Loop và Microtask Queue là gì?
JavaScript là single-threaded nhưng non-blocking nhờ Event Loop. Call Stack thực thi synchronous code. Sau khi Call Stack empty, Event Loop check Microtask Queue (Promises, queueMicrotask) trước, sau đó mới check Macrotask Queue (setTimeout, setInterval, I/O). Toàn bộ Microtask Queue được drain trước khi một Macrotask được thực thi.
console.log("1: Start");
setTimeout(() => {
console.log("4: setTimeout");
}, 0);
Promise.resolve()
.then(() => console.log("3: Promise 1"))
.then(() => console.log("3.5: Promise 2 (microtask chain)"));
console.log("2: End");
// Output theo thứ tự:
// 1: Start
// 2: End
// 3: Promise 1 (microtask - ưu tiên cao hơn setTimeout)
// 3.5: Promise 2 (microtask tiếp theo trong chain)
// 4: setTimeout (macrotask - sau khi microtask queue empty)
// Gotcha: queueMicrotask
console.log("A");
queueMicrotask(() => console.log("C: Microtask"));
console.log("B");
// Output: A, B, C
Phần 2: Câu Hỏi JavaScript Trung Cấp - Mid-Level
6. Prototype Chain hoạt động như thế nào?
JavaScript dùng prototypal inheritance. Mỗi object có một hidden property [[Prototype]] (truy cập qua __proto__ hoặc Object.getPrototypeOf()). Khi truy cập một property không có trên object, JavaScript tìm lên prototype chain cho đến khi gặp null.
// Prototype chain cơ bản
const animal = {
eat() {
console.log(`${this.name} đang ăn`);
}
};
const dog = {
name: "Rex",
bark() {
console.log("Gâu gâu!");
}
};
// Set prototype của dog là animal
Object.setPrototypeOf(dog, animal);
dog.bark(); // "Gâu gâu!" - found on dog directly
dog.eat(); // "Rex đang ăn" - found on dog's prototype (animal)
// Kiểm tra prototype chain
console.log(Object.getPrototypeOf(dog) === animal); // true
console.log(dog.hasOwnProperty("name")); // true
console.log(dog.hasOwnProperty("eat")); // false (từ prototype)
// Class syntax (syntactic sugar cho prototype)
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} tạo ra tiếng động`);
}
}
class Dog extends Animal {
speak() {
console.log(`${this.name} sủa`);
}
}
const d = new Dog("Rex");
d.speak(); // "Rex sủa" (override)
// instanceof kiểm tra prototype chain
console.log(d instanceof Dog); // true
console.log(d instanceof Animal); // true (chain)
console.log(d instanceof Object); // true (tất cả object đều là instance của Object)
7. Giải thích Promises và async/await. Khi nào dùng cái nào?
// Promise cơ bản
const fetchUser = (id) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id > 0) {
resolve({ id, name: "User " + id });
} else {
reject(new Error("Invalid ID"));
}
}, 1000);
});
};
// Promise chaining
fetchUser(1)
.then(user => {
console.log(user);
return fetchUser(2); // Return promise để chain
})
.then(user2 => console.log(user2))
.catch(err => console.error(err))
.finally(() => console.log("Done"));
// async/await (syntactic sugar cho Promise)
async function loadUsers() {
try {
const user1 = await fetchUser(1);
const user2 = await fetchUser(2);
console.log(user1, user2);
} catch (err) {
console.error(err);
} finally {
console.log("Done");
}
}
// Parallel vs Sequential - quan trọng trong phỏng vấn!
// SEQUENTIAL: chậm (2 giây) - mỗi await chờ cái trước
async function sequential() {
const user1 = await fetchUser(1); // Chờ 1 giây
const user2 = await fetchUser(2); // Chờ thêm 1 giây
// Tổng: ~2 giây
}
// PARALLEL: nhanh hơn (1 giây) - chạy đồng thời
async function parallel() {
const [user1, user2] = await Promise.all([
fetchUser(1),
fetchUser(2)
]);
// Tổng: ~1 giây (chạy đồng thời)
}
// Promise.allSettled: không fail khi một promise reject
async function safeLoad() {
const results = await Promise.allSettled([
fetchUser(1),
fetchUser(-1), // Sẽ reject
fetchUser(3)
]);
results.forEach(result => {
if (result.status === "fulfilled") {
console.log("Success:", result.value);
} else {
console.log("Failed:", result.reason.message);
}
});
}
// Promise.race: lấy kết quả đầu tiên (resolved hoặc rejected)
// Promise.any: lấy kết quả resolved đầu tiên, bỏ qua rejections
8. Debounce và Throttle là gì? Implement từ đầu.
// DEBOUNCE: chỉ execute sau khi không có event trong X ms
// Use case: search input, resize handler, auto-save
function debounce(fn, delay) {
let timeoutId;
return function(...args) {
// Hủy timer cũ nếu có
clearTimeout(timeoutId);
// Tạo timer mới
timeoutId = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
const searchHandler = debounce((query) => {
console.log("Searching for:", query);
// API call ở đây
}, 300);
// Người dùng gõ nhanh: chỉ gọi API sau 300ms kể từ lần gõ cuối
input.addEventListener("input", (e) => searchHandler(e.target.value));
// THROTTLE: execute tối đa một lần trong X ms
// Use case: scroll events, mouse move, API rate limiting
function throttle(fn, limit) {
let inThrottle = false;
return function(...args) {
if (!inThrottle) {
fn.apply(this, args);
inThrottle = true;
setTimeout(() => {
inThrottle = false;
}, limit);
}
};
}
const scrollHandler = throttle(() => {
console.log("Scroll position:", window.scrollY);
}, 200);
window.addEventListener("scroll", scrollHandler);
// Key difference:
// Debounce: chờ "yên lặng" sau đó execute một lần
// Throttle: execute đều đặn với khoảng cách tối thiểu
9. Shallow Copy vs Deep Copy trong JavaScript
const original = {
name: "Trung",
address: {
city: "HCM",
street: "Nguyen Hue"
},
hobbies: ["coding", "reading"]
};
// SHALLOW COPY - các cách phổ biến
const shallow1 = Object.assign({}, original);
const shallow2 = { ...original };
const shallow3 = Array.from(original.hobbies); // cho array
// Vấn đề với shallow copy
shallow1.name = "Minh"; // OK - primitive, không ảnh hưởng original
shallow1.address.city = "HN"; // PROBLEM - cùng reference!
console.log(original.address.city); // "HN" - bị thay đổi!
// DEEP COPY - các cách
// Cách 1: JSON (không handle: functions, undefined, Date, RegExp, circular refs)
const deepJson = JSON.parse(JSON.stringify(original));
// Cách 2: structuredClone (native, ES2022+, handle nhiều type hơn JSON)
const deep2 = structuredClone(original);
// Handle: Date, RegExp, Map, Set, ArrayBuffer
// Không handle: functions, DOM nodes, class instances
// Cách 3: Recursion
function deepClone(obj) {
if (obj === null || typeof obj !== "object") return obj;
if (obj instanceof Date) return new Date(obj.getTime());
if (obj instanceof Array) return obj.map(item => deepClone(item));
const clone = {};
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
clone[key] = deepClone(obj[key]);
}
}
return clone;
}
// Test deep clone
const deep3 = deepClone(original);
deep3.address.city = "DA"; // Không ảnh hưởng original
console.log(original.address.city); // "HN" - OK!
10. Giải thích Currying và Partial Application
// CURRYING: chuyển function(a, b, c) thành function(a)(b)(c)
// Mỗi lần gọi nhận một argument và return function nhận argument tiếp theo
// Manual currying
const add = (a) => (b) => (c) => a + b + c;
console.log(add(1)(2)(3)); // 6
// Curry utility function
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return function(...args2) {
return curried.apply(this, args.concat(args2));
};
};
}
const multiply = curry((a, b, c) => a * b * c);
console.log(multiply(2)(3)(4)); // 24
console.log(multiply(2, 3)(4)); // 24
console.log(multiply(2)(3, 4)); // 24
console.log(multiply(2, 3, 4)); // 24
// Ứng dụng thực tế: tái sử dụng function với context cố định
const addTax = curry((taxRate, price) => price * (1 + taxRate));
const addVAT = addTax(0.1); // 10% VAT
const addLuxuryTax = addTax(0.25); // 25% luxury tax
console.log(addVAT(100)); // 110
console.log(addLuxuryTax(100)); // 125
// PARTIAL APPLICATION: fix một số argument trước, gọi sau
function partial(fn, ...presetArgs) {
return function(...laterArgs) {
return fn(...presetArgs, ...laterArgs);
};
}
const multiply3 = partial((a, b, c) => a * b * c, 2, 3);
console.log(multiply3(4)); // 24
Phần 3: Câu Hỏi JavaScript Nâng Cao - Senior Level
11. Giải thích Memory Leaks trong JavaScript và cách phát hiện
// 1. Accidental global variables
function leak() {
leakyVar = "Tôi là global!"; // Thiếu let/const/var
}
// 2. Forgotten event listeners
const button = document.querySelector("#btn");
const heavyObject = { data: new Array(1000000).fill("leak") };
// Leak: listener giữ reference đến heavyObject
button.addEventListener("click", () => {
console.log(heavyObject.data.length);
});
// Fix: lưu reference và remove khi không cần
const handler = () => console.log(heavyObject.data.length);
button.addEventListener("click", handler);
// Sau này:
button.removeEventListener("click", handler);
// 3. Closures giữ reference không cần thiết
function createLeak() {
const hugeData = new Array(1000000).fill("data");
return function() {
// Closure này giữ hugeData trong memory
// ngay cả khi không dùng đến hugeData
return "something small";
};
}
// Fix: set về null khi không cần
function createFixed() {
let hugeData = new Array(1000000).fill("data");
const result = hugeData.length; // Dùng xong
hugeData = null; // Giải phóng
return function() {
return result;
};
}
// 4. Detached DOM nodes
let elements = [];
function addElement() {
const el = document.createElement("div");
document.body.appendChild(el);
elements.push(el); // Giữ reference sau khi remove khỏi DOM
}
function removeElement() {
const el = elements.pop();
document.body.removeChild(el);
// el vẫn còn trong memory vì elements array giữ reference
}
// Fix: dùng WeakMap hoặc dọn reference
let elementRefs = new WeakMap(); // GC có thể collect khi DOM node bị remove
12. Web Workers và cách sử dụng cho heavy computation
// main.js - Main thread
const worker = new Worker("worker.js");
// Gửi data đến worker
worker.postMessage({
type: "CALCULATE",
data: Array.from({ length: 1000000 }, (_, i) => i)
});
// Nhận kết quả từ worker
worker.onmessage = function(event) {
console.log("Kết quả từ worker:", event.data.result);
// UI không bị block trong suốt quá trình tính toán
};
worker.onerror = function(error) {
console.error("Worker error:", error.message);
};
// Cleanup
// worker.terminate();
// ---
// worker.js - Worker thread (chạy trong background)
self.onmessage = function(event) {
const { type, data } = event.data;
if (type === "CALCULATE") {
// Heavy computation không block main thread
const result = data.reduce((sum, n) => sum + n, 0);
self.postMessage({ result });
}
};
// SharedArrayBuffer cho shared memory giữa threads
// (cần header: Cross-Origin-Opener-Policy và Cross-Origin-Embedder-Policy)
const sharedBuffer = new SharedArrayBuffer(4);
const sharedArray = new Int32Array(sharedBuffer);
13. Generator Functions và Iterators
// Generator function cơ bản
function* numberGenerator() {
console.log("Bắt đầu");
yield 1;
console.log("Sau yield 1");
yield 2;
console.log("Sau yield 2");
yield 3;
console.log("Kết thúc");
}
const gen = numberGenerator();
console.log(gen.next()); // "Bắt đầu" → { value: 1, done: false }
console.log(gen.next()); // "Sau yield 1" → { value: 2, done: false }
console.log(gen.next()); // "Sau yield 2" → { value: 3, done: false }
console.log(gen.next()); // "Kết thúc" → { value: undefined, done: true }
// Infinite sequence với generator
function* infiniteCounter(start = 0) {
while (true) {
yield start++;
}
}
const counter = infiniteCounter(1);
console.log(counter.next().value); // 1
console.log(counter.next().value); // 2
// ... không bao giờ hết
// Ứng dụng thực tế: pagination
function* paginate(data, pageSize) {
for (let i = 0; i < data.length; i += pageSize) {
yield data.slice(i, i + pageSize);
}
}
const items = Array.from({ length: 100 }, (_, i) => `Item ${i}`);
const pages = paginate(items, 10);
console.log(pages.next().value); // Items 0-9
console.log(pages.next().value); // Items 10-19
// Custom iterable
const range = {
from: 1,
to: 5,
[Symbol.iterator]() {
let current = this.from;
const last = this.to;
return {
next() {
if (current <= last) {
return { value: current++, done: false };
}
return { value: undefined, done: true };
}
};
}
};
for (const num of range) {
console.log(num); // 1, 2, 3, 4, 5
}
14. Proxy và Reflect
// Proxy cho phép intercept và customize các operations trên object
const handler = {
get(target, key, receiver) {
console.log(`Getting ${key}`);
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
if (typeof value !== "number") {
throw new TypeError(`${key} phải là number`);
}
console.log(`Setting ${key} = ${value}`);
return Reflect.set(target, key, value, receiver);
},
has(target, key) {
console.log(`Checking if ${key} exists`);
return Reflect.has(target, key);
}
};
const obj = new Proxy({}, handler);
obj.age = 25; // "Setting age = 25"
obj.age = "twenty"; // TypeError!
console.log(obj.age); // "Getting age" → 25
console.log("age" in obj); // "Checking if age exists" → true
// Ứng dụng thực tế: Validation
function createValidator(target, validator) {
return new Proxy(target, {
set(obj, prop, value) {
if (prop in validator) {
const { type, min, max } = validator[prop];
if (typeof value !== type) {
throw new TypeError(`${prop} phải là ${type}`);
}
if (min !== undefined && value < min) {
throw new RangeError(`${prop} phải >= ${min}`);
}
if (max !== undefined && value > max) {
throw new RangeError(`${prop} phải <= ${max}`);
}
}
obj[prop] = value;
return true;
}
});
}
const person = createValidator({}, {
age: { type: "number", min: 0, max: 150 },
name: { type: "string" }
});
person.name = "Trung"; // OK
person.age = 25; // OK
person.age = -5; // RangeError!
person.age = "old"; // TypeError!
15. Performance Optimization: Memoization và Pure Functions
// Memoization: cache kết quả của expensive function
function memoize(fn) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
console.log("Cache hit!");
return cache.get(key);
}
console.log("Computing...");
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
}
// Fibonacci không có memoize: O(2^n)
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
// Fibonacci với memoize: O(n)
const memoFib = memoize(function fib(n) {
if (n <= 1) return n;
return memoFib(n - 1) + memoFib(n - 2);
});
console.time("no-memo");
fibonacci(40); // ~2 giây
console.timeEnd("no-memo");
console.time("with-memo");
memoFib(40); // < 1ms
console.timeEnd("with-memo");
// WeakMap-based memoization cho object keys (no memory leak)
function memoizeWeak(fn) {
const cache = new WeakMap();
return function(obj) {
if (!cache.has(obj)) {
cache.set(obj, fn(obj));
}
return cache.get(obj);
};
}
Phần 4: Câu Hỏi JavaScript Thực Tế - Tất Cả Level
16. Output của các đoạn code này là gì? (Câu hỏi trick phổ biến)
// Quiz 1: typeof
console.log(typeof null); // "object" (lỗi lịch sử của JS)
console.log(typeof undefined); // "undefined"
console.log(typeof NaN); // "number"
console.log(typeof function(){}); // "function"
console.log(typeof []); // "object"
console.log(typeof class {}); // "function"
// Quiz 2: Equality
console.log(0 == false); // true (type coercion)
console.log("" == false); // true
console.log(null == undefined); // true
console.log(null === undefined); // false
console.log(NaN === NaN); // false (NaN không bằng chính nó)
console.log(Number.isNaN(NaN)); // true (cách đúng để check)
// Quiz 3: Tricky hoisting
var name = "global";
function test() {
console.log(name); // undefined (không phải "global"!)
var name = "local";
console.log(name); // "local"
}
test();
// var name bị hoisted lên đầu function test,
// shadowing global name, nhưng chưa có value
// Quiz 4: Event loop
async function main() {
console.log("1");
await Promise.resolve();
console.log("3");
}
main();
console.log("2");
// Output: 1, 2, 3
// Quiz 5: Spread và Rest
const arr = [1, 2, 3];
const arr2 = [...arr];
arr2.push(4);
console.log(arr); // [1, 2, 3] - shallow copy, không bị ảnh hưởng
console.log(arr2); // [1, 2, 3, 4]
// Quiz 6: Object reference
const a = { x: 1 };
const b = a;
b.x = 2;
console.log(a.x); // 2 (cùng reference)
const c = { x: 1 };
const d = { ...c }; // Shallow copy
d.x = 3;
console.log(c.x); // 1 (khác reference cho top-level)
17. Implement Array methods từ đầu (câu hỏi coding challenge)
// Implement Array.prototype.map
Array.prototype.myMap = function(callback) {
const result = [];
for (let i = 0; i < this.length; i++) {
if (i in this) { // Handle sparse arrays
result[i] = callback(this[i], i, this);
}
}
return result;
};
// Implement Array.prototype.filter
Array.prototype.myFilter = function(callback) {
const result = [];
for (let i = 0; i < this.length; i++) {
if (i in this && callback(this[i], i, this)) {
result.push(this[i]);
}
}
return result;
};
// Implement Array.prototype.reduce
Array.prototype.myReduce = function(callback, initialValue) {
let accumulator = initialValue !== undefined
? initialValue
: this[0];
const startIndex = initialValue !== undefined ? 0 : 1;
for (let i = startIndex; i < this.length; i++) {
if (i in this) {
accumulator = callback(accumulator, this[i], i, this);
}
}
return accumulator;
};
// Test
const nums = [1, 2, 3, 4, 5];
console.log(nums.myMap(x => x * 2)); // [2, 4, 6, 8, 10]
console.log(nums.myFilter(x => x > 2)); // [3, 4, 5]
console.log(nums.myReduce((acc, x) => acc + x, 0)); // 15
// Implement Function.prototype.bind
Function.prototype.myBind = function(context, ...presetArgs) {
const fn = this;
return function(...args) {
return fn.apply(context, [...presetArgs, ...args]);
};
};
// Implement Promise.all
function myPromiseAll(promises) {
return new Promise((resolve, reject) => {
const results = [];
let completed = 0;
if (promises.length === 0) {
resolve([]);
return;
}
promises.forEach((promise, index) => {
Promise.resolve(promise)
.then(value => {
results[index] = value;
completed++;
if (completed === promises.length) {
resolve(results);
}
})
.catch(reject);
});
});
}
18. Design Patterns thường gặp trong JavaScript Interview
// 1. SINGLETON: đảm bảo chỉ có một instance
class DatabaseConnection {
static instance = null;
static #isCreating = false;
constructor(config) {
if (!DatabaseConnection.#isCreating) {
throw new Error("Dùng DatabaseConnection.getInstance()");
}
this.config = config;
}
static getInstance(config) {
if (!this.instance) {
this.#isCreating = true;
this.instance = new DatabaseConnection(config);
this.#isCreating = false;
}
return this.instance;
}
}
// 2. OBSERVER: publish-subscribe pattern
class EventEmitter {
constructor() {
this.listeners = {};
}
on(event, callback) {
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event].push(callback);
return () => this.off(event, callback); // Unsubscribe function
}
off(event, callback) {
if (this.listeners[event]) {
this.listeners[event] = this.listeners[event]
.filter(cb => cb !== callback);
}
}
emit(event, data) {
if (this.listeners[event]) {
this.listeners[event].forEach(cb => cb(data));
}
}
}
const emitter = new EventEmitter();
const unsub = emitter.on("data", (d) => console.log("Received:", d));
emitter.emit("data", { id: 1 }); // "Received: { id: 1 }"
unsub(); // Unsubscribe
// 3. MODULE PATTERN (trước ES modules)
const counterModule = (() => {
let count = 0; // Private
return {
increment: () => ++count,
decrement: () => --count,
getCount: () => count
};
})();
Phần 5: Câu Hỏi JavaScript 2026 - Modern Features
19. Optional Chaining, Nullish Coalescing, và các ES2022+ features
// Optional Chaining (?.) - ES2020
const user = {
profile: {
address: {
city: "HCM"
}
}
};
// Cũ: dài và dễ lỗi
const city1 = user && user.profile && user.profile.address
&& user.profile.address.city;
// Mới: ngắn gọn và safe
const city2 = user?.profile?.address?.city; // "HCM"
const zip = user?.profile?.address?.zip; // undefined (không lỗi)
// Với method calls
const result = obj?.method?.(); // undefined nếu không có method
// Nullish Coalescing (??) — ES2020
const value = null ?? "default"; // "default"
const value2 = 0 ?? "default"; // 0 (khác với ||, vì 0 không nullish)
const value3 = "" ?? "default"; // "" (khác với ||)
const value4 = false ?? "default"; // false
// So sánh ?? vs ||
console.log(0 || "fallback"); // "fallback" (0 là falsy)
console.log(0 ?? "fallback"); // 0 (0 không phải null/undefined)
// Nullish Assignment (??=) - ES2021
let a = null;
a ??= "default"; // a = "default"
let b = 0;
b ??= "default"; // b = 0 (không assign vì 0 không nullish)
// Logical Assignment
let x = null;
x ||= "value"; // x = "value" (vì null là falsy)
let y = 1;
y &&= 2; // y = 2 (vì 1 là truthy)
// at() method - ES2022
const arr = [1, 2, 3, 4, 5];
console.log(arr.at(-1)); // 5 (negative indexing!)
console.log(arr.at(-2)); // 4
console.log("hello".at(-1)); // "o"
// Object.hasOwn() - ES2022 (thay thế hasOwnProperty)
const obj = Object.create({ inherited: true });
obj.own = true;
console.log(Object.hasOwn(obj, "own")); // true
console.log(Object.hasOwn(obj, "inherited")); // false
// Array.prototype.findLast() và findLastIndex() - ES2023
const nums = [1, 2, 3, 4, 5];
console.log(nums.findLast(x => x % 2 === 0)); // 4
console.log(nums.findLastIndex(x => x % 2 === 0)); // 3
// Array grouping — ES2024
const people = [
{ name: "A", dept: "Engineering" },
{ name: "B", dept: "Design" },
{ name: "C", dept: "Engineering" }
];
const grouped = Object.groupBy(people, p => p.dept);
// { Engineering: [{...}, {...}], Design: [{...}] }
20. TypeScript phổ biến - câu hỏi JS với TS context
// Generics
function getFirst<T>(arr: T[]): T | undefined {
return arr[0];
}
const first = getFirst([1, 2, 3]); // type: number | undefined
const firstStr = getFirst(["a", "b", "c"]); // type: string | undefined
// Conditional Types
type IsArray<T> = T extends any[] ? true : false;
type A = IsArray<number[]>; // true
type B = IsArray<string>; // false
// Mapped Types
type Readonly<T> = {
readonly [K in keyof T]: T[K];
};
type Optional<T> = {
[K in keyof T]?: T[K];
};
// Utility Types hay được hỏi
interface User {
id: number;
name: string;
email: string;
password: string;
}
type PublicUser = Omit<User, "password">;
type UserPreview = Pick<User, "id" | "name">;
type PartialUser = Partial<User>;
type RequiredUser = Required<Partial<User>>;
type ReadonlyUser = Readonly<User>;
// Template Literal Types
type EventName = "click" | "focus" | "blur";
type HandlerName = `on${Capitalize<EventName>}`;
// type HandlerName = "onClick" | "onFocus" | "onBlur"
// infer trong conditional types
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type Fn = () => string;
type Result = ReturnType<Fn>; // string
Câu Hỏi Thường Gặp Về Phỏng Vấn JavaScript
Phỏng vấn JavaScript năm 2026 tập trung vào những chủ đề nào?
Năm 2026, phỏng vấn JavaScript tập trung nhiều vào: async patterns (Promise, async/await, Event Loop), closures và scope, prototypal inheritance, modern ES2020-2024 features (optional chaining, nullish coalescing, structuredClone), performance optimization (debounce, throttle, memoization), và TypeScript fundamentals. Với senior position, thêm: Design Patterns, Web Workers, Generator/Iterator, Proxy/Reflect, và system design cho frontend architecture.
Nên học JavaScript theo thứ tự nào để chuẩn bị phỏng vấn hiệu quả?
Thứ tự ưu tiên: (1) Core fundamentals: scope, hoisting, closures, this, prototype chain - đây là nền tảng mà tất cả câu hỏi khác build trên. (2) Async JavaScript: callbacks, Promises, async/await, Event Loop - chiếm 30-40% câu hỏi mid-senior level. (3) Modern features ES2020-2024: cần biết để không bị hỏi những thứ "đã deprecated". (4) Performance và patterns: debounce, throttle, memoization, design patterns. (5) TypeScript nếu apply cho vị trí yêu cầu. Dành 2-3 tuần ôn từng phần, focus vào viết code tay không phải chỉ đọc.
Interviewer muốn nghe gì khi hỏi về Event Loop?
Interviewer không chỉ muốn định nghĩa. Họ muốn bạn giải thích được: tại sao Promise.then chạy trước setTimeout dù cả hai đều async (Microtask vs Macrotask queue), tại sao JavaScript có thể non-blocking dù single-threaded (Call Stack + Web APIs + Event Loop), và quan trọng nhất là output của một đoạn code mix sync/async/Promise/setTimeout. Chuẩn bị vẽ được diagram Call Stack, Web APIs, Microtask Queue, Macrotask Queue và giải thích flow của một đoạn code cụ thể.
Nên làm bài coding challenge JavaScript như thế nào trong phỏng vấn?
Ba bước quan trọng nhất: (1) Clarify requirements trước khi code - hỏi về edge cases, input validation, và constraints. Interviewer đánh giá cao người biết hỏi đúng câu hỏi. (2) Think out loud - giải thích approach trước khi code, interviewer cần hiểu tư duy của bạn, không chỉ output. (3) Start with brute force, then optimize - một solution working nhưng O(n²) tốt hơn không có solution. Sau đó cải thiện với time/space complexity tốt hơn và giải thích trade-off. Viết code sạch với meaningful variable names ngay từ đầu.
Khác biệt giữa phỏng vấn JS cho frontend và fullstack là gì?
Frontend interview tập trung thêm vào: DOM manipulation, browser APIs (Fetch, WebSocket, Service Worker), CSS-in-JS, React/Vue/Angular specific patterns, performance (Core Web Vitals, lazy loading, code splitting), và accessibility. Fullstack interview thêm: Node.js event loop (khác browser), streams, file system APIs, REST API design, database interaction patterns, và security (SQL injection, XSS, CSRF). Core JavaScript fundamentals overlap giữa hai loại nhưng context áp dụng khác nhau đáng kể.
Kết Luận: Code Beats Memorization
Tổng hợp câu hỏi phỏng vấn JavaScript này bao gồm những gì được hỏi nhiều nhất trong năm 2026, nhưng điều quan trọng hơn là cách bạn chuẩn bị. Memorize câu trả lời không work bằng thực sự hiểu tại sao mỗi behavior xảy ra và có thể áp dụng vào những tình huống mới.
Ba thứ làm bạn nổi bật trong phỏng vấn JavaScript: (1) Giải thích được tại sao JavaScript hoạt động theo cách đó, không chỉ nó hoạt động như thế nào. (2) Viết được code tay không có IDE autocomplete cho các pattern cơ bản như debounce, memoize, custom Promise. (3) Nhận ra và nói ra trade-off của các approach khác nhau thay vì chỉ đưa ra một câu trả lời.
Bookmark bài này, mở một IDE, và gõ lại từng đoạn code. Không copy-paste. Gõ tay. Sửa lỗi khi nó chạy sai. Đó là cách duy nhất để những kiến thức này thực sự vào não và hiện ra trong phỏng vấn.