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 (Java, JavaScript & TypeScript, 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.
01Dependency Injection trong Spring là gì? Vì sao nên dùng constructor injection thay cho
@Autowiredtrên field?JavaGợi ý trả lời
Ôn thêm câu hỏi Java →IoC container của Spring tạo và quản lý các bean, rồi "tiêm" phụ thuộc vào class cần dùng, thay vì class tự
new. Nhờ vậy code ít phụ thuộc cứng, dễ thay implementation và dễ test.Constructor injection được khuyên dùng vì:
- Field có thể là
final, object luôn ở trạng thái đầy đủ sau khi tạo. - Test unit không cần Spring: chỉ cần
new OrderService(mockRepo). - Constructor quá nhiều tham số là dấu hiệu class đang ôm nhiều việc.
- Class chỉ có một constructor thì không cần ghi
@Autowired.
Nên nói thêm về scope: mặc định bean là
singleton(một instance cho cả context), ngoài ra cóprototype,request,session.Hay sai: để state thay đổi được (ví dụ biến đếm, list) trong bean singleton. Nhiều request chạy song song sẽ dùng chung và gây race condition.
- Field có thể là
02
@Transactionalhoạt động thế nào? Vì sao có lúc gắn annotation rồi mà transaction không chạy?JavaGợi ý trả lời
Ôn thêm câu hỏi Java →Spring tạo proxy bao quanh bean. Khi gọi method có
@Transactionalqua proxy, proxy mở transaction, gọi method thật, rồi commit hoặc rollback.Những lý do hay gặp khiến nó "không ăn":
- Self-invocation: method A trong cùng class gọi
this.b()có@Transactional, lời gọi không đi qua proxy. - Method là
privatehoặcfinal: proxy không chặn được lời gọi. - Mặc định chỉ rollback với unchecked exception và
Error. Checked exception vẫn commit, trừ khi khai báorollbackFor = Exception.class. - Exception bị
catchvà nuốt bên trong method.
Nên biết thêm
propagation(REQUIRED,REQUIRES_NEW) vàreadOnly = truecho các truy vấn chỉ đọc.Người phỏng vấn muốn nghe: bạn giải thích được bằng cơ chế proxy, không chỉ liệt kê lỗi.
- Self-invocation: method A trong cùng class gọi
03Lỗi N+1 query trong JPA/Hibernate là gì và xử lý ra sao?Java
Gợi ý trả lời
Ôn thêm câu hỏi Java →N+1 xảy ra khi bạn lấy N bản ghi bằng 1 query, rồi mỗi lần truy cập quan hệ lazy lại phát sinh thêm 1 query. Ví dụ lấy 100 đơn hàng, rồi gọi
order.getCustomer()trong vòng lặp là thành 101 query.Cách xử lý:
JOIN FETCHtrong JPQL:select o from Order o join fetch o.customer.@EntityGraph(attributePaths = "customer")trên method của repository.@BatchSizehoặchibernate.default_batch_fetch_sizeđể gom lazy load thành câuIN (...).- Với màn hình chỉ đọc, trả thẳng DTO projection thay vì entity.
Phát hiện bằng cách bật log SQL (
spring.jpa.show-sqlhoặc log của Hibernate) khi dev, hoặc dùng thống kê của Hibernate.Hay sai: đổi sang
FetchType.EAGERcho xong. Cách này chỉ đổi chỗ lỗi và làm mọi truy vấn khác nặng hơn. Fetch join nhiều collection cùng lúc cũng dễ gây tích Descartes.04So 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.05Trong 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).06Một endpoint làm tác vụ nặng CPU (xử lý ảnh, tạo báo cáo) làm cả server chậm theo. Bạn xử lý thế nào?Node.js
Gợi ý trả lời
Ôn thêm câu hỏi Node.js →Tác vụ CPU chạy trên main thread sẽ chặn event loop, nên mọi request khác phải chờ. Có vài hướng:
worker_threads: chạy tính toán ở thread khác trong cùng process. Nên dùng một pool worker (ví dụ thư viện Piscina) thay vì tạo worker mới cho mỗi request.- Hàng đợi công việc: API chỉ nhận yêu cầu, đẩy job vào queue (BullMQ trên Redis, RabbitMQ), trả về
202kèm mã job; worker riêng xử lý, client hỏi lại trạng thái hoặc nhận thông báo. - Tách service: nếu việc nặng là trọng tâm, viết bằng ngôn ngữ phù hợp hơn hoặc dùng dịch vụ chuyên dụng.
clusterhay PM2 chạy nhiều process giúp tận dụng nhiều core, nhưng không giải quyết việc một request nặng chặn process của nó.Người phỏng vấn muốn nghe: bạn đo được độ trễ event loop (
perf_hooks.monitorEventLoopDelay) để phát hiện vấn đề này.07Bộ nhớ JVM gồm những vùng nào? Garbage Collector hoạt động ra sao và Java có bị memory leak không?Java
Gợi ý trả lời
Ôn thêm câu hỏi Java →Hai vùng chính: stack (mỗi thread một stack, chứa biến cục bộ và tham chiếu) và heap (chứa object, dùng chung). Ngoài ra có Metaspace chứa thông tin class.
- GC tìm các object không còn được tham chiếu từ "GC roots" (biến trên stack, field static...) và thu hồi.
- Heap chia theo thế hệ: object mới nằm ở young generation, sống lâu thì được chuyển lên old generation.
- G1 là GC mặc định; ZGC hợp khi cần pause rất ngắn với heap lớn.
Java vẫn leak khi object không dùng nữa mà vẫn còn tham chiếu:
- Cache hoặc
static Mapchỉ thêm, không bao giờ xoá. ThreadLocalkhôngremove()trong thread pool.- Listener đăng ký mà không huỷ.
Người phỏng vấn muốn nghe: phân biệt được
OutOfMemoryError: Java heap spacevới lỗi Metaspace, và biết dùng heap dump để tìm ai đang giữ object.08Virtual threads trong Java 21 là gì? Khi nào nên dùng và cần lưu ý gì?Java · Senior
Gợi ý trả lời
Ôn thêm câu hỏi Java →Virtual thread là thread nhẹ do JVM quản lý, được gắn lên một số ít platform thread (carrier). Khi gặp I/O blocking, nó nhả carrier để chạy việc khác, nên bạn có thể có hàng trăm nghìn thread mà vẫn viết code blocking dễ đọc.
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { ids.forEach(id -> executor.submit(() -> callApi(id))); }- Hợp với tải nhiều I/O (gọi DB, HTTP). Không giúp gì với tác vụ nặng CPU.
- Spring Boot 3.2+ bật bằng
spring.threads.virtual.enabled=true. - Không đưa virtual thread vào pool: tạo mới cho mỗi task là cách dùng đúng.
- Giới hạn tài nguyên (connection pool DB) vẫn là giới hạn thật; dùng
Semaphorenếu cần. - Trên Java 21, blocking bên trong
synchronizedcó thể "pin" carrier; các bản JDK mới hơn đã cải thiện. Cẩn thận vớiThreadLocalchứa object lớn.
Người phỏng vấn muốn nghe: virtual threads tăng throughput, không làm từng request nhanh hơn.
09So sánh
synchronized,volatile,ReentrantLockvàConcurrentHashMap. Bạn tránh race condition thế nào?Java · SeniorGợi ý trả lời
Ôn thêm câu hỏi Java →Race condition xảy ra khi nhiều thread đọc-sửa-ghi cùng dữ liệu mà không đồng bộ. Mỗi công cụ giải quyết một phần:
volatile: đảm bảo visibility (thread khác thấy giá trị mới nhất), nhưng không làmcount++thành atomic.synchronized: vừa loại trừ lẫn nhau vừa đảm bảo visibility (quan hệ happens-before).ReentrantLock: nhưsynchronizednhưng cótryLockvới timeout, lock có thể ngắt, nhiềuCondition.AtomicInteger/LongAdder: đếm không cần lock.ConcurrentHashMap: thread-safe với độ tranh chấp thấp. Dùngcompute/merge/putIfAbsentcho thao tác kép, đừnggetrồiput.
Hay sai: dùng
Collections.synchronizedMaprồi duyệt map mà không lock, hoặc kiểm traif (!map.containsKey(k)) map.put(k, v)trên map đồng thời: hai bước riêng lẻ vẫn bị chen ngang. Cách tốt nhất vẫn là giảm state dùng chung (object bất biến, mỗi thread một bản).10Bạn phản ứng thế nào khi nhận góp ý tiêu cực về công việc của mình?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á →Cho thấy bạn tách góp ý khỏi cảm xúc cá nhân và biến nó thành hành động.
- Lắng nghe hết, hỏi lại để hiểu ví dụ cụ thể và kỳ vọng.
- Cảm ơn, kể cả khi chưa đồng ý hoàn toàn; nếu có góc nhìn khác, trao đổi bằng thông tin, không phòng thủ.
- Lên kế hoạch cải thiện và chủ động xin phản hồi lại sau một thời gian.
Câu mẫu: "Có lần lead góp ý code review của em quá dài, khó review. Lúc đầu em hơi chột dạ, nhưng em hỏi anh muốn chia thế nào cho dễ. Từ đó em tách pull request nhỏ theo từng thay đổi, thời gian review của team giảm hẳn và em cũng ít bị yêu cầu sửa lại hơn."
Hay sai: nói "em luôn vui vẻ đón nhận mọi góp ý" mà không có ví dụ.
Bạn đã phỏng vấn ở TON Corporation?
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