Câu hỏi phỏng vấn Senior Frontend Engineer Và cách mình sẽ trả lời
Tổng hợp 25+ câu hỏi phỏng vấn React, TypeScript, JavaScript nâng cao kèm tips trả lời từ góc nhìn senior frontend engineer.
Nguyen Nhat Long
@longnn

Mình vừa ngồi lại list ra những câu hỏi mà mình từng bị hỏi hoặc từng hỏi ứng viên trong các buổi phỏng vấn senior frontend. Nói thật, nhiều câu nghe quen nhưng để trả lời cho "đã" thì không dễ. Hôm nay mình chia sẻ lại toàn bộ, kèm tips trả lời theo cách mà mình nghĩ sẽ ghi điểm.
React — Không chỉ biết dùng, phải hiểu bên trong
Reconciliation Algorithm và React Fiber
Câu hỏi: Thuật toán reconciliation của React hoạt động thế nào, và React Fiber tối ưu gì?
Tips trả lời: Đừng chỉ nói "so sánh Virtual DOM". Hãy giải thích rằng React dùng thuật toán diffing với độ phức tạp O(n) bằng 2 giả định: elements khác type → rebuild cả subtree, và key prop giúp xác định element nào stable. React Fiber là kiến trúc viết lại reconciler, cho phép chia render work thành các unit nhỏ (fiber nodes), có thể pause/resume/abort. Điều này mở đường cho concurrent rendering. Theo kinh nghiệm của mình, hiểu Fiber giúp bạn debug performance issues tốt hơn rất nhiều bạn sẽ biết tại sao một component re-render không cần thiết.

Concurrent Rendering, useTransition và useDeferredValue
Câu hỏi: React xử lý concurrent rendering ra sao?
Tips trả lời: Concurrent rendering cho phép React chuẩn bị nhiều version của UI cùng lúc mà không block main thread. useTransition dùng khi bạn muốn đánh dấu một state update là "không urgent" ví dụ chuyển tab mà tab mới cần load data nặng. useDeferredValue thì dùng khi bạn muốn giữ giá trị cũ hiển thị trong khi giá trị mới đang được tính toán rất hữu ích cho search/filter với dataset lớn. Mẹo: nêu ví dụ thực tế bạn đã dùng, đừng chỉ đọc docs.
React Server Components vs Client Components
Tips trả lời: RSC chạy trên server, output là serialized React tree (không phải HTML), không ship JavaScript xuống client. Client Components là cách render truyền thống chạy trên browser, có state, có event handlers. Điều mình thấy hay là RSC giảm bundle size đáng kể vì các dependency nặng (markdown parser, date library) chỉ sống trên server. Nhưng nhớ nói rõ: RSC không thay thế SSR chúng bổ sung cho nhau.
State Management: Redux vs Zustand vs Recoil
Tips trả lời:
Redux | Zustand | Recoil
Ưu: Ecosystem lớn, DevTools mạnh, predictable, API đơn giản, nhẹ (~1KB), không cần Provider, Atom-based, tích hợp tốt với React concurrent
Nhược: Boilerplate nhiều (dù RTK đã giảm), learning curve, Community nhỏ hơn, ít middleware, Vẫn experimental, Facebook có vẻ bỏ rơi
Theo kinh nghiệm của mình, Zustand là lựa chọn mặc định cho hầu hết project mới. Redux chỉ khi team lớn, cần strict patterns. Recoil thì mình đã ngừng recommend.
Memoization — Khi nào gây phản tác dụng?
Câu hỏi: useMemo, useCallback, React.memo hoạt động thế nào?
Tips trả lời: Mỗi lần dùng memo, bạn đang trade memory lấy speed. React phải lưu giá trị trước đó VÀ so sánh dependencies mỗi render. Nếu component render nhanh sẵn rồi, memo chỉ thêm overhead. React.memo gây phản tác dụng khi props là object/array mới mỗi render (vì shallow compare luôn fail). Mình hay nói với team: "Profile trước, memo sau. Đừng memo phòng thủ."

TypeScript — Vượt qua mức "gõ type cho hết lỗi đỏ"
Dynamic Form với strict typing
Tips trả lời: Dùng generics kết hợp discriminated unions. Ví dụ:
1type FieldConfig<T extends string> = {2 name: T;3 type: 'text' | 'select' | 'checkbox';4 validation?: (value: unknown) => string | null;5};67type FormValues<T extends readonly FieldConfig<string>[]> = {8 [K in T[number]['name']]: string;9};
Điểm mấu chốt: dùng as const cho field config array để TypeScript infer được literal types.
Generics vs Mapped Types vs Conditional Types
Tips trả lời: Generics là biến cho types. Mapped types là loop qua keys để tạo type mới (Partial<T>, Record<K, V>). Conditional types là if-else cho types (T extends string ? A : B). Chúng mạnh nhất khi kết hợp: ví dụ Pick<T, K> vừa dùng generics vừa dùng mapped types. Nêu ví dụ thực tế bạn đã viết utility type cho project.
JavaScript — Engine level
Event Loop với Promises
Tips trả lời: Call stack chạy synchronous code. Khi gặp Promise resolved, callback vào microtask queue. setTimeout callback vào macrotask queue. Microtasks LUÔN chạy hết trước khi event loop lấy macrotask tiếp theo. Async functions là syntax sugar — mỗi await tạo một microtask boundary. Mẹo phỏng vấn: vẽ ra execution order của một đoạn code mix setTimeout + Promise + async/await.
WeakMap, WeakSet và Garbage Collection
Tips trả lời: WeakMap giữ weak reference đến key nếu không còn reference nào khác đến key object, GC sẽ dọn cả entry. Use case thực tế: cache data theo DOM element, hoặc store private data cho class instances. React dùng concept tương tự trong Fiber khi component unmount, các fiber nodes liên quan sẽ được GC thu hồi.

HTML/CSS — Hiểu browser rendering pipeline
Từ HTML đến pixels
Tips trả lời: HTML → DOM Tree, CSS → CSSOM → kết hợp thành Render Tree → Layout (tính toán vị trí, kích thước) → Paint (vẽ pixels) → Composite (ghép layers). Reflow là khi layout thay đổi (đắt nhất). Repaint là khi chỉ visual thay đổi (màu sắc, shadow). Composite chỉ xảy ra với transform, opacity rẻ nhất vì chạy trên GPU. Mình luôn khuyên: animate bằng transform thay vì top/left.
CSS-in-JS vs Traditional Stylesheets
Tips trả lời: CSS-in-JS (styled-components, Emotion) có runtime cost generate class names mỗi render. Với app lớn, mình đã chuyển sang zero-runtime solutions như Vanilla Extract hoặc Tailwind CSS. Lý do: bundle size nhỏ hơn, không runtime overhead, vẫn có type-safety. Nếu dùng CSS Modules thì cũng ổn scoped by default, zero runtime.
Quy trình và tư duy engineering
Feature lifecycle
Tips trả lời: Mình follow flow: Design review → Technical spec (viết RFC nếu feature lớn) → Break down tasks → Implement với feature flag → Code review → QA → Gradual rollout. Điều quan trọng nhất mà nhiều người bỏ qua: viết technical spec TRƯỚC khi code. Nó buộc bạn nghĩ về edge cases, API design, và migration path.
Testing strategy vượt xa unit tests
Tips trả lời: Testing trophy (không phải pyramid): nhiều integration tests (React Testing Library), ít unit tests cho pure logic, vài E2E tests cho critical paths (Playwright), và visual regression tests (Chromatic). Mình thấy integration tests cho ROI cao nhất test behavior thay vì implementation details.

Performance budgets
Tips trả lời: Set budget trong CI/CD: bundle size (Bundlewatch), Lighthouse scores (Lighthouse CI), Core Web Vitals thresholds. Mỗi PR vượt budget → fail build. Mình cũng dùng import cost extension trong VS Code để team aware về dependency size ngay khi code. Quan trọng nhất: performance budget phải là team agreement, không phải rule từ trên xuống.
Lời khuyên cuối
Điều mình học được sau nhiều năm phỏng vấn cả hai phía: người ta không tìm người biết hết, mà tìm người biết sâu và biết cách suy luận. Khi gặp câu không biết, hãy nói rõ phần bạn biết, phần bạn đoán, và cách bạn sẽ tìm hiểu thêm. Đó mới là tư duy senior.
Mấy tips chung:
- Luôn nêu ví dụ thực tế từ project bạn đã làm
- Trade-offs mọi quyết định kỹ thuật đều có đánh đổi, hãy nói cả hai mặt
- Đừng học thuộc hiểu mental model rồi suy ra câu trả lời
- Hỏi ngược "Trong context của team anh/chị, mình sẽ approach khác..." cho thấy bạn nghĩ thực tế
Hy vọng bài này giúp bạn chuẩn bị tốt hơn. Nếu có câu nào bạn muốn mình đào sâu hơn, cứ comment nhé.
Nguyen Nhat Long
@longnnThấy hay? Chia sẻ cho bạn bè!
Bài viết liên quan
Có thể bạn cũng thích

Zero-Downtime Deployment: Deploy mà user không biết
Làm sao để deploy bản mới lên production mà không ai nhận ra? Cùng tìm hiểu các chiến lược Blue-Green, Rolling, Canary và cách áp dụng thực tế.
Tolaria Quản lý knowledge base bằng Markdown như dân chuyên nghiệp
Tolaria là desktop app open source giúp quản lý knowledge base bằng markdown files, git-first, offline-first. Đây có thể là thứ bạn đang thiếu cho second brain.
Floci: Giã từ LocalStack, chào đón AWS emulator miễn phí thực sự
LocalStack Community đã sunset. Floci là alternative mới — mã nguồn mở, không cần auth token, startup 24ms. Đây là lý do bạn nên thử ngay.