function createRateLimiter(max: number) {
let used = 0; // private, chỉ closure truy cập được
return () => (used < max ? (++used, true) : false);
}
const allow = createRateLimiter(2);
allow(); allow(); allow(); // true, true, false
JavaScript Core
draftedClosures, this, prototypes, event loop, promises/async, modules, memory, number/date gotchas — kèm output-prediction puzzles và debug thực tế.
0/51 rated · mastery 0%
Bài học
11 bài · ~156 phút đọc. Học theo thứ tự, rồi làm câu hỏi.- 01~14 phút· 4 câu
Kiểu dữ liệu, coercion, == vs === và || vs ??
Bảy kiểu primitive, object, luật ép kiểu ngầm (ToPrimitive, ToNumber), vì sao == khó đoán, khi nào ?? đúng hơn ||, và cách validate dữ liệu ở biên.
- 02~13 phút· 5 câuverify
Number, BigInt, JSON, deep clone và Date
IEEE 754 double và vì sao 0.1 + 0.2 lệch, biểu diễn tiền và ID 64-bit, giới hạn của JSON, structuredClone, và bug lệch ngày do timezone.
- 03~15 phút· 6 câuverify
Scope, hoisting, TDZ và closure
Lexical scope và environment record, var/let/const khác nhau thế nào, vì sao TDZ tồn tại, closure giữ gì, và stale closure trong React hooks.
- 04~13 phút· 4 câuverify
this, arrow function và các cách gọi function
Bốn luật binding của this theo thứ tự ưu tiên, arrow function khác regular function ở đâu, mất this khi truyền method làm callback, và tagged template literal.
- 05~13 phút· 4 câu
Prototype chain, class, copy nông/sâu và prototype pollution
Property lookup qua [[Prototype]], class thực chất biên dịch thành gì, vì sao spread chỉ là shallow copy, Object.freeze một tầng, và lỗ hổng prototype pollution khi merge JSON.
- 06~14 phút· 5 câuverify
Event loop: call stack, macrotask, microtask và timer
JavaScript single-threaded chạy thế nào, thứ tự sync → microtask → macrotask → render, vì sao promise có thể bỏ đói timer và UI, giới hạn của setTimeout, và xử lý việc nặng CPU.
- 07~14 phút· 5 câuverify
Promise, async/await, combinator và giới hạn concurrency
Promise state machine, chaining và error propagation, all/allSettled/race/any, bug forEach(async), return vs return await, promise pool tự viết, và chuẩn hoá code async trong một codebase lớn.
- 08~14 phút· 6 câuverify
Error handling, unhandled rejection và cancellation
try/catch bắt được gì và bỏ sót gì, unhandled rejection trong browser và Node, EventEmitter với listener async, thiết kế error class có cause, AbortController, và một httpClient có timeout/retry.
- 09~12 phút· 4 câuverify
Iterator, generator, async iterator và xử lý dữ liệu lớn
Iteration protocol, generator như hàm tạm dừng được, async generator cho API phân trang, for await với stream, backpressure, và cách xử lý file CSV triệu dòng không chặn event loop.
- 10~16 phút· 6 câuverify
Module: ES Modules, CommonJS, circular import và migration
Module được load, link và evaluate thế nào, live binding so với copy, circular dependency trong CJS và ESM, require(esm), dual package hazard, dynamic import và lỗi chunk, cùng conventions cho monorepo.
- 11~18 phút· 7 câuverify
Memory, garbage collection, leak và hidden class trong V8
Reachability và GC thế hệ của V8, những thứ giữ object sống (cache, listener, closure, timer), Map vs WeakMap vs WeakRef, tìm leak bằng heap snapshot ở Node và SPA, và vì sao shape của object ảnh hưởng tốc độ.
Tóm tắt & cheat sheet của track
TL;DR
- JavaScript là ngôn ngữ single-threaded chạy trên một event loop; mọi thứ bất đồng bộ (timer, I/O, promise) đều là callback được xếp hàng chờ call stack rỗng.
- Senior interview hiếm khi hỏi "closure là gì" — họ hỏi tại sao đoạn code này in ra thế này, leak ở đâu, promise nào bị nuốt lỗi.
- Nắm chắc 5 trục: scope & closure,
thisbinding, prototype chain, event loop (macro vs micro task), promise error propagation.
Khái niệm cốt lõi
| Khái niệm | Một câu | Ví dụ ngắn |
|---|---|---|
| Closure | Function "nhớ" biến của scope nơi nó được tạo | const inc = counter(); inc(); inc() giữ count riêng |
this | Được quyết định lúc gọi, không phải lúc khai báo (trừ arrow function) | obj.fn() → this = obj; const f = obj.fn; f() → undefined (strict) |
| Prototype | Object tra cứu property theo chuỗi __proto__ | class chỉ là syntax sugar trên prototype |
| Microtask | Promise callbacks, queueMicrotask — chạy hết trước macrotask kế tiếp | Promise.resolve().then(...) chạy trước setTimeout(..., 0) |
| TDZ | let/const bị hoisted nhưng chưa khởi tạo | Truy cập trước khai báo → ReferenceError |
| ESM live binding | import là tham chiếu sống tới binding của module xuất | export let n thay đổi → importer thấy giá trị mới |
| Generator / iterator | Function tạm dừng được (yield), trả iterator lazy | Duyệt API phân trang bằng for await...of |
| WeakMap / WeakRef | Giữ reference không ngăn GC thu hồi key/target | Metadata gắn vào DOM node / request object |
| Number (IEEE 754 double) | Mọi number là float 64-bit; số nguyên an toàn tới 2^53 - 1 | 0.1 + 0.2 !== 0.3; id 64-bit từ DB phải là string/BigInt |
Cơ chế hoạt động
Event loop (browser & Node, mô hình chung)
Node.js có thêm
process.nextTickqueue (chạy trước promise microtasks) và các phase của libuv — xem track 05 Node.js Internals.
Promise lifecycle & error propagation
- Mỗi
.then/.catchtrả về promise mới; lỗi "trôi" xuống tới handler gần nhất. returntrong.thenquan trọng: quênreturn(hoặc quênawait) = promise bị "floating", lỗi không tới caller.
Scope chain & closure
Closure giữ cả environment, không chỉ biến nó dùng (engine có tối ưu, nhưng đừng dựa vào) → callback sống lâu (timer, listener, cache) có thể giữ object lớn khỏi GC.
Trade-offs
| Lựa chọn | Ưu | Nhược | Dùng khi |
|---|---|---|---|
async/await tuần tự | Dễ đọc, stack trace rõ | Chậm nếu các bước độc lập | Bước sau phụ thuộc bước trước |
Promise.all | Song song, fail-fast | Không giới hạn concurrency, không huỷ phần còn lại | Vài request độc lập bắt buộc |
Promise pool / p-limit | Song song có giới hạn | Thêm code/dependency | Hàng trăm+ task gọi DB/API |
| Callback / EventEmitter | Nhiều sự kiện theo thời gian, không cần promise | Dễ leak listener, lỗi không propagate | Stream, socket, event bus |
Async iterator (for await) | Lazy, có backpressure tự nhiên | Tuần tự theo mặc định | Duyệt phân trang, stream lớn |
class | Rõ ràng, private #field, tooling tốt | this binding khi truyền method làm callback | Domain object có state |
| Closure / factory | Private tự nhiên, không lo this | Mỗi instance một bản function (memory) | Module nhỏ, hook, middleware |
number | Nhanh, native | Mất chính xác với tiền/số lớn | Đếm, tính toán thông thường |
BigInt / decimal lib / minor units | Chính xác | Chậm hơn, không trộn với number | Tiền, id 64-bit |
Pitfalls
forEach(async ...)không chờ gì cả;array.map(async)mà quênPromise.all.- Truyền
obj.methodlàm callback → mấtthis; sửa bằng arrow field hoặc.bind. constchỉ chặn gán lại, không làm object bất biến;Object.freezechỉ freeze một tầng.- Spread
{...obj}/[...arr]là shallow copy → mutate nested vẫn ảnh hưởng bản gốc (state React, Redux). sort,reverse,splicemutate mảng gốc — dùngtoSorted,toReversed,toSpliced(ES2023).JSON.parseid 64-bit (9007199254740993) → mất chính xác;JSON.stringifyBigInt →TypeError.new Date('2026-09-29')là UTC nhưngnew Date('2026-09-29T00:00')là local time → lệch ngày theo timezone.- Event listener / interval không cleanup → memory leak (SPA, Socket.IO client, long-running Node).
||thay cho??làm mất giá trị hợp lệ0,'',false.
Cheat sheet
Promise.allfail-fast ·allSettledkhông bao giờ reject ·race= cái settle đầu tiên ·any= cái fulfill đầu tiên (reject bằngAggregateErrornếu tất cả fail).asyncfunction luôn trả về Promise;throwtrong nó = reject.==coercion:null == undefinedlàtrue, nhưngnull == 0làfalse.typeof null === "object"(bug lịch sử) ·typeof function(){} === "function".- Arrow function: không có
this,arguments,prototype; không dùng làm constructor. varfunction-scoped + hoistedundefined·let/constblock-scoped + TDZ · function declaration hoisted cả body.??chỉ fallback khinull/undefined·?.short-circuit khinull/undefined.Number.MAX_SAFE_INTEGER = 2^53 - 1; so sánh float dùng epsilon; tiền dùng minor units (cents) hoặc decimal library.structuredClonecho deep copy; spread chỉ shallow.WeakMapkey phải là object (hoặc non-registered symbol); không iterate được, không cósize.- Microtask chạy hết trước macrotask kế tiếp → vòng lặp microtask vô hạn làm treo cả timer và render.
for...induyệt key enumerable (kể cả kế thừa) ·for...ofduyệt giá trị qua iterator.
References
- MDN — Event loop / concurrency model ↗
- MDN — Closures ↗
- javascript.info — Microtasks ↗
- MDN — Using promises ↗
- MDN — this ↗
- MDN — Inheritance and the prototype chain ↗
- MDN — Memory management ↗
- MDN — Iterators and generators ↗
- MDN — JavaScript modules ↗
- ECMAScript Language Specification (latest) ↗
- Node.js — Modules — ECMAScript modules ↗
- HTML Standard — Event loops ↗
Interview Questions
Showing 51 / 51
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
queueMicrotask(() => console.log('4'));
(async () => {
console.log('5');
await null;
console.log('6');
})();
console.log('7');
async function saveAll(orders: Order[]) {
const results = [];
orders.forEach(async (o) => {
try {
results.push(await db.save(o));
} catch (e) {
logger.error(e);
}
});
return results; // always []
}
const cache = new Map<string, User>();
export async function getUser(req: Request) {
const key = `${req.headers.get('x-request-id')}:${req.userId}`;
if (!cache.has(key)) cache.set(key, await db.users.find(req.userId));
return cache.get(key)!;
}
console.log(typeof a, typeof b);
var a = 1;
let b = 2;
const cfg = { retries: 0, label: '' };
cfg.retries || 3; // 3 (bug)
cfg.retries ?? 3; // 0
const cart = { id: 'c1', items: [{ sku: 'A', qty: 1 }] };
const next = { ...cart };
next.items[0].qty = 5;
console.log(cart.items[0].qty); // ?
setTimeout(() => console.log('t1'), 0);
Promise.resolve().then(() => {
console.log('p1');
setTimeout(() => console.log('t2'), 0);
Promise.resolve().then(() => console.log('p2'));
});
Promise.resolve().then(() => console.log('p3'));
console.log('s');
class Counter {
count = 0;
inc() { this.count++; }
}
const c = new Counter();
const { inc } = c;
try { inc(); } catch (e) { console.log((e as Error).constructor.name); }
setTimeout(c.inc, 0);
async function f() {
try { return Promise.reject(new Error('x')); } catch { return 'caught'; }
}
async function g() {
try { return await Promise.reject(new Error('x')); } catch { return 'caught'; }
}
// f() → ? g() → ?
0.1 + 0.2; // 0.30000000000000004
JSON.parse('{"id":9007199254740993}').id; // 9007199254740992
new Intl.NumberFormat('vi-VN', { style: 'currency', currency: 'VND' }).format(125000);
async function* products(client: Api, signal?: AbortSignal) {
let cursor: string | undefined;
do {
const page = await client.list({ cursor, limit: 100, signal });
yield* page.items;
cursor = page.nextCursor;
} while (cursor);
}
for await (const p of products(api)) {
await indexer.push(p); // natural backpressure: next page fetched only when needed
if (done()) break; // break → generator's finally/return runs
}
async function getJson<T>(url: string, parent?: AbortSignal): Promise<T> {
const signal = AbortSignal.any([AbortSignal.timeout(3000), ...(parent ? [parent] : [])]);
const res = await fetch(url, { signal });
if (!res.ok) throw new Error(`HTTP ${res.status}`, { cause: { url, status: res.status } });
return res.json() as Promise<T>;
}
// counter.mjs // counter.cjs
export let count = 0; let count = 0;
export function inc() { count++; } module.exports = { count, inc: () => { count++; } };
// main.mjs // main.cjs
import { count, inc } from './counter.mjs';
inc(); console.log(count); const { count, inc } = require('./counter.cjs');
inc(); console.log(count);
// API returns: { "deliveryDate": "2026-03-08" }
const d = new Date(order.deliveryDate);
label.textContent = `${d.getMonth() + 1}/${d.getDate()}`; // US users see 3/7
function sql(strings: TemplateStringsArray, ...values: unknown[]) {
const text = strings.reduce((acc, s, i) => acc + s + (i < values.length ? `$${i + 1}` : ''), '');
return { text, values };
}
const q = sql`SELECT * FROM orders WHERE tenant_id = ${tenantId} AND id = ${orderId}`;
// { text: 'SELECT * FROM orders WHERE tenant_id = $1 AND id = $2', values: [tenantId, orderId] }
class AppError extends Error {
constructor(public code: string, message: string, options?: { cause?: unknown; status?: number }) {
super(message, { cause: options?.cause });
this.name = new.target.name;
}
}
class NotFound extends AppError {}
try { await repo.get(id); }
catch (e) { throw new AppError('ORDER_LOAD_FAILED', 'Cannot load order', { cause: e }); }
function Elapsed() {
const [sec, setSec] = useState(0);
useEffect(() => {
const id = setInterval(() => setSec(sec + 1), 1000);
return () => clearInterval(id);
}, []);
return <span>{sec}s</span>;
}
async function processAll(items: Item[]) {
for (const it of items) {
await transform(it); // transform is sync work wrapped in an async function
}
}
setTimeout(() => console.log('fired'), 30 * 24 * 3600 * 1000);
// (node) TimeoutOverflowWarning: 2592000000 does not fit into a 32-bit signed integer.
// Timeout duration was set to 1. → prints "fired" almost immediately
// config.ts
import { log } from './logger';
export const config = { level: process.env.LOG_LEVEL ?? 'info' };
log('config loaded');
// logger.ts
import { config } from './config';
const threshold = config.level; // top-level read, added in the refactor
export function log(msg: string) { if (threshold !== 'silent') console.log(msg); }
// entry: import './config'
import { EventEmitter } from 'node:events';
const bus = new EventEmitter();
bus.on('order.created', async (order) => {
await inventory.reserve(order); // may reject
});
try {
bus.emit('order.created', order);
} catch (e) {
logger.error(e); // never runs
}
function merge(target: any, src: any) {
for (const key in src) {
if (typeof src[key] === 'object' && src[key] !== null) {
target[key] ??= {};
merge(target[key], src[key]);
} else {
target[key] = src[key];
}
}
return target;
}
merge(settings, JSON.parse('{"__proto__": {"isAdmin": true}}'));
console.log(({} as any).isAdmin); // true
async function run() {
try {
JSON.parse('{bad'); // (1)
setTimeout(() => { throw new Error('t'); }, 0); // (2)
doAsyncThing(); // (3) returns a rejected promise, not awaited
await Promise.reject(new Error('a')); // (4)
} catch (e) {
console.log('caught', (e as Error).message);
}
}