10 câu hỏi có thể gặp khi phỏng vấn
Chọn theo kỹ năng và cấp của tin (JavaScript & TypeScript, ReactJS, Node.js). Gợi ý trả lời do Drama Công Ty biên soạn để bạn ôn nhanh, không phải đề của công ty.
01So sánh
Promise.all,Promise.allSettled,Promise.racevàPromise.any. Xử lý lỗi với async/await thế nào?JavaScript & TypeScriptGợi ý trả lời
Ôn thêm câu hỏi JavaScript & TypeScript →Cả bốn đều chạy nhiều Promise song song, khác nhau ở cách kết thúc:
all: chờ tất cả thành công; một cái lỗi là reject ngay.allSettled: chờ tất cả xong, trả về trạng thái từng cái; không bao giờ reject.race: lấy kết quả (thành công hay lỗi) của cái xong đầu tiên; hay dùng để đặt timeout.any: lấy cái thành công đầu tiên; chỉ reject khi tất cả đều lỗi.
try { const [user, orders] = await Promise.all([getUser(id), getOrders(id)]); } catch (err) { showError(err); }Hay sai:
awaitlần lượt trong vòng lặp các request không phụ thuộc nhau, khiến tổng thời gian cộng dồn. Ngược lại, gọiPromise.allvới hàng nghìn request cùng lúc có thể làm quá tải server; khi đó cần giới hạn số request đồng thời.02Trong TypeScript,
interfacevàtypekhác nhau thế nào? Phân biệtany,unknownvànever.JavaScript & TypeScriptGợi ý trả lời
Ôn thêm câu hỏi JavaScript & TypeScript →Cả hai đều mô tả hình dạng object và phần lớn trường hợp dùng thay nhau được.
interfacecó thể gộp khai báo (declaration merging) vàextends; hợp để mô tả object, API công khai.typemô tả được union, tuple, kiểu ánh xạ, kiểu điều kiện:type Status = "idle" | "loading".
Ba kiểu đặc biệt:
any: tắt kiểm tra kiểu, lỗi lọt qua compiler.unknown: nhận mọi giá trị nhưng bắt buộc thu hẹp kiểu trước khi dùng. Hợp cho dữ liệu từJSON.parse, API,catch (e).never: giá trị không bao giờ xảy ra; dùng để bắt thiếu case trongswitch.
function assertNever(x: never): never { throw new Error("Unhandled: " + x); }Hay sai: dùng
as SomeTypeđể ép dữ liệu từ API. Đó chỉ là lời hứa với compiler; muốn chắc chắn phải validate lúc runtime (ví dụ bằng Zod).03Debounce và throttle khác nhau thế nào? Mỗi cái dùng trong trường hợp nào?JavaScript & TypeScript
Gợi ý trả lời
Ôn thêm câu hỏi JavaScript & TypeScript →Cả hai giới hạn số lần một hàm chạy khi sự kiện bắn liên tục.
- Debounce: chỉ chạy sau khi sự kiện ngừng một khoảng thời gian. Hợp với ô tìm kiếm gợi ý (gõ xong mới gọi API), validate form, resize xong mới tính lại layout.
- Throttle: chạy tối đa một lần mỗi khoảng thời gian. Hợp với scroll, kéo thả, theo dõi vị trí chuột.
function debounce(fn, ms) { let timer; return (...args) => { clearTimeout(timer); timer = setTimeout(() => fn(...args), ms); }; }Hay sai: tạo hàm debounce mới mỗi lần render (trong React), khiến timer bị reset liên tục và debounce không có tác dụng. Với ô tìm kiếm, nhớ huỷ request cũ (
AbortController) để kết quả cũ không đè kết quả mới.04Bạn chọn cách quản lý state thế nào: Context, Redux Toolkit, Zustand hay TanStack Query?ReactJS
Gợi ý trả lời
Ôn thêm câu hỏi ReactJS →Trước hết phân loại state:
- Server state (dữ liệu từ API): dùng TanStack Query hoặc SWR. Chúng lo cache, loading, refetch, đồng bộ. Phần lớn "global state" trong app thực ra thuộc loại này.
- UI state cục bộ:
useState/useReducerngay trong component. - State trên URL (bộ lọc, trang): để trên query string, chia sẻ link được.
- Client state toàn cục thật sự (giỏ hàng, theme, user đang đăng nhập): Context nếu ít thay đổi; Zustand hoặc Redux Toolkit nếu thay đổi thường xuyên hoặc logic phức tạp.
Redux Toolkit hợp khi team lớn cần quy ước chặt và DevTools mạnh. Zustand gọn nhẹ hơn.
Hay sai: gom state hay thay đổi vào một Context lớn. Mọi component dùng Context đó render lại mỗi lần bất kỳ giá trị nào đổi. Hãy tách Context, hoặc dùng thư viện có selector.
05Stale closure trong React là gì? Vì sao
setIntervaltronguseEffectcứ in ra giá trị cũ?ReactJSGợi ý trả lời
Ôn thêm câu hỏi ReactJS →Mỗi lần render tạo ra hàm mới, "chụp" lại props và state của lần render đó. Nếu effect chỉ chạy một lần, callback bên trong mãi thấy giá trị lúc mount.
useEffect(() => { const id = setInterval(() => { setCount(count + 1); // count luôn là 0 }, 1000); return () => clearInterval(id); }, []);Cách sửa:
- Dùng functional update:
setCount(c => c + 1), không cần đọccount. - Khai báo đúng dependency (effect chạy lại khi giá trị đổi, nhớ cleanup).
- Với giá trị chỉ cần đọc mới nhất mà không muốn chạy lại effect: lưu vào
useRef.
Hay sai: tắt cảnh báo
exhaustive-depscủa ESLint cho xong. Cảnh báo đó thường chính là dấu hiệu stale closure.- Dùng functional update:
06Trong NestJS, module, provider và dependency injection hoạt động ra sao? Guard, interceptor, pipe khác nhau thế nào?Node.js
Gợi ý trả lời
Ôn thêm câu hỏi Node.js →NestJS tổ chức app thành module. Mỗi module khai báo controller, provider (service, repository) và
exportsnhững gì module khác được dùng. IoC container của Nest tự tạo provider và inject qua constructor.@Injectable() export class OrdersService { constructor(private readonly repo: OrdersRepository) {} }Thứ tự một request đi qua: middleware, guard, interceptor (trước), pipe, handler, interceptor (sau); exception filter bắt lỗi ở bất kỳ bước nào.
- Guard: quyết định request có được đi tiếp không (xác thực, phân quyền).
- Pipe: biến đổi và validate input (
ValidationPipevới DTO). - Interceptor: bọc quanh handler: log, đo thời gian, đổi định dạng response, cache.
Hay sai: hai provider phụ thuộc vòng tròn rồi xử lý bằng
forwardRefkhắp nơi. Thường đó là dấu hiệu nên tách lại trách nhiệm giữa các module.07Dùng generics và type narrowing trong TypeScript thế nào? Cho ví dụ discriminated union.JavaScript & TypeScript · Senior
Gợi ý trả lời
Ôn thêm câu hỏi JavaScript & TypeScript →Generics giúp viết hàm và kiểu dùng lại được mà vẫn giữ thông tin kiểu, thay vì rơi về
any.type Result<T> = | { ok: true; data: T } | { ok: false; error: string }; function show(r: Result<User>) { if (r.ok) return r.data.name; // TS biết có data return r.error; }- Field chung (
ok,type,status) giúp TypeScript thu hẹp kiểu trongif/switch. Đây là cách mô hình hoá trạng thái không thể sai: không có chuyện vừa códatavừa cóerror. - Ràng buộc generic:
<T extends { id: string }>. - Utility types hay dùng:
Partial,Pick,Omit,Record,ReturnType,Awaited. - Type guard tự viết:
function isUser(x: unknown): x is User.
Người phỏng vấn muốn nghe: bạn dùng kiểu để làm trạng thái sai không biểu diễn được, chứ không chỉ để chú thích.
- Field chung (
08Một trang web dùng càng lâu càng chậm và tốn RAM. Bạn tìm memory leak ở frontend thế nào?JavaScript & TypeScript · Senior
Gợi ý trả lời
Ôn thêm câu hỏi JavaScript & TypeScript →Memory leak ở frontend thường do thứ gì đó vẫn giữ tham chiếu đến object đáng lẽ đã bỏ đi, nhất là trong SPA vì trang không bao giờ tải lại.
Nguyên nhân hay gặp:
addEventListenertrênwindow/documentmà không gỡ khi component bị huỷ.setInterval, subscription (WebSocket, store) không huỷ.- DOM đã bị gỡ khỏi trang nhưng vẫn được biến JS giữ lại (detached DOM).
- Cache hoặc mảng toàn cục chỉ tăng không giảm.
Cách điều tra với Chrome DevTools:
- Tab Memory: chụp heap snapshot, thực hiện thao tác vài lần, chụp lại, so sánh phần tăng thêm.
- Lọc "Detached" để tìm DOM bị giữ, xem chuỗi retainers để biết ai đang giữ.
- Tab Performance bật Memory để xem heap có giảm sau GC không.
Người phỏng vấn muốn nghe: mỗi tài nguyên đăng ký đều có chỗ huỷ tương ứng (cleanup của effect,
ngOnDestroy,onUnmounted,AbortController).09Tell me about a time you disagreed with a teammate or your manager.Phỏng vấn tiếng Anh
Gợi ý trả lời
Ôn thêm câu hỏi Phỏng vấn tiếng Anh →"My tech lead wanted to release a feature without load testing because of a tight deadline. I was worried because the feature would run during peak hours. I explained my concern and suggested a quick test on just the critical API, which took half a day. The test found a memory issue that we fixed before release. My lead appreciated it, and load testing became part of our release checklist."
Mẹo: giọng điệu nên tôn trọng: "I explained my concern", "I suggested", tránh "I told him he was wrong". Câu chuyện cần có kết quả tích cực cho team, không phải bạn thắng tranh luận.
10Bạn thấy mình ở đâu sau 2–3 năm nữa?Câu hỏi hành vi & văn hoá
Gợi ý trả lời
Ôn thêm câu hỏi Câu hỏi hành vi & văn hoá →Người phỏng vấn muốn biết mục tiêu của bạn có khớp với hướng phát triển ở công ty và bạn có định gắn bó không.
- Nêu hướng phát triển cụ thể, thực tế: đi sâu chuyên môn (senior, chuyên gia một mảng) hay quản lý (lead, PM).
- Kèm kế hoạch: kỹ năng cần học, trách nhiệm muốn nhận thêm.
- Gắn với cơ hội ở vị trí đang ứng tuyển.
Câu mẫu: "Trong 2–3 năm tới em muốn trở thành senior có thể tự thiết kế và chịu trách nhiệm một module lớn, đồng thời hướng dẫn được các bạn mới. Em đang học thêm về thiết kế hệ thống và thấy team bên mình có nhiều bài toán ở quy mô lớn, rất hợp để em phát triển theo hướng đó."
Hay sai: "Em muốn mở công ty riêng" hoặc "em chưa nghĩ tới". Cũng đừng nói muốn ngồi vào chỗ của người đang phỏng vấn.
Bạn đã phỏng vấn ở Gradion?
Kể lại các vòng và câu hỏi đã gặp (ẩn danh) để người sau ôn đúng trọng tâm.
Chia sẻ kinh nghiệm phỏng vấn