Việc làm

22/09 · Hà Nội · Tại văn phòng

Backend Developer (Java/ Golang/ NodeJS)

Digital Innovation · Backend Developer

Xem mô tả & ứng tuyển trên ITviecMô tả công việc, lương và ứng tuyển nằm ở ITviec.

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, Node.js, Golang). 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.

  1. 01Lỗi N+1 query trong JPA/Hibernate là gì và xử lý ra sao?Java

    Gợi ý trả lời

    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 FETCH trong JPQL: select o from Order o join fetch o.customer.
    • @EntityGraph(attributePaths = "customer") trên method của repository.
    • @BatchSize hoặc hibernate.default_batch_fetch_size để gom lazy load thành câu IN (...).
    • 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-sql hoặc log của Hibernate) khi dev, hoặc dùng thống kê của Hibernate.

    Hay sai: đổi sang FetchType.EAGER cho 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.

    Ôn thêm câu hỏi Java →
  2. 02Bộ 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

    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 Map chỉ thêm, không bao giờ xoá.
    • ThreadLocal không remove() 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 space với lỗi Metaspace, và biết dùng heap dump để tìm ai đang giữ object.

    Ôn thêm câu hỏi Java →
  3. 03Virtual 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

    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 Semaphore nếu cần.
    • Trên Java 21, blocking bên trong synchronized có thể "pin" carrier; các bản JDK mới hơn đã cải thiện. Cẩn thận với ThreadLocal chứ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.

    Ôn thêm câu hỏi Java →
  4. 04Trong 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

    NestJS tổ chức app thành module. Mỗi module khai báo controller, provider (service, repository) và exports nhữ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 (ValidationPipe vớ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 forwardRef khắ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.

    Ôn thêm câu hỏi Node.js →
  5. 05Process Node trên production tăng RAM liên tục rồi bị kill. Bạn tìm memory leak thế nào?Node.js · Senior

    Gợi ý trả lời

    Trước hết xác nhận có leak thật: theo dõi heap qua thời gian (process.memoryUsage(), metrics của APM). Heap tăng rồi giảm sau GC là bình thường; chỉ tăng không giảm mới là leak.

    Nguyên nhân hay gặp:

    • Cache trong bộ nhớ (Map, object) không giới hạn kích thước hoặc thời hạn.
    • Event listener đăng ký mỗi request mà không gỡ (thường kèm cảnh báo MaxListenersExceededWarning).
    • Closure giữ object lớn; timer không clearInterval.
    • Mảng toàn cục lưu dữ liệu request để debug rồi quên xoá.

    Cách điều tra:

    1. Chạy với --inspect, mở Chrome DevTools, chụp heap snapshot trước và sau khi bắn tải, so sánh object tăng thêm và chuỗi retainers.
    2. Dùng --heapsnapshot-near-heap-limit để tự chụp snapshot khi sắp hết bộ nhớ.

    Người phỏng vấn muốn nghe: cache thì dùng LRU có giới hạn hoặc Redis, và biết rằng tăng --max-old-space-size chỉ làm lỗi đến chậm hơn.

    Ôn thêm câu hỏi Node.js →
  6. 06Goroutine leak là gì? Làm sao phát hiện và phòng tránh?Golang · Senior

    Gợi ý trả lời

    Goroutine leak là goroutine bị chặn mãi mãi và không bao giờ kết thúc, giữ theo bộ nhớ và tài nguyên nó tham chiếu. Số goroutine tăng dần theo thời gian là dấu hiệu rõ nhất.

    Nguyên nhân hay gặp:

    • Gửi vào channel mà bên nhận đã bỏ đi (ví dụ đã trả về vì timeout).
    • Nhận từ channel không bao giờ được gửi hoặc đóng.
    • Vòng lặp chạy nền không kiểm tra ctx.Done().
    • HTTP response không đóng Body.
    ch := make(chan Result, 1) // buffer 1: goroutine gửi xong vẫn thoát được
    go func() { ch <- slowCall() }()
    select {
    case r := <-ch: return r, nil
    case <-ctx.Done(): return Result{}, ctx.Err()
    }
    

    Phát hiện: theo dõi runtime.NumGoroutine(), xem profile goroutine qua net/http/pprof, dùng thư viện goleak trong test.

    Người phỏng vấn muốn nghe: mỗi goroutine bạn tạo phải có câu trả lời rõ ràng cho câu hỏi "nó kết thúc khi nào?".

    Ôn thêm câu hỏi Golang →
  7. 07So sánh synchronized, volatile, ReentrantLock và ConcurrentHashMap. Bạn tránh race condition thế nào?Java · Senior

    Gợi ý trả lời

    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àm count++ thành atomic.
    • synchronized: vừa loại trừ lẫn nhau vừa đảm bảo visibility (quan hệ happens-before).
    • ReentrantLock: như synchronized nhưng có tryLock với timeout, lock có thể ngắt, nhiều Condition.
    • AtomicInteger/LongAdder: đếm không cần lock.
    • ConcurrentHashMap: thread-safe với độ tranh chấp thấp. Dùng compute/merge/putIfAbsent cho thao tác kép, đừng get rồi put.

    Hay sai: dùng Collections.synchronizedMap rồi duyệt map mà không lock, hoặc kiểm tra if (!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).

    Ôn thêm câu hỏi Java →
  8. 08Một service Spring Boot trên production đột nhiên chậm, CPU cao hoặc bị OOM. Bạn điều tra thế nào?Java · Senior

    Gợi ý trả lời

    Đi từ số liệu đến bằng chứng, đừng đoán rồi sửa.

    1. Xem metrics (Actuator, Micrometer): latency, số request, heap, GC pause, connection pool đang chờ, thời gian gọi DB và API ngoài.
    2. CPU cao: lấy thread dump vài lần cách nhau vài giây (jcmd <pid> Thread.print hoặc jstack), tìm thread luôn RUNNABLE ở cùng chỗ. Có thể là vòng lặp, regex xấu, hoặc GC chạy liên tục.
    3. Chậm mà CPU thấp: thread đang chờ: hết connection DB, lock, gọi API ngoài không có timeout.
    4. OOM hoặc heap tăng dần: bật -XX:+HeapDumpOnOutOfMemoryError, mở heap dump bằng Eclipse MAT, xem ai giữ nhiều bộ nhớ nhất.
    5. Java Flight Recorder (jcmd <pid> JFR.start) cho profile chi phí thấp, dùng được trên production.

    Người phỏng vấn muốn nghe: có quy trình rõ ràng, biết cả cách giảm thiệt hại tạm thời (rollback, scale) trước khi tìm nguyên nhân gốc.

    Ôn thêm câu hỏi Java →
  9. 09Hai người cùng đặt mua sản phẩm cuối cùng trong kho. Bạn xử lý cập nhật đồng thời trong JPA thế nào?Java · Senior

    Gợi ý trả lời

    Đây là bài toán lost update. Có hai hướng chính, chọn theo mức độ tranh chấp:

    • Optimistic locking: thêm field @Version. Khi update, Hibernate kèm điều kiện where version = ?; nếu người khác đã sửa trước, bạn nhận OptimisticLockException và có thể retry hoặc báo lỗi. Hợp khi tranh chấp ít.
    • Pessimistic locking: @Lock(LockModeType.PESSIMISTIC_WRITE) sinh SELECT ... FOR UPDATE, giữ khoá đến hết transaction. Hợp khi tranh chấp nhiều, đổi lại throughput giảm và có nguy cơ deadlock.
    • Đơn giản nhất cho tồn kho: update có điều kiện ngay trong DB:
    UPDATE product SET stock = stock - 1
    WHERE id = ? AND stock > 0;
    

    Kiểm tra số dòng bị ảnh hưởng: bằng 0 nghĩa là hết hàng.

    Hay sai: dùng synchronized trong code Java. Nó vô dụng khi chạy nhiều instance, và cũng không bảo vệ được dữ liệu ở DB.

    Ôn thêm câu hỏi Java →
  10. 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

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

    Ôn thêm câu hỏi Câu hỏi hành vi & văn hoá →

Bạn đã phỏng vấn ở Digital Innovation?

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