Quay lại
Career✦ Nổi bật
7 phút đọc31 tháng 3, 202612

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.

N

Nguyen Nhat Long

@longnn

So sánh khi nào nên và không nên dùng React memoization

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.

Minh họa cách React Fiber hoạt động

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ủ."

So sánh khi nào nên và không nên dùng React memoization

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ụ:

TypeScript
1type FieldConfig<T extends string> = {
2 name: T;
3 type: 'text' | 'select' | 'checkbox';
4 validation?: (value: unknown) => string | null;
5};
6
7type 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.

Minh họa cách WeakMap và Garbage Collection hoạt động

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.

Mô hình Testing Trophy cho frontend

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é.

NN

Nguyen Nhat Long

@longnn

Thấy hay? Chia sẻ cho bạn bè!

Bài viết liên quan

Có thể bạn cũng thích

Xem tất cả