Việc làm

2 ngày trước · Hồ Chí Minh · Tại văn phòng

Expert Backend Engineer (Golang, Microservices)

DatVietVAC Group Holdings · 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 (SQL & Database, Golang, System Design & Kiến trúc). 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. 01Phân biệt INNER JOIN, LEFT JOIN, RIGHT JOIN và FULL OUTER JOIN. Khi nào dùng LEFT JOIN?SQL & Database · Junior

    Gợi ý trả lời

    JOIN quyết định giữ lại những dòng nào khi ghép hai bảng theo điều kiện.

    • INNER JOIN: chỉ giữ dòng khớp ở cả hai bảng.
    • LEFT JOIN: giữ mọi dòng bảng trái; bảng phải không khớp thì cột thành NULL.
    • RIGHT JOIN: ngược lại với LEFT, ít dùng vì có thể đổi chỗ bảng.
    • FULL OUTER JOIN: giữ tất cả dòng của cả hai (MySQL không hỗ trợ trực tiếp, phải UNION hai LEFT JOIN).

    Ví dụ tìm khách chưa từng đặt hàng:

    SELECT c.id, c.name
    FROM customers c
    LEFT JOIN orders o ON o.customer_id = c.id
    WHERE o.id IS NULL;
    

    Hay sai: đặt điều kiện lọc bảng phải trong WHERE (vd. WHERE o.status = 'paid') làm LEFT JOIN biến thành INNER JOIN; điều kiện đó phải để trong ON.

    Ôn thêm câu hỏi SQL & Database →
  2. 02WHERE và HAVING khác nhau thế nào? Thứ tự thực thi logic của một câu SELECT ra sao?SQL & Database · Junior

    Gợi ý trả lời

    WHERE lọc từng dòng trước khi gom nhóm; HAVING lọc sau GROUP BY, nên dùng được hàm tổng hợp như COUNT, SUM.

    Thứ tự logic: FROM/JOIN → WHERE → GROUP BY → HAVING → SELECT → DISTINCT → ORDER BY → LIMIT/OFFSET.

    SELECT department_id, AVG(salary) AS avg_salary
    FROM employees
    WHERE status = 'active'
    GROUP BY department_id
    HAVING AVG(salary) > 20000000;
    
    • Vì SELECT chạy sau WHERE, bạn không dùng được alias của SELECT trong WHERE (đa số DB).
    • Điều kiện không cần hàm tổng hợp thì nên để ở WHERE để giảm số dòng phải gom nhóm.

    Người phỏng vấn muốn nghe: bạn hiểu thứ tự logic, từ đó giải thích được vì sao alias hoặc aggregate dùng sai chỗ bị lỗi.

    Ôn thêm câu hỏi SQL & Database →
  3. 03Index là gì, hoạt động thế nào? Có nên đánh index cho mọi cột không?SQL & Database · Junior

    Gợi ý trả lời

    Index là cấu trúc dữ liệu phụ (thường là B-Tree) giúp tìm dòng theo giá trị cột mà không phải quét cả bảng, đổi lại tốn dung lượng và làm chậm thao tác ghi.

    • B-Tree hợp với so sánh =, <, >, BETWEEN, ORDER BY, LIKE 'abc%'.
    • Mỗi INSERT/UPDATE/DELETE phải cập nhật thêm mọi index liên quan.
    • Cột có độ chọn lọc thấp (vd. gender, is_deleted) đánh index riêng thường ít tác dụng.

    Không nên đánh index tràn lan: chỉ thêm cho cột hay xuất hiện trong WHERE, JOIN, ORDER BY của các query quan trọng, rồi kiểm tra bằng EXPLAIN.

    Hay sai: bọc cột trong hàm, vd. WHERE YEAR(created_at) = 2026 hoặc LOWER(email) = ..., khiến index thường trên cột đó không được dùng. Hãy viết lại thành khoảng thời gian hoặc dùng index trên biểu thức.

    Ôn thêm câu hỏi SQL & Database →
  4. 04Array và slice khác nhau thế nào? Vì sao append đôi khi làm thay đổi cả slice khác?Golang · Junior

    Gợi ý trả lời

    Array có độ dài cố định và là giá trị (gán là copy toàn bộ). Slice là một "khung nhìn" gồm con trỏ đến mảng bên dưới, len và cap.

    • Gán hoặc cắt slice (b := a[1:3]) không copy dữ liệu; hai slice dùng chung mảng bên dưới.
    • append khi còn cap sẽ ghi thẳng vào mảng chung, nên có thể ghi đè dữ liệu slice khác đang thấy. Khi hết cap, nó cấp phát mảng mới và từ đó hai slice tách rời.
    a := []int{1, 2, 3, 4}
    b := a[:2]
    b = append(b, 99) // a thành [1 2 99 4]
    
    • Muốn tách hẳn: slices.Clone(a) hoặc dùng full slice expression a[:2:2] để giới hạn cap.
    • Biết trước kích thước thì make([]T, 0, n) để tránh cấp phát lại nhiều lần.

    Người phỏng vấn muốn nghe: luôn dùng giá trị trả về của append (s = append(s, x)).

    Ôn thêm câu hỏi Golang →
  5. 05Go xử lý lỗi như thế nào? Khi nào dùng panic? errors.Is và errors.As để làm gì?Golang · Junior

    Gợi ý trả lời

    Lỗi trong Go là giá trị kiểu error, được trả về như kết quả thông thường và phải kiểm tra tường minh. panic chỉ dành cho tình huống không thể tiếp tục hoặc lỗi lập trình (ví dụ cấu hình bắt buộc bị thiếu lúc khởi động), không dùng cho lỗi nghiệp vụ.

    Bọc lỗi để giữ ngữ cảnh mà không mất lỗi gốc:

    if err != nil {
        return fmt.Errorf("load user %d: %w", id, err)
    }
    // Ở tầng trên:
    if errors.Is(err, sql.ErrNoRows) { /* trả 404 */ }
    
    • errors.Is: so với một lỗi cụ thể trong chuỗi bọc.
    • errors.As: lấy ra lỗi thuộc một kiểu cụ thể để đọc field của nó.
    • recover trong defer dùng ở biên (middleware HTTP) để một request panic không làm sập cả server.

    Hay sai: dùng %v thay %w khi bọc, làm errors.Is không tìm thấy lỗi gốc.

    Ôn thêm câu hỏi Golang →
  6. 06Monolith và microservices: khi nào nên tách? Cái giá phải trả là gì?System Design & Kiến trúc

    Gợi ý trả lời

    Microservices giải quyết vấn đề tổ chức: nhiều team cần deploy độc lập và scale riêng từng phần. Nó không tự làm hệ thống nhanh hơn.

    Nên cân nhắc tách khi:

    • Nhiều team đụng nhau trong cùng codebase, deploy phải chờ nhau.
    • Một phần có tải hoặc yêu cầu công nghệ rất khác phần còn lại.
    • Ranh giới nghiệp vụ (bounded context) đã rõ ràng và ổn định.

    Cái giá: gọi mạng thay vì gọi hàm (độ trễ, lỗi một phần), nhất quán dữ liệu phân tán, cần tracing, service discovery, CI/CD và vận hành phức tạp hơn.

    Người phỏng vấn muốn nghe: "modular monolith" là bước khởi đầu hợp lý: chia module rõ ràng trong một ứng dụng, sau này tách theo đúng ranh giới đó. Tách sai ranh giới tạo ra "distributed monolith", tệ hơn cả monolith.

    Ôn thêm câu hỏi System Design & Kiến trúc →
  7. 07Transaction là gì? Giải thích ACID.SQL & Database · Junior

    Gợi ý trả lời

    Transaction là một nhóm thao tác được thực hiện như một đơn vị: hoặc tất cả thành công (COMMIT), hoặc không có gì xảy ra (ROLLBACK).

    • Atomicity: không có trạng thái làm dở dang.
    • Consistency: sau transaction dữ liệu vẫn thoả ràng buộc (khoá ngoại, unique, check).
    • Isolation: các transaction chạy đồng thời không thấy dữ liệu dở dang của nhau, mức độ tuỳ isolation level.
    • Durability: đã commit thì không mất dù server sập (nhờ write-ahead log / redo log).
    BEGIN;
    UPDATE accounts SET balance = balance - 500 WHERE id = 1;
    UPDATE accounts SET balance = balance + 500 WHERE id = 2;
    COMMIT;
    

    Người phỏng vấn muốn nghe: một ví dụ thực tế như chuyển tiền hoặc trừ tồn kho, và bạn biết giữ transaction ngắn, không gọi API bên ngoài khi đang mở transaction.

    Ôn thêm câu hỏi SQL & Database →
  8. 08Composite index (index nhiều cột) hoạt động ra sao? Thứ tự cột có quan trọng không?SQL & Database

    Gợi ý trả lời

    Có. Composite index sắp xếp theo cột đầu, rồi đến cột thứ hai trong cùng giá trị cột đầu, v.v. Vì vậy nó chỉ dùng hiệu quả khi query dùng các cột từ trái sang (leftmost prefix).

    Với INDEX (status, created_at):

    • WHERE status = 'paid' AND created_at > ...: dùng tốt.
    • WHERE status = 'paid' ORDER BY created_at DESC: dùng cho cả lọc lẫn sắp xếp.
    • WHERE created_at > ... (không có status): thường không dùng được.

    Nguyên tắc chọn thứ tự: cột so sánh bằng (=) đặt trước, cột so sánh khoảng hoặc sắp xếp đặt sau. Nếu index chứa đủ mọi cột query cần (covering index) thì DB không phải đọc lại bảng.

    Hay sai: tạo riêng index cho từng cột rồi nghĩ rằng chúng sẽ kết hợp hiệu quả như một composite index.

    Ôn thêm câu hỏi SQL & Database →
  9. 09Các isolation level là gì? Dirty read, non-repeatable read, phantom read khác nhau thế nào?SQL & Database

    Gợi ý trả lời

    Isolation level quyết định một transaction thấy được bao nhiêu thay đổi của transaction khác; mức càng cao càng an toàn nhưng càng dễ tranh chấp.

    • Dirty read: đọc dữ liệu transaction khác chưa commit.
    • Non-repeatable read: đọc lại cùng một dòng thấy giá trị đã đổi.
    • Phantom read: chạy lại cùng điều kiện thấy thêm hoặc mất dòng.

    Bốn mức chuẩn: Read Uncommitted, Read Committed, Repeatable Read, Serializable. Mặc định của PostgreSQL, SQL Server, Oracle là Read Committed; MySQL InnoDB là Repeatable Read.

    Thực tế các DB dùng MVCC nên hành vi có khác lý thuyết: Repeatable Read của PostgreSQL dùng snapshot nên không gặp phantom, còn InnoDB dùng gap lock cho câu đọc có khoá.

    Người phỏng vấn muốn nghe: bạn biết DB mình dùng mặc định ở mức nào và đã từng xử lý lost update, vd. bằng SELECT ... FOR UPDATE hoặc optimistic locking.

    Ôn thêm câu hỏi SQL & Database →
  10. 10Bạn nghĩ sao về việc làm thêm giờ (OT)?Câu hỏi hành vi & văn hoá

    Gợi ý trả lời

    Trả lời thẳng thắn và cân bằng: sẵn sàng khi thật sự cần, nhưng không coi OT kéo dài là bình thường.

    Câu mẫu: "Em sẵn sàng làm thêm khi có việc gấp như sự cố production hay trước một đợt release quan trọng. Nhưng nếu OT diễn ra thường xuyên, em nghĩ cần xem lại cách estimate hoặc phạm vi, và em sẽ chủ động nêu điều đó với team. Em cũng muốn hỏi thêm: ở team mình OT thường xảy ra trong trường hợp nào và được tính như thế nào ạ?"

    Đây cũng là lúc bạn tìm hiểu ngược: OT có được trả lương hoặc nghỉ bù theo quy định không, tần suất ra sao. Câu trả lời của họ cho bạn biết nhiều về văn hoá công ty.

    Hay sai: hứa "OT thoải mái" để lấy điểm, rồi vào làm mới thấy không chịu nổi.

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

Bạn đã phỏng vấn ở DatVietVAC Group Holdings?

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