Bài viết nổi bật
Bộ Câu Hỏi Phỏng Vấn Web Developer Kèm Câu Trả Lời Chi Tiết
Tác giả: Thanh Huyền
Bộ Câu Hỏi Phỏng Vấn Web Developer Kèm Câu Trả Lời Chi Tiết
Bạn đã nắm vững React, viết được TypeScript flowing, nhưng mỗi khi bước vào phòng phỏng vấn, mọi thứ như bị xóa sạch khỏi đầu. Âm thanh bàn phím im bặt, máy tính bị thay bằng bảng trắng, và người ta yêu cầu bạn giải thích sự khác biệt giữa var, let, const - hay viết một hàm debounce từ đầu. Nếu bạn đang ở giai đoạn chuẩn bị cho vòng phỏng vấn Web Developer, bài viết này sẽ giúp bạn hiểu rõ bức tranh tổng thể: từ những câu hỏi JavaScript cơ bản nhất, qua các bài whiteboard coding, cho đến thiết kế hệ thống phía frontend. 1. Tại sao phỏng vấn Web Developer lại khó vậy? Khác với công việc hàng ngày, nơi bạn có Google, Stack Overflow, và AI assistant sẵn sàng giúp đỡ, phỏng vấn yêu cầu bạn tái hiện kiến thức từ kýức dài hạn. Không có IntelliSense, không có autocomplete, không có documentation tra cứu. Đó là lý do một câu hỏi tưởng như đơn giản như "giải thích event loop" lại làm khó được rất nhiều ứng viên senior. Bên cạnh đó, phỏng vấn Web Developer năm 2026 đã thay đổi đáng kể so với ba năm trước. Các công ty không còn chỉ hỏi syntax hoặc API của React. Họ đào sâu vào cách bạn suy nghĩ về kiến trúc, cách bạn đưa ra quyết định dựa trên ràng buộc thực tế, và liệu bạn có thể giải thích tại sao lựa chọn này tốt hơn lựa chọn kia. Một hiring manager ở Los Angeles từng chia sẻ: "Tôi đã loại một ứng viên vì câu trả lời của họ cho mọi câu hỏi về state management đều là Redux. Không sai, nhưng hẹp." Điều đó cho thấy: hiểu sâu nguyên lý quan trọng hơn thuộc lòng câu trả lời mẫu. 👉 Luyện tập trả lời câu hỏi JavaScript phỏng vấn Web Developer tại X Interview để phản xạ nhanh hơn! 2. Những câu hỏi JavaScript mà bất kỳ ai cũng phải trả lời được JavaScript là xương sống của mọi vòng phỏng vấn frontend. Dù công ty dùng React, Vue, hay Angular, họ đều kỳ vọng bạn nắm vững JS thuần. Dưới đây là những câu hỏi xuất hiện trong hầu hết mọi buổi phỏng vấn, kèm cách trả lời để bạn không chỉ "đoán đúng" mà thực sự hiểu. 1.1. Closures - khai báo một lần, truy cập mãi mãi Closure là một trong những khái niệm được hỏi nhiều nhất và cũng là nơi nhiều ứng viên bị phân loại nhất. Định nghĩa cơ bản: closure xảy ra khi một hàm ghi nhớ biến từ phạm vi bên ngoài ngay cả khi hàm đó đã được gọi bên ngoài phạm vi gốc. Câu hỏi kinh điển nhất trong phỏng vấn là bài toán vòng lặp với var: for (var i = 0; i < 3; i++) { setTimeout(function () { console.log(i); }, 100); } // Output: 3, 3, 3 - không phải 0, 1, 2 Câu trả lời phổ biến là "dùng let thay vì var". Đúng, nhưng chưa đủ để gây ấn tượng. Câu trả lời mạnh hơn sẽ giải thích tại sao var có function scope trong khi let có block scope, và tại sao closure giữ tham chiếu đến i chứ không phải giá trị tại thời điểm tạo closure. Một ứng viên thực sự hiểu closure sẽ đi xa hơn: họ có thể chỉ ra rằng closure cũng là cơ chế để tạo private state, và kể một case study thực tế nơi closure không được cleanup đúng cách dẫn đến memory leak trong một ứng dụng React. 1.2. Event loop - đừng chỉ nói "đơn luồng" Nếu bạn chỉ nói "JavaScript là đơn luồng", bạn mới đi được nửa câu trả lời. Điều interviewer thực sự muốn nghe là cách event loop phân biệt giữa macrotask và microtask. console.log('1'); setTimeout(function () { console.log('2'); }, 0); Promise.resolve().then(function () { console.log('3'); }); console.log('4'); // Output: 1, 4, 3, 2 Câu trả lời đúng: call stack chạy hết đồng bộ trước (1, 4), sau đó microtask queue (Promise callbacks) được drain hoàn toàn trước khi bất kỳ macrotask nào (setTimeout) được xử lý. Đó là lý do "3" xuất hiện trước "2". Một câu hỏi follow-up phổ biến: "Tại sao Promise.then() đượcưu tiên hơn setTimeout(..., 0)?" - câu trả lời nằm ở queue prioritization. Nếu bạn trả lời được điều này bằng ngôn ngữ tự nhiên mà không cần nhìn code, bạn đã vượt qua phần đánh giá cốt lõi. 1.3. this binding - biết đủ để không bị đánh lừa this trong JavaScript là một trong những nguồn bug phổ biến nhất trong thực tế, và cũng là câu hỏi phỏng vấn được yêu thích. Phiên bản nâng cao nhất của câu hỏi này trông như thế này: class Timer { constructor() { this.time = 0; } start() { setTimeout(function () { this.time++; console.log(this.time); }, 100); } } const timer = new Timer(); timer.start(); // this.time is undefined - 'this' đã mất context Câu trả lời đúng bao gồm ít nhất ba cách fix: .bind(this) trong constructor, arrow function ở class field, hoặc arrow function trong callback.Ứng viên senior còn phân tích được trade-off giữa các cách này: arrow function tạo một function reference mới mỗi lần render, ảnh hưởng đến performance nếu component re-render thường xuyên. 3. Whiteboard coding - khi thuật toán được viết trên bảng trắng Phần whiteboard coding kiểm tra cách bạn suy nghĩ có cấu trúc dưới áp lực. Không phải công ty nào cũng yêu cầu, nhưng nếu bạn ứng tuyển vào vị trí senior hoặc product company, khả năng cao bạn sẽ gặp ít nhất một bài whiteboard. 3.1. Debounce và Throttle - hai anh em không thể thiếu Debounce và throttle là hai pattern được hỏi liên tục vì chúng xuất hiện trong hầu hết mọi ứng dụng web thực tế: search autocomplete, resize handler, scroll event, form validation. Cách tốt nhất để trả lời là viết code từ đầu và giải thích rõ sự khác biệt. Debounce - "chờ cho đến khi yên lặng": hàm chỉ thực thi sau khi người dùng ngừng tương tác trong một khoảng thời gian nhất định. Phù hợp cho search input, nơi bạn không muốn gọi API sau mỗi keystroke. Throttle - "giới hạn tần suất": hàm đảm bảo được gọi tối đa một lần trong mỗi khoảng thời gian, bất kể người dùng tương tác bao nhiêu lần. Phù hợp cho scroll event hoặc resize event, nơi bạn cần updates nhưng không cần mỗi pixel. Một ứng viên trung bình sẽ viết đúng. Một ứng viên xuất sắc sẽ đề cập đến leading và trailing edge options, hoặc sử dụng requestAnimationFrame thay vì setTimeout để throttle scroll events để đảm bảo updates đồng bộ với paint cycle. 3.2. Deep clone - sao chép đối tượng mà không chia sẻ tham chiếu Đây là bài toán mà nhiều ứng viên vẫn không làm đúng dù nghĩ mình biết. Phân biệt shallow copy và deep copy là cốt lõi. // Shallow copy - vấn đề const original = { user: { name: 'Lan', scores: [90, 85] } }; const shallow = { ...original }; shallow.user.name = 'Minh'; shallow.user.scores.push(100); console.log(original.user.name); // 'Minh' - original bị thay đổi! Câu trả lời đầy đủ bao gồm: JSON.parse(JSON.stringify(obj)) - hoạt động nhưng không xử lý được circular references hoặc kiểu như Date, Map, Set, undefined structuredClone() - API có sẵn trong trình duyệt hiện đại, xử lý được cả circular references Recursive custom implementation - cho phép kiểm soát hoàn toàn quá trình clone Một câu hỏi follow-up thú vị: "Khi nào shallow copy là lựa chọn đúng?" - câu trả lời: khi object có cấu trúc phẳng và bạn muốn tối ưu performance, hoặc khi bạn cố ý chia sẻ một phần dữ liệu giữa các context. 3.3. Promise.all vs Promise.race - chọn đúng cho đúng bài toán Đây là câu hỏi thường xuyên bị hỏi ở mức độ trung bình, nhưng phần nhiều ứng viên chỉ nhớ được cú pháp mà không hiểu khi nào nên dùng. Promise.all - đợi tất cả hoàn thành, fail ngay khi bất kỳ promise nào reject. Phù hợp khi bạn cần tất cả kết quả mới có thể tiếp tục. Promise.race - kết quả hoặc reject của promise đầu tiên hoàn thành. Phù hợp cho timeout pattern: "nếu API không phản hồi trong 3 giây, hiển thị fallback". Promise.allSettled - đợi tất cả dù có reject hay không. Phù hợp khi bạn muốn gather mọi kết quả mà không muốn fail early. Promise.any - resolve ngay khi promise đầu tiên resolve, chỉ reject khi tất cả reject. Phù hợp cho pattern "lấy kết quả nhanh nhất từ nhiều nguồn". 👉 Thực hành whiteboard coding và thuật toán với X Interview! 4. React và State Management - phần mà ứng viên hay mắc lỗi nhất React dominate phỏng vấn frontend năm 2026, và phần nhiều ứng viên failở đây không phải vì không biết syntax, mà vì không hiểu cơ chế bên dưới. 4.1. Khi nào một component re-render - và làm sao ngăn chặn Câu trả lời cơ bản "khi state hoặc props thay đổi" là chưa đủ. Sự thật là component cũng re-render khi parent của nó re-render, bất kể props có thay đổi hay không. Đây là điều mà nhiều ứng viên không nhận ra cho đến khi gặp performance problem thực tế. React.memo giải quyết vấn đề này bằng shallow comparison props. Nhưng nếu bạn truyền một object literal hoặc một callback inline làm prop, memoization bị đánh bại hoàn toàn vì reference thay đổi mỗi lần render. useMemo và useCallback tồn tại để giải quyết chính xác vấn đề này. Một hiring manager ở Orange County kể lại: "Một ứng viên đã tìm ra memory leak trong React app của chúng tôi -12MB mỗi lần navigation vì một addEventListener trong useEffect không được cleanup, và closure giữ reference đến toàn bộ state snapshot của component. Ứng viên đó không chỉ học thuộc định nghĩa closure. Họ đã sống qua hậu quả production của nó." 4.2. useEffect cleanup - sai một lần, leak cả đời Nếu bạn đặt setTimeout trong useEffect mà không cleanup, bạn đã tạo ra một memory leak nhỏ. Nếu bạn subscribe một event listener mà không unsubscribe, ứng dụng sẽ chạy slower theo thời gian khi người dùng navigate qua lại giữa các trang. useEffect(() => { const timer = setInterval(() => { setCount((c) => c + 1); }, 1000); // Cleanup: xóa interval khi component unmount return () => clearInterval(timer); }, []); Một câu hỏi nâng cao hơn: "Sự khác biệt giữa useEffect và useLayoutEffect là gì?" - useLayoutEffect fire đồng bộ sau khi DOM thay đổi nhưng trước khi trình duyệt paint, trong khi useEffect fire bất đồng bộ sau paint. Dùng sai chỗ có thể gây ra visual flicker. 4.3. Server state vs client state - đừng nhét mọi thứ vào Redux Năm 2026, câu hỏi "Redux vs Context API" đã lỗi thời. Phiên bản hiện đại là: "Bạn có một shopping cart cần persist qua các page navigation, đồng bộ với server, handle optimistic updates, và hoạt động offline. Bạn sẽ chọn cách nào?" Ứng viên xuất sắc sẽ phân tách rõ: server state (dữ liệu từ API, cần caching, background refetch, optimistic updates) nên dùng TanStack Query hoặc SWR. Client state thuần túy (UI state như modal open/close, theme) có thể dùng Zustand, Jotai, hoặc Context tùy mức độ phức tạp. Việc nhét mọi thứ vào Redux không sai, nhưng nó phản ánh một cách suy nghĩ hẹp. 5. System Design cho Frontend - tư duy kiến trúc thay vì giao diện Vòng system design dành cho senior và staff engineer, nơi bạn được yêu cầu thiết kế kiến trúc của một tính năng hoặc ứng dụng từ đầu. Đây là vòng phân loại thực sự giữa mid-level và senior. 5.1. Rendering strategy - SSR, CSR, SSG, ISR chọn thế nào Không có câu trả lời đúng tuyệt đối. Interviewer muốn thấy bạn hiểu trade-off và chọn đúng cho từng bối cảnh cụ thể. SSR (Server-Side Rendering): Server render HTML cho mỗi request. Tốt cho nội dung cá nhân hóa và SEO. Chi phí server cao hơn. CSR (Client-Side Rendering): Browser render toàn bộ. Tốt choứng dụng behind authentication, highly interactive. SEO khó hơn nếu không có các cấu hình đặc biệt. SSG (Static Site Generation): HTML được build sẵn tại thời điểm deploy. Tốiưu nhất cho performance, phù hợp với blog, docs, landing pages. ISR (Incremental Static Regeneration): Hybrid - trang tĩnh nhưng có thể revalidate trong background. Phù hợp cho nội dung thay đổi định kỳ. Một câu hỏi cụ thể từ thực tế phỏng vấn: "Bạn có một component fetch dữ liệu và render một danh sách. Nó nên là server component hay client component, và điều gì thay đổi nếu danh sách cần một search filter?" - Câu trả lời mạnh: server component cho initial data fetch. Thêm 'use client' chỉ cho search input và logic filtering vì cần interactivity. Dữ liệu fetch giữ trên server, truyền filtered results xuống. 5.2. Performance và Core Web Vitals - điều interviewer thực sự hỏi Core Web Vitals đã trở thành tiêu chuẩn đo lường performance chính thức của Google. Interviewer không chỉ muốn bạn định nghĩa LCP, CLS, INP - họ muốn bạn giải thích cách optimize cho từng metric. LCP (Largest Contentful Paint): Tối ưu bằng cách preload hero image, dùng responsive images với srcset, loại bỏ render-blocking resources. CLS (Cumulative Layout Shift): Đặt explicit width và height trên images, reserve space cho dynamic content bằng aspect ratio boxes. INP (Interaction to Next Paint): Tối ưu bằng cách chia nhỏ long tasks, tránh main thread blocking, dùng scheduler.yield() cho các tác vụ nặng. Một case study từ thực tế: một ứng dụng React có re-render không cần thiết trên toàn bộ tree mỗi khi một button được click.Ứng viên giỏi sẽ dùng React DevTools Profiler để xác định hot path, sau đó áp dụng React.memo, useMemo, useCallback có chọn lọc thay vì apply blanket memoizationở mọi nơi - vì memoization overhead đôi khi còn lớn hơn chi phí re-render mà nó ngăn chặn. 5.3. Security - XSS và CSP không chỉ là chuyện của backend Frontend developer cần hiểu security không kém backend. XSS (Cross-Site Scripting) vẫn là attack vector phổ biến nhất trên web, và phần lớn responsibility thuộc về frontend khi render user-generated content. Những điều cần nhắc đến trong phỏng vấn: Sanitize tất cả user-generated content trước khi render, đặc biệt khi dùng innerHTML hoặc markdown parsing CSP (Content Security Policy) headers ngăn chặn inline script injection HttpOnly cookies cho auth tokens thay vì localStorage - localStorage có thể bị đọc bởi XSS Subresource Integrity (SRI) khi load third-party scripts Một câu hỏi cụ thể: "Làm thế nào để bạn render một comment từ người dùng một cách an toàn trong React?" - Câu trả lời mạnh: dùng textContent thay vì innerHTML, hoặc dùng thư viện sanitize như DOMPurify nếu cần hỗ trợ markdown/HTML cơ bản. Bạn có thể đọc thêm: Bộ Câu Hỏi Phỏng Vấn Ngành IT Bộ Câu Hỏi Phỏng Vấn Tester Tài liệu tham khảo MDN Web Docs - JavaScript React Official Documentation Google Web.dev - Best Practices 6. Kết luận Phỏng vấn Web Developer năm 2026 đã tiến hóa từ việc kiểm tra kiến thức syntax sang đánh giá cách bạn suy nghĩ có hệ thống, đưa ra quyết định kiến trúc, và giải thích tại sao lựa chọn của bạn tốt hơn alternatives trong bối cảnh cụ thể. Nắm vững JavaScript fundamentals (closures, event loop, this binding), thực hành whiteboard coding patterns (debounce, deep clone, Promise combinators), hiểu sâu React rendering và state management, và có tư duy system design - đó là bốn trụ cột bạn cần xây dựng. Điều quan trọng nhất: đừng chỉ học thuộc câu trả lời. Hãy hiểu tại sao câu trả lời đó đúng, và tìm cách áp dụng nó vào những tình huống thực tế mà bạn đã gặp trong quá khứ.Ứng viên có thể kể một case study production thực tế luôn gây ấn tượng mạnh hơn bất kỳ câu trả lời textbook nào. Bạn có thể đọc thêm: Bộ câu hỏi phỏng vấn theo ngành IT
Đọc bài viết