April 10, 2025

Câu hỏi phỏng vấn JavaScript cho Senior
Tuyển tập các câu hỏi phỏng vấn JavaScript cơ bản dành cho Senior, kèm giải thích dễ hiểu và ví dụ minh hoạ.
Q: Event Loop và Call Stack hoạt động như thế nào?
A: JavaScript là single-threaded, nhưng sử dụng Event Loop để xử lý bất đồng bộ. Mỗi khi gọi setTimeout
, Promise
, hay fetch
, các callback được đưa vào Web APIs → sau đó đưa về Task Queue hoặc Microtask Queue. Event Loop sẽ kiểm tra Call Stack trống rồi đẩy task vào thực thi. Microtask có ưu tiên hơn macro task.
Q: Microtask và Macrotask khác nhau ra sao?
A:
- Microtask:
Promise.then
,MutationObserver
, có độ ưu tiên cao, chạy sau mỗi tick event loop. - Macrotask:
setTimeout
,setInterval
,setImmediate
.
Microtask luôn chạy trước macrotask nếu cả hai cùng được tạo trong 1 vòng event loop.
Q: Memoization là gì và ứng dụng trong JS?
A: Memoization là kỹ thuật cache kết quả của function với đầu vào nhất định để tránh tính toán lại. Giúp tối ưu performance, đặc biệt trong recursion hoặc tính toán nặng.
function memoize(fn) {
const cache = {};
return (x) => (x in cache ? cache[x] : (cache[x] = fn(x)));
}
Q: Khi nào cần WeakMap thay vì Map?
A: WeakMap
không ngăn garbage collection key (phải là object). Dùng khi lưu metadata liên quan đến DOM element hoặc object tạm thời, tránh memory leak.
Q: Làm sao để tránh memory leak trong JS app lớn?
A:
- Xoá event listener khi không dùng
- Không giữ reference tới DOM element không còn tồn tại
- Sử dụng WeakMap cho cache tạm
- Cẩn thận với global variables và closure giữ reference không cần thiết
Q: Tree shaking là gì?
A: Tree shaking là kỹ thuật loại bỏ code thừa không được sử dụng trong bundle cuối cùng. Nó phụ thuộc vào ES Modules (static import/export). Tools như Webpack hoặc Rollup thực hiện tree shaking để tối ưu kích thước file.
Q: So sánh debounce và throttle?
A:
debounce(fn, delay)
: chỉ gọifn
sau khi ngừng hành động trongdelay
msthrottle(fn, delay)
: gọifn
tối đa 1 lần mỗidelay
ms
Ứng dụng: search input (debounce), resize/scroll (throttle)
Q: Làm sao để kiểm tra memory leak bằng Chrome DevTools?
A:
- Mở tab “Memory”
- Dùng “Heap Snapshot” hoặc “Allocation instrumentation”
- Gắn ref trong code (window.__leak = something) để kiểm tra object còn tồn tại hay không
- Dùng timeline để theo dõi heap không giảm sau khi bỏ thao tác
Q: Khi nào nên dùng Web Worker?
A: Dùng khi cần thực hiện tác vụ nặng (tính toán, xử lý hình ảnh, JSON lớn…) mà không muốn block main thread. Web Worker chạy trên thread riêng và giao tiếp qua postMessage
.
Q: Làm sao để tránh render thừa trong React?
A:
- Dùng
React.memo
để tránh render component không thay đổi props - Dùng
useMemo
,useCallback
để cache function/values - Tránh khai báo inline function không cần thiết
- Tránh truyền object/array mới mỗi render nếu không thay đổi
Q: Singleton pattern trong JS là gì?
A: Là mẫu thiết kế đảm bảo 1 class chỉ có 1 instance. Trong JS, ta có thể sử dụng module pattern hoặc closure.
const Singleton = (() => { let instance; return { getInstance: () => { if (!instance) instance = { created: Date.now() }; return instance; }, }; })();
Q: Làm sao để phát hiện performance bottleneck trong JS app?
A:
- Dùng Chrome DevTools tab “Performance” để ghi lại hoạt động
- Tìm các “long tasks” (màu đỏ)
- Tối ưu lại code nào chiếm nhiều thời gian CPU
- Tách logic, dùng lazy load, debounce…
Q: Module bundler như Webpack hoạt động thế nào?
A: Webpack phân tích các module (JS, CSS, image…) bắt đầu từ entry point, dựng dependency graph, áp dụng loader & plugin, sau đó bundle lại thành các file để browser hiểu. Giúp tối ưu kích thước và cấu trúc project.
Q: Khi nào nên dùng Object.freeze
?
A: Khi cần tạo object immutable – không thay đổi được key/value. Dùng trong Redux, hoặc các cấu hình không được chỉnh sửa.
const config = Object.freeze({ env: "prod" });
Q: So sánh SSR và CSR trong hiệu suất?
A:
- SSR (Server-side Rendering): render HTML từ server → nhanh cho lần đầu, tốt SEO
- CSR (Client-side Rendering): tải JS rồi mới render → tải đầu chậm nhưng mượt sau đó
Next.js hỗ trợ cả hai và có thể hybrid theo trang
Q: import()
khác gì require()
?
A:
require()
là CommonJS, sync, chỉ dùng trong Node.jsimport()
là ES Module, async, có thể dùng trong trình duyệt với dynamic load
Trong frontend hiện đại, dùngimport()
để lazy load component, route…
Q: Khi nào nên dùng Proxy trong JS?
A: Dùng khi muốn can thiệp vào hành vi của object như get/set, validate, log, reactive. Vue 3 dùng Proxy để tạo reactivity system.
const person = new Proxy({}, {
set(obj, key, val) {
console.log(`Setting ${key}`);
obj[key] = val;
return true;
}
});
Q: Làm sao để unit test code async?
A: Dùng framework như Jest + async/await
hoặc done()
callback. Luôn mock API hoặc hàm bất đồng bộ, test cả case lỗi.
test('fetches data', async () => { const data = await fetchData(); expect(data).toBeTruthy(); });
Q: Làm sao để phân tích bundle size?
A:
- Dùng Webpack Bundle Analyzer để xem cấu trúc bundle
- Kiểm tra module nặng
- Tối ưu bằng code splitting, lazy load, tree shaking
- Tránh import toàn bộ thư viện như lodash, moment
Q: Làm sao để bảo vệ frontend khỏi XSS (Cross-site Scripting)?
A:
- Escape mọi dữ liệu khi render HTML (tránh
innerHTML
khi không cần) - Dùng framework hỗ trợ tự escape như React
- Sử dụng CSP (Content Security Policy) headers để giới hạn script nguồn ngoài
- Không bao giờ tin input từ user – luôn sanitize dữ liệu
Q: Tại sao nên tránh sử dụng eval()
trong JavaScript?
A:
eval()
thực thi string như code – dễ gây lỗ hổng bảo mật (XSS) và làm chậm performance do browser không thể tối ưu hoá code dynamic. Hầu hết use-case có thể giải quyết bằng JSON, Function constructor hoặc template engine.
Q: Làm sao để tối ưu bundle trong ứng dụng lớn?
A:
- Dùng
dynamic import()
để lazy load - Code splitting bằng route/component
- Tree shaking với ES modules
- Giảm dùng polyfill không cần thiết
- Gộp common dependencies và tách vendor bundle riêng
Q: Khi nào nên dùng Service Worker?
A: Khi muốn ứng dụng hoạt động offline, cache tài nguyên (HTML, CSS, JS), hoặc xử lý push notification. Service Worker chạy tách khỏi main thread và kiểm soát request network.
Q: So sánh cấu trúc monolith frontend vs microfrontend?
A:
- Monolith: tất cả UI, logic trong 1 repo → dễ quản lý ban đầu, khó scale team lớn
- Microfrontend: chia nhỏ UI theo module, team riêng biệt quản lý → tốt cho dự án lớn, cần chuẩn giao tiếp (Web Components, iframe, Module Federation...)
Q: Intl.DateTimeFormat
và toLocaleDateString()
khác gì?
A: Intl.DateTimeFormat
cho phép cấu hình phức tạp và tái sử dụng nhiều lần. toLocaleDateString()
là shortcut đơn giản, ít tùy chọn hơn.
const formatter = new Intl.DateTimeFormat('vi-VN', { dateStyle: 'long' });
console.log(formatter.format(new Date())); // "10 tháng 4, 2025"
Q: Làm sao kiểm tra có memory leak trên production?
A:
- Dùng Performance API (
performance.memory
) để monitor heap - Kết hợp analytics để tracking tương tác người dùng
- Gửi report lên server khi memory vượt ngưỡng
- Kết hợp Chrome DevTools để phân tích nếu có crash/log nghi ngờ
Q: Khi nào nên tránh async/await
và dùng stream/observable?
A:
Khi làm việc với data liên tục, không có điểm kết thúc cụ thể (WebSocket, file lớn, sensor input...) → async/await
chỉ xử lý 1 lần. Stream/Observable giúp xử lý luồng dữ liệu dài, có thể hủy giữa chừng, retry linh hoạt.
Q: Làm sao tối ưu ứng dụng React hiển thị danh sách lớn?
A:
- Virtualization: chỉ render phần tử trong viewport (
react-window
,react-virtualized
) - Pagination hoặc infinite scroll
- Memoization và tránh rerender list item không thay đổi
- Skeleton UI để che loading
Q: Làm sao để cấu hình ứng dụng đa ngôn ngữ hiệu quả?
A:
- Tách file JSON per language
- Dùng thư viện i18n như
react-i18next
,next-intl
- Hỗ trợ lazy load locale để giảm bundle size
- Format number, date, currency theo vùng
Q: Module Federation trong Webpack là gì?
A: Cho phép app này import module từ app khác runtime mà không cần build chung. Dùng trong microfrontend, giúp các team deploy module độc lập.
Q: Làm sao để viết code JS bảo trì tốt trong team lớn?
A:
- Tuân theo convention (naming, structure)
- Dùng TypeScript để tăng tính rõ ràng
- Viết test (unit + integration)
- Có CI/CD kiểm tra build, lint
- Tách logic domain ra khỏi UI
Q: So sánh giữa requestIdleCallback
và setTimeout
?
A: requestIdleCallback
chỉ chạy khi browser rảnh → dùng để xử lý background task không khẩn cấp. setTimeout
chạy sau khoảng thời gian xác định, không quan tâm CPU đang bận hay không.
Q: Closure dùng trong thực tế để làm gì?
A:
- Ẩn biến private
- Tạo factory function
- Làm debounce/throttle
- Tạo cache/memoize
Closure giữ reference đến scope cũ ngay cả sau khi function đã return.
Q: Làm sao phát hiện duplicate code trong project lớn?
A:
- Dùng ESLint plugin hoặc tool như SonarQube, CodeClimate
- Refactor khi thấy code smell
- Tạo utilities/helper dùng chung
- Viết test để đảm bảo logic refactor không sai
Q: Dùng CDN có lợi gì so với tải từ server app?
A:
- Phân phối gần client hơn → tải nhanh hơn
- Tự động cache
- Giảm tải lên server chính
- Tối ưu SEO và tốc độ initial load
Q: Làm sao để scale ứng dụng JS nhiều team cùng làm?
A:
- Áp dụng microfrontend hoặc module federation
- Đặt convention code rõ ràng
- Có core team quản lý shared component
- Tự động hóa CI/CD và code review
Q: Khi nào nên dùng WebSocket thay vì HTTP?
A:
- Khi cần giao tiếp real-time hai chiều (chat, game, trading...)
- HTTP phù hợp với request-response; WebSocket cho kết nối mở lâu dài
- Dùng thêm heartbeat để giữ kết nối
Q: Làm sao tổ chức test hiệu quả cho app JS lớn?
A:
- Unit test cho logic đơn vị (Jest, Vitest)
- Integration test khi các module tương tác
- E2E test (Cypress, Playwright) cho flow người dùng
- Mock API để test nhanh và ổn định
JavaScript Interview Questions for Senior
A collection of basic JavaScript interview questions for Senior, with clear explanations and illustrative examples.
Q: How do Event Loop and Call Stack work?
A: JavaScript is single-threaded but uses an Event Loop to handle async tasks. When calling setTimeout
, Promise
, or fetch
, callbacks go to Web APIs → then to Task Queue or Microtask Queue. The Event Loop checks if the Call Stack is empty before pushing tasks in. Microtasks are prioritized over macrotasks.
Q: What's the difference between Microtask and Macrotask?
A:
- Microtask:
Promise.then
,MutationObserver
; high priority, runs after each event loop tick. - Macrotask:
setTimeout
,setInterval
,setImmediate
.
Microtasks always run before macrotasks within the same event loop cycle.
Q: What is Memoization and how is it used in JS?
A: Memoization caches the result of a function based on input to avoid recomputation. It improves performance, especially for recursion or heavy calculations.
function memoize(fn) {
const cache = {};
return (x) => (x in cache ? cache[x] : (cache[x] = fn(x)));
}
Q: When should you use WeakMap instead of Map?
A: WeakMap
allows garbage collection of object keys. Use it to store metadata tied to DOM elements or temporary objects to avoid memory leaks.
Q: How to prevent memory leaks in large JS apps?
A:
- Remove event listeners when not needed
- Avoid holding references to removed DOM elements
- Use WeakMap for temporary caching
- Be careful with global variables and closures holding unnecessary references
Q: What is Tree Shaking?
A: Tree shaking eliminates unused code from the final bundle. It relies on ES Modules (static imports/exports). Tools like Webpack or Rollup perform tree shaking for optimized file size.
Q: Compare debounce and throttle.
A:
debounce(fn, delay)
: callsfn
only afterdelay
ms of no actionthrottle(fn, delay)
: callsfn
at most once everydelay
ms
Use debounce for inputs (search), throttle for resize/scroll events.
Q: How to detect memory leaks with Chrome DevTools?
A:
- Open the “Memory” tab
- Use “Heap Snapshot” or “Allocation instrumentation”
- Attach refs in code (e.g.,
window.__leak = something
) - Use the timeline to check if heap size remains high after actions
Q: When to use Web Worker?
A: Use for heavy tasks (computation, image processing, large JSON...) that would block the main thread. Web Workers run on a separate thread and communicate via postMessage
.
Q: How to avoid unnecessary renders in React?
A:
- Use
React.memo
for components with unchanged props - Use
useMemo
,useCallback
to cache functions/values - Avoid inline functions unless needed
- Avoid passing new object/array references on each render
Q: What is Singleton pattern in JS?
A: Ensures a class has only one instance. In JS, can be implemented using closures or module pattern.
const Singleton = (() => { let instance; return { getInstance: () => { if (!instance) instance = { created: Date.now() }; return instance; }, }; })();
Q: How to identify performance bottlenecks in JS apps?
A:
- Use Chrome DevTools “Performance” tab
- Look for “long tasks” (red blocks)
- Optimize CPU-heavy code
- Split logic, use lazy load, debounce, etc.
Q: How does a module bundler like Webpack work?
A: Webpack analyzes modules (JS, CSS, images…) from the entry point, builds a dependency graph, applies loaders and plugins, then bundles everything into files for the browser.
Q: When should you use Object.freeze
?
A: When creating immutable objects — values and keys cannot be changed. Useful in Redux or for constant config objects.
const config = Object.freeze({ env: "prod" });
Q: Compare SSR and CSR in terms of performance.
A:
- SSR (Server-side Rendering): renders HTML on the server → fast first load, good for SEO
- CSR (Client-side Rendering): loads JS and then renders → slower initial load but smoother afterward
Next.js supports both and allows per-page hybrid rendering.
Q: What's the difference between import()
and require()
?
A:
require()
is CommonJS, synchronous, Node.js onlyimport()
is ES Module, async, usable in browsers
Useimport()
for lazy-loading components or routes in modern frontend.
Q: When to use Proxy in JS?
A: To intercept object behavior like get/set, validation, logging, reactivity. Vue 3 uses Proxy for its reactivity system.
const person = new Proxy({}, {
set(obj, key, val) {
console.log(`Setting ${key}`);
obj[key] = val;
return true;
}
});
Q: How to unit test async code?
A: Use frameworks like Jest with async/await
or done()
callback. Always mock APIs or async functions and test failure cases too.
test('fetches data', async () => { const data = await fetchData(); expect(data).toBeTruthy(); });
Q: How to analyze bundle size?
A:
- Use Webpack Bundle Analyzer
- Identify large modules
- Optimize using code splitting, lazy load, tree shaking
- Avoid importing entire libraries like lodash, moment
Q: How to protect frontend from XSS?
A:
- Escape all dynamic content in HTML (avoid
innerHTML
when possible) - Use frameworks that auto-escape (e.g., React)
- Set CSP (Content Security Policy) headers
- Never trust user input — always sanitize it
Q: Why should you avoid using eval()
in JS?
A:
eval()
executes a string as code — a major security risk (XSS) and performance killer, as the browser can’t optimize dynamic code. Prefer JSON, Function
constructor, or template engines.
Q: How to optimize bundle in large applications?
A:
- Use
dynamic import()
for lazy loading - Apply code splitting by route/component
- Enable tree shaking via ES Modules
- Avoid unnecessary polyfills
- Split vendor and common dependencies
Q: When to use Service Worker?
A: When you want offline support, cache resources (HTML, CSS, JS), or handle push notifications. Runs in a separate thread and intercepts network requests.
Q: Compare monolith frontend vs microfrontend.
A:
- Monolith: all UI and logic in one repo → easier at first, hard to scale across teams
- Microfrontend: split UI into modules managed by separate teams → good for large projects, requires clear communication and tools (Web Components, iframe, Module Federation…)
Q: Difference between Intl.DateTimeFormat
and toLocaleDateString()
?
A: Intl.DateTimeFormat
is more configurable and reusable. toLocaleDateString()
is a simpler shortcut.
const formatter = new Intl.DateTimeFormat('vi-VN', { dateStyle: 'long' });
console.log(formatter.format(new Date())); // "April 10, 2025"
Q: How to detect memory leaks in production?
A:
- Use
performance.memory
to monitor heap - Combine with analytics to track user interactions
- Send reports when memory exceeds threshold
- Analyze with Chrome DevTools if crashes/logs indicate issues
Q: When should you avoid async/await
and use stream/observable?
A: When dealing with continuous data without a clear end (WebSocket, large files, sensor input...) → async/await
handles one-time async. Streams/Observables support long-running flows, cancellation, and retries.
Q: How to optimize large list rendering in React?
A:
- Use virtualization libraries (
react-window
,react-virtualized
) - Implement pagination or infinite scroll
- Memoize and avoid unnecessary re-renders
- Show skeleton UI for better UX
Q: How to configure multilingual apps effectively?
A:
- Separate JSON files per language
- Use i18n libraries (
react-i18next
,next-intl
) - Lazy load locales to reduce bundle size
- Format number, date, currency based on locale
Q: What is Module Federation in Webpack?
A: Allows one app to import modules from another app at runtime without bundling them together. Used in microfrontend architectures for independent deployment.
Q: How to write maintainable JS code in large teams?
A:
- Follow code conventions (naming, structure)
- Use TypeScript for better clarity
- Write unit and integration tests
- Set up CI/CD for build, lint checks
- Separate domain logic from UI components
Q: Compare requestIdleCallback
vs setTimeout
.
A: requestIdleCallback
runs when the browser is idle — good for background/non-urgent tasks. setTimeout
runs after a fixed delay regardless of CPU load.
Q: Practical use cases of Closure?
A:
- Create private variables
- Build factory functions
- Implement debounce/throttle
- Create caches/memoization
Closures retain access to their outer scope even after function returns.
Q: How to detect duplicate code in large projects?
A:
- Use ESLint plugins or tools like SonarQube, CodeClimate
- Refactor on code smell
- Create shared utilities/helpers
- Write tests to ensure safe refactoring
Q: Benefits of using CDN over app server?
A:
- Closer to users → faster load
- Automatic caching
- Offloads traffic from main server
- Improves SEO and initial load time
Q: How to scale JS apps with multiple teams?
A:
- Use microfrontend or module federation
- Establish clear code conventions
- Maintain a core team for shared components
- Automate CI/CD and enforce code reviews
Q: When to use WebSocket instead of HTTP?
A:
- For real-time two-way communication (chat, games, trading...)
- HTTP is request-response; WebSocket keeps long-lived connections
- Use heartbeat to maintain connection
Q: How to organize testing in large JS apps?
A:
- Unit tests for isolated logic (Jest, Vitest)
- Integration tests for module interactions
- E2E tests (Cypress, Playwright) for user flows
- Mock APIs for fast and stable testing