Câu hỏi phỏng vấn

Luyện trả lời theo đúng trình độ bạn đang ứng tuyển. Mỗi câu có đáp án ngắn gọn và điều người phỏng vấn thật sự muốn nghe.

  • Junior 0–2 năm: nền tảng, công cụ hằng ngày, làm việc an toàn trong team.
  • Middle 2–5 năm: tự chịu trách nhiệm một tính năng, hiểu đánh đổi.
  • Senior 5+ năm: thiết kế hệ thống, dẫn dắt kỹ thuật, quyết định dài hạn.
  1. JuniorAIAI viết code gọi một hàm của thư viện mà bạn chưa từng thấy. Bạn kiểm tra nó có thật không bằng cách nào?

    Người phỏng vấn muốn biết bạn có coi code do AI viết là bản nháp cần kiểm chứng hay không, và bạn có biết những cách kiểm chứng rẻ, nhanh, đáng tin hay chỉ hỏi lại AI.

    Cách đáng tin nhất là dùng nguồn mà AI không thể bịa. Bạn tìm tên hàm trong code hoặc file kiểu của thư viện đang cài, ví dụ tìm trong node_modules. Nếu tìm không ra thì hàm không có ở phiên bản đang dùng. Hỏi lại AI "hàm này có thật không" là cách kém tin cậy, vì nó có thể trả lời chắc chắn theo đúng cách đã bịa lúc đầu.

    Có ba lớp kiểm chứng, từ rẻ đến kỹ:

    1. Chạy thử. Bắt được lỗi trên đường chương trình đi qua, nhưng chỉ thấy lỗi từng cái một, và lỗi nằm trong nhánh ít chạy sẽ nằm im tới khi có người dùng thật đụng tới.
    2. Trình biên dịch hoặc linter. Với TypeScript, tsc --noEmit đọc cả file và báo mọi lời gọi sai hình dạng trong một lượt. Một lỗi ở đầu chuỗi có thể kéo theo các lỗi hệ quả, nên sửa lỗi đầu rồi chạy lại.
    3. Test. Bắt được hành vi mà bạn đã nghĩ ra để kiểm tra, ví dụ "sửa tin nhắn của người này không đụng tới sổ của người kia".

    Ý quan trọng để nêu: cả ba lớp đều không bắt được code đúng hình dạng nhưng sai ý nghĩa. Một hàm có thật nhưng dùng sai, hay tìm trong dữ liệu chung thay vì dữ liệu của riêng từng người, vẫn qua hết. Vì vậy cuối cùng cần đọc tài liệu chính thức cho những điều như "mã này có duy nhất trong phạm vi nào" và tự viết test cho các tình huống quan trọng.

    Điểm cộng: kể rằng bạn coi mỗi hàm lạ AI đưa ra là một câu hỏi cần trả lời chứ không phải câu trả lời, và nhắc rằng tên hàm bịa thường mượn từ thư viện tương tự (như hàm của Telegraf trong grammY).

    Mở trang riêng của câu hỏi
  2. JuniorAILàm sao biết một unit test do AI viết thật sự có tác dụng?

    Người phỏng vấn muốn biết bạn có coi test như một thứ cần được kiểm tra, hay chỉ thấy xanh là yên tâm. Câu trả lời tốt cho thấy bạn hiểu test có giá trị khi nó đỏ lúc code sai.

    Ý chính: một test xanh với code đúng chưa chứng minh được gì. Cách kiểm tra là làm hỏng code có chủ đích. Bạn sửa một chỗ nhỏ, như đảo một điều kiện, bỏ một dòng, đổi một hằng số, rồi chạy lại test. Nếu test vẫn xanh thì nó chưa bảo vệ chỗ đó. Kỹ thuật này gọi là mutation testing, và có công cụ làm tự động như Stryker cho JavaScript.

    Có thể nêu thêm bốn thói quen:

    1. Nói rõ điều cần bảo vệ khi nhờ AI viết test, ví dụ "hai người dùng không dùng chung dữ liệu", thay vì chỉ nói "viết test cho file này".
    2. Đừng tin độ phủ (coverage) một mình. Độ phủ chỉ cho biết dòng code đã chạy qua, không cho biết kết quả có được kiểm tra hay không. Hai bộ test có thể cùng độ phủ 100% mà một bộ bắt được ít lỗi hơn hẳn.
    3. Đọc từng câu assert. Kiểm tra kiểu "có trả lời" hay "chứa chữ này" rất lỏng. So sánh với kết quả đầy đủ chặt hơn nhiều.
    4. Thử đúng các lỗi đã từng xảy ra. Mỗi lỗi từng gặp nên có ít nhất một test sẽ đỏ nếu nó quay lại.

    Điểm cộng: kể một ví dụ cụ thể, chẳng hạn một bot có test cho tính năng ghi chi tiêu nhưng chỉ thử với một người dùng, nên khi code vô tình dùng chung một danh sách cho mọi người, test vẫn xanh. Chỉ khi thêm test với hai người dùng thì lỗi mới lộ ra.

    Mở trang riêng của câu hỏi
  3. JuniorAIRAG là gì, và khi nào nên dùng RAG thay vì gửi cả tài liệu vào prompt?

    Người phỏng vấn muốn biết bạn hiểu RAG như một quy trình có các bước, chứ không chỉ như một cái tên thời thượng, và bạn có biết cân nhắc đánh đổi hay không.

    RAG, viết tắt của retrieval-augmented generation, gồm ba bước. Một là tách tài liệu thành các đoạn nhỏ, làm một lần từ trước. Hai là khi có câu hỏi, tìm vài đoạn liên quan nhất, bằng đếm từ khóa hoặc bằng cách so nghĩa. Ba là ghép các đoạn đó vào prompt rồi mới gọi mô hình, kèm yêu cầu chỉ trả lời dựa trên các đoạn đó.

    Nên dùng RAG khi:

    1. Tài liệu đủ dài để gửi cả tài liệu là tốn. Mọi thứ trong prompt được gửi lại ở mỗi lần gọi, nên chi phí và độ trễ tăng theo độ dài tài liệu, còn RAG chỉ gửi vài đoạn.
    2. Tài liệu thay đổi thường xuyên. Sửa tài liệu và cập nhật chỉ mục, không phải viết lại prompt.
    3. Bạn cần biết câu trả lời dựa vào đâu. Mỗi đoạn có tên, nên có thể bắt mô hình trích nguồn và kiểm tra lại.

    Không nên dùng RAG khi tài liệu chỉ vài trang. Gửi cả tài liệu đơn giản hơn và bớt một chỗ có thể sai.

    Ý quan trọng để nêu: RAG thêm một chỗ để lỗi xảy ra là bước tìm kiếm. Nếu bước đó không đưa đúng đoạn, mô hình có thể trả lời thiếu hoặc đoán, trong khi câu trả lời vẫn nghe rất trôi chảy. Vì vậy khi RAG trả lời sai, việc đầu tiên là xem các đoạn nào đã được gửi đi, chứ không phải sửa prompt ngay.

    Điểm cộng: kể một ví dụ cụ thể, như khách hỏi "tiền về bao lâu" trong khi tài liệu viết "thời hạn hoàn tiền". Tìm theo từ khóa bỏ sót câu này vì hai cách nói không có chữ nào chung, và đó là lý do người ta chuyển sang tìm theo nghĩa bằng embedding.

    Mở trang riêng của câu hỏi
  4. JuniorAI AgentAI agent khác chatbot và workflow ở điểm nào? Khi nào không nên dùng agent?

    Câu trả lời tốt phân biệt ba thứ theo một tiêu chí: ai quyết định bước tiếp theo.

    • Chatbot theo luật: luật viết tay quyết định (ví dụ dò từ khóa). Rẻ, nhưng không hiểu câu; "đơn của em giao chưa" có thể bị hiểu thành hỏi phí ship vì có chữ "giao".
    • Workflow: lập trình viên viết sẵn đường đi trong code; LLM chỉ làm từng bước nhỏ như phân loại hay viết câu trả lời. Dễ đoán, dễ kiểm thử, rẻ, nhưng chỉ đi được những đường đã viết.
    • Agent: LLM tự chọn bước tiếp theo trong một vòng lặp: gọi công cụ, đọc kết quả, quyết định gọi tiếp hay trả lời. Xử lý được yêu cầu nhiều bước, có điều kiện, như "nếu đơn chưa giao thì thêm một món".

    Không nên dùng agent khi:

    • các loại yêu cầu biết trước và mỗi loại có cách xử lý rõ ràng (workflow đủ, nhanh và rẻ hơn);
    • một lần gọi LLM kèm thông tin phù hợp đã giải quyết được;
    • hành động có rủi ro cao (tiền, xóa dữ liệu) mà chưa có người duyệt.

    Điểm cộng: nhắc tới giới hạn số bước của vòng lặp, chi phí mỗi bước là một lần gọi mô hình, và thiết kế kết hợp, tức workflow ở cửa vào, agent chỉ cho yêu cầu phức tạp.

    Mở trang riêng của câu hỏi
  5. JuniorAI AgentLLM hallucination là gì? Khi xây chatbot chăm sóc khách hàng, bạn giảm nó bằng cách nào?

    Hallucination là khi mô hình ngôn ngữ sinh ra thông tin nghe hợp lý nhưng sai, hoặc không có trong dữ liệu được cung cấp. Ví dụ, khách hỏi "đơn 1041 giao chưa" mà request gửi cho mô hình không hề có dữ liệu đơn hàng, mô hình vẫn có thể trả lời "đơn của bạn đã giao rồi ạ".

    Ý quan trọng cần nói được là mô hình chỉ biết những gì nằm trong request cộng với kiến thức chung nó đã học. Nó không tự kết nối với database của bạn. Vì vậy, với dữ liệu thay đổi liên tục như trạng thái đơn, viết prompt khéo hơn không phải là cách sửa.

    Các cách giảm, từ dễ đến chắc chắn:

    • Đưa thông tin cần thiết vào prompt (giờ mở cửa, bảng giá) và dặn "chỉ dùng thông tin này".
    • Cho phép mô hình nói "chưa biết", kèm câu thay thế cụ thể. Tài liệu của Anthropic coi đây là kỹ thuật cơ bản đầu tiên.
    • Với dữ liệu động, để code tra dữ liệu thật rồi đưa kết quả cho mô hình, tức là dùng tool calling hoặc RAG.
    • Thêm lớp kiểm tra bằng code cho những câu trả lời rủi ro, và có test cho lớp đó.

    Điểm cộng: nói rõ các kỹ thuật này giảm chứ không loại bỏ hoàn toàn hallucination, nên việc có rủi ro cao (tiền, hứa hẹn với khách) vẫn cần người duyệt.

    Mở trang riêng của câu hỏi
  6. JuniorAI AgentTool calling (function calling) là gì? Khi dùng tool calling, mô hình có tự chạy hàm của bạn không?

    Tool calling là cách cho LLM dùng dữ liệu và hành động bên ngoài nó. Ý quan trọng nhất cần nói là mô hình không tự chạy hàm nào cả: nó chỉ viết ra một yêu cầu, còn code của bạn quyết định có chạy hay không.

    Một lần gọi công cụ đi qua bốn bước:

    1. Ứng dụng gửi request kèm danh sách công cụ, mỗi công cụ có tên, mô tả (khi nào nên dùng) và JSON Schema cho đầu vào.
    2. Nếu cần, mô hình trả về khối tool_use (tên công cụ và đối số) với stop_reason là tool_use, thay vì câu trả lời.
    3. Ứng dụng chạy hàm thật, ví dụ đọc đơn 1041 trong database, rồi gửi kết quả lại trong khối tool_result có cùng id.
    4. Mô hình đọc kết quả và viết câu trả lời cuối.

    Nên nói thêm vài ý để cho thấy bạn đã làm thật:

    • Đầu vào do mô hình viết là dữ liệu không đáng tin, phải kiểm tra như dữ liệu người dùng gửi lên (định dạng, quyền truy cập). Schema chỉ bảo đảm kiểu dữ liệu, không bảo đảm giá trị hợp lý.
    • Lỗi của công cụ nên trả về cho mô hình (ví dụ is_error: true, "không có đơn 1099") để nó hỏi lại khách, thay vì làm cả ứng dụng dừng.
    • Mỗi lần dùng công cụ tốn thêm một lượt gọi API, và định nghĩa công cụ được tính vào token đầu vào ở mọi request.
    • Khi câu hỏi cần nhiều bước (tìm mã đơn theo số điện thoại rồi mới xem trạng thái), ứng dụng phải lặp cho tới khi mô hình trả lời bằng chữ. Đó là vòng lặp agent.
    Mở trang riêng của câu hỏi
  7. JuniorAI cho developerBạn chọn công cụ AI hỗ trợ lập trình (Copilot, Cursor, Claude Code…) dựa trên tiêu chí nào?

    Câu trả lời tốt không xếp hạng công cụ, mà cho thấy bạn hiểu chúng khác nhau ở đâu và bạn kiểm soát kết quả thế nào.

    Tiêu chí đầu tiên là mức độ giao việc. Gợi ý khi gõ (Copilot, Tab của Cursor) giữ bạn là người viết từng dòng. Agent trong editor sửa nhiều file và để bạn duyệt từng thay đổi. Agent tự chạy lệnh như Claude Code tự chạy test, đọc lỗi, sửa tiếp. Giao càng nhiều thì càng phải review kỹ.

    Tiêu chí thứ hai là quy định của công ty: công cụ nào được phép, code có được gửi ra ngoài không, có chính sách dữ liệu nào không. Ở công ty, đây thường là tiêu chí quyết định.

    Tiêu chí thứ ba là chi phí và gói miễn phí, vì giá thay đổi liên tục, nên luôn xem trang giá chính thức.

    Phần quan trọng nhất là cách bạn kiểm tra kết quả, dù dùng công cụ nào:

    • viết spec ngắn và test trước khi giao việc;
    • dặn rõ "đừng sửa test";
    • tự chạy test với điều kiện giống môi trường thật (ví dụ múi giờ UTC);
    • thử thêm vài trường hợp ngoài test, và giải thích được từng đoạn code trước khi merge.

    Điểm cộng: kể một lỗi cụ thể mà test hoặc việc tự thử thêm đã bắt được.

    Mở trang riêng của câu hỏi
  8. JuniorAI cho developerBạn debug một lỗi khó như thế nào? Bạn dùng AI ở bước nào?

    Người phỏng vấn muốn nghe một quy trình, không phải một công cụ. Câu trả lời tốt có các bước rõ ràng và cho thấy bạn kiểm chứng trước khi tin.

    Một quy trình bạn có thể trình bày:

    1. Tái hiện lỗi bằng cách nhỏ nhất có thể: một lệnh, một script, một test. Chưa tái hiện được thì chưa nên sửa, vì bạn không có cách biết mình đã sửa xong.
    2. Ghi rõ kết quả thật và kết quả mong muốn. "Tổng sai" không đủ; "tổng ra 67.000đ, lẽ ra 35.000đ" thì đủ.
    3. Lần theo dữ liệu, từ chỗ nó vào tới chỗ nó sai: tạo ở đâu, đi qua hàm nào, ai dùng chung. Đọc stack trace nếu có, đặt breakpoint thay vì rải console.log.
    4. Loại trừ có bằng chứng: phần nào đã có test xanh, phần nào đã kiểm tra bằng tay.
    5. Sửa, rồi chạy lại cách tái hiện và toàn bộ test. Tìm mọi nơi gọi hàm mà bạn vừa đổi.

    Về AI, nên nói rõ bạn dùng nó ở đâu và không dùng ở đâu. AI giúp nhiều ở bước 3: đọc code lạ, gợi ý chỗ đáng nghi, giải thích một thông báo lỗi. Nhưng AI chỉ thấy những gì bạn đưa cho nó, nên bạn đưa cả cách tái hiện, kết quả thật, kết quả mong muốn, những file mà dữ liệu đi qua và những gì đã loại trừ. Bạn cũng yêu cầu nó chỉ ra file và dòng trước khi sửa, để tự kiểm tra nguyên nhân.

    Điểm cộng: kể một lỗi cụ thể, ví dụ một lỗi chỉ xuất hiện khi có hai người dùng cùng lúc, và nói bạn đã làm gì để nó không quay lại (một test tái hiện đúng lỗi đó).

    Mở trang riêng của câu hỏi
  9. JuniorAI cho developerBạn dùng AI review code thế nào, và kiểm tra nhận xét của nó ra sao?

    Người phỏng vấn muốn biết bạn dùng AI như một người review thứ hai có kiểm soát, không phải một người phán quyết, và bạn có giữ được khả năng tự đọc code hay không.

    Mình tự review trước, rồi mới nhờ AI. Nếu xem danh sách của AI trước, mình bị neo vào đó và chỉ đánh giá những gì nó nêu, thay vì tự tìm. Khi tự review, mình dùng khung quen thuộc: thiết kế, chức năng và trường hợp biên, độ phức tạp, test, tên gọi (Google có tài liệu hướng dẫn về những điều cần nhìn khi review).

    Khi nhờ AI, mình đòi mỗi nhận xét kèm dòng liên quan, một đầu vào cụ thể gây lỗi, và mức độ, để có thứ mà chạy thử. Rồi mình kiểm từng nhận xét: viết một phép thử nhỏ, hoặc chạy đầu vào đó, xem lỗi có thật không. Bước này quan trọng vì AI có thể nói sai mà rất tự tin, ví dụ khẳng định một hàm sẽ ném lỗi trong khi nó lặng lẽ trả về giá trị sai. Lỗi phụ thuộc môi trường như múi giờ cần được thử ở nhiều cấu hình.

    Cuối cùng mình ghi lại mỗi lần review: mình bắt được gì trước, chỉ AI bắt được gì, AI nói sai gì. Cột "chỉ AI bắt được" cho biết mình đang yếu ở loại lỗi nào.

    Điểm cộng: nhắc rằng AI thiếu bối cảnh cho câu hỏi thiết kế (có nên thêm tính năng này lúc này không), nên coi câu trả lời về thiết kế là ý kiến, và không dán khóa API hay dữ liệu khách hàng vào prompt.

    Mở trang riêng của câu hỏi
  10. JuniorAI cho developerBạn dùng công cụ AI khi lập trình như thế nào? Làm sao biết code AI viết là đúng?

    Người phỏng vấn không hỏi bạn có dùng AI không; họ muốn biết bạn có kiểm soát được code mình nộp hay không. Một câu trả lời tốt mô tả một quy trình cụ thể:

    1. Định nghĩa "đúng" trước khi nhờ AI. Viết yêu cầu ngắn kèm vài ví dụ đầu vào và kết quả mong muốn, gồm cả trường hợp lỗi. Ví dụ: tin nhắn "cà phê 35k" phải ra 35.000đ, tin nhắn không có số tiền phải bị từ chối.
    2. Biến ví dụ thành test, để AI viết code cho tới khi test qua, và chính mình chạy lại test.
    3. Đọc và giải thích được từng đoạn code trước khi commit. Đoạn nào không giải thích được thì hỏi lại hoặc viết lại.
    4. Thử thêm các trường hợp ngoài ví dụ. Test chỉ bảo vệ những gì mình đã nghĩ tới; ví dụ "35 nghìn" có thể bị đọc thành 35 đồng mà test vẫn xanh.
    5. Kiểm chứng API và thư viện với tài liệu chính thống, vì AI có thể gọi hàm không tồn tại hoặc dùng cú pháp cũ.
    6. Không dán bí mật (API key, dữ liệu khách hàng, code công ty khi chưa được phép) vào công cụ AI.

    Điểm cộng: kể một lần cụ thể bạn phát hiện code AI sai và cách bạn phát hiện ra.

    Mở trang riêng của câu hỏi
  11. JuniorAI cho developerBạn không nên đưa gì cho công cụ AI khi lập trình, và nếu lỡ lộ một API key thì làm gì?

    Người phỏng vấn muốn biết bạn dùng AI một cách có ý thức về bảo mật, và biết thứ tự xử lý khi sự cố đã xảy ra.

    Có ba nhóm không nên đưa cho công cụ AI. Thứ nhất là khóa và mật khẩu: API key, token, chuỗi kết nối cơ sở dữ liệu. Thứ hai là dữ liệu của người khác, như tên, số điện thoại, email của người dùng thật. Thứ ba là code và tài liệu không thuộc về bạn, như code của công ty hay khách hàng, vì mỗi công cụ và mỗi gói dịch vụ có chính sách riêng về việc lưu và dùng dữ liệu, và bạn cần đọc điều khoản của đúng gói mình dùng, đồng thời hỏi công ty có danh sách công cụ được duyệt không.

    Cách làm cụ thể: thay dữ liệu thật bằng dữ liệu giả có cùng hình dạng, che khóa trước khi dán (có thể bằng một bộ lọc chạy trước), giữ khóa trong biến môi trường chứ không viết vào code, đưa .env vào .gitignore, và cấu hình công cụ AI để nó không đọc được các file bí mật. Nên có thêm một lớp tự động như quét bí mật trước khi commit hoặc bảo vệ lúc đẩy code, vì quy trình thủ công hay bị quên đúng lúc vội.

    Nếu lỡ lộ một khóa, việc đầu tiên là thu hồi nó, chứ không phải xóa dấu vết: xóa dòng khỏi Git là chưa đủ vì bản sao cũ còn trong lịch sử, các bản clone và fork. Sau khi thu hồi thì tạo khóa mới và cập nhật nơi đang dùng, xem lịch sử sử dụng có hoạt động lạ không, và chỉ sau cùng mới dọn lịch sử Git.

    Điểm cộng: nói được rằng bộ lọc bí mật chỉ bắt được các hình dạng đã biết nên không thay được thói quen, và nhắc việc thử bộ lọc với tên biến thật của dự án.

    Mở trang riêng của câu hỏi
  12. JuniorAI cho developerKhi nhờ AI viết hoặc sửa code, bạn viết prompt thế nào để kết quả dùng được?

    Người phỏng vấn không cần một "câu thần chú". Họ muốn biết bạn có giữ quyền kiểm soát hay chỉ dán yêu cầu rồi nhận code. Một câu trả lời tốt nêu ba thứ mà prompt nào cũng nên nói rõ:

    1. Việc cần làm, kèm ngữ cảnh đủ dùng: file nào, spec nào, điều kiện chạy (ví dụ test chạy với TZ=UTC).
    2. Việc không được làm: "chưa viết code, hãy hỏi lại chỗ chưa rõ", "không sửa test", "không thêm thư viện", "chỉ sửa đúng vấn đề số 1". Thiếu ràng buộc, AI dễ chọn con đường ngắn nhất, ví dụ sửa test cho xanh.
    3. Cách kiểm tra kết quả: "chạy test và dán kết quả", "test mới phải đỏ trên code hiện tại", "với mỗi khẳng định, đưa một lệnh để mình tự kiểm chứng".

    Nên kể một ví dụ cụ thể. Chẳng hạn: trước khi sửa một lệnh trong app, bạn nhờ AI liệt kê các đầu vào dễ sai, tự chạy thử chúng và phát hiện tin nhắn "/tong tháng 10" bị ghi thành một khoản chi 10 đồng. Sau đó bạn viết test đỏ trước, rồi mới nhờ AI sửa code mà không được đụng vào test.

    Điểm cộng: nói rằng bước kiểm tra là phần không giao cho AI. Lời giải thích nghe hợp lý chưa chắc đã đúng, nên bạn chạy lệnh, đo, đọc diff, rồi mới chấp nhận.

    Mở trang riêng của câu hỏi
  13. JuniorAI cho developerKhi phải dùng một thư viện hoặc nhận một codebase bạn chưa từng thấy, bạn tìm hiểu nó thế nào?

    Người phỏng vấn muốn biết bạn có phương pháp hay chỉ thử sai cho tới khi chạy. Một câu trả lời tốt đi theo thứ tự từ rộng tới hẹp:

    1. Xác định phiên bản và điểm vào. Xem package.json đang dùng bản nào, đọc README và file điểm vào (ví dụ index/mod). Với codebase, chạy được dự án và test trước khi đọc sâu.
    2. Đi theo một luồng cụ thể thay vì đọc hết. Chọn đúng việc mình cần làm (ví dụ "nhận một tin nhắn và trả lời") rồi lần theo từ hàm công khai vào trong.
    3. Liệt kê giả định và kiểm chứng từng cái. Ví dụ với một thư viện Telegram: trường date tính bằng giây hay mili giây, tin nhắn được sửa có đi qua handler không, handler ném lỗi thì chuyện gì xảy ra. Mỗi câu trả lời phải chỉ ra được file và dòng trong mã nguồn hoặc tài liệu chính thức.
    4. Viết một script nhỏ chạy được để thấy hành vi thật, tốt nhất là không cần mạng hay dịch vụ ngoài.

    Nếu bạn dùng AI, hãy nói rõ cách dùng: cho nó đọc mã nguồn của đúng phiên bản đã cài (trong node_modules) thay vì trả lời theo trí nhớ, yêu cầu kèm file và số dòng, rồi tự mở ra đọc. Trí nhớ của mô hình có thể đúng cho một phiên bản cũ, nên câu trả lời trôi chảy vẫn có thể sai với bản bạn đang dùng.

    Nên kể một ví dụ thật, chẳng hạn một lần giả định sai về đơn vị thời gian cho ra ngày tháng 1 năm 1970, và bạn phát hiện nhờ chạy thử thay vì nhờ đọc.

    Điểm cộng: nói rằng bạn ghi lại những gì đã tìm hiểu (file nào làm gì, chỗ nào dễ sai) vào README hoặc ghi chú, để người sau không phải dò lại.

    Mở trang riêng của câu hỏi
  14. JuniorAI cho developerLàm sao để công cụ AI viết code theo đúng quy ước của dự án qua nhiều phiên làm việc, và bạn kiểm tra điều đó thế nào?

    Người phỏng vấn muốn biết bạn coi AI như một đồng nghiệp mới mỗi phiên, cần được cho ngữ cảnh, và bạn không tin vào lời nhắc nếu có cách để máy kiểm.

    Mỗi phiên làm việc với một công cụ AI bắt đầu từ một cửa sổ ngữ cảnh mới, nên nó không nhớ quy ước mà bạn đã chỉ hôm qua. Cách phổ biến là viết quy ước vào một file nằm trong dự án mà công cụ đọc ở đầu phiên, như CLAUDE.md của Claude Code hay AGENTS.md ở nhiều công cụ khác. Nội dung nên là những thứ AI không tự suy ra được từ code: lệnh chạy test và kiểm kiểu, cú pháp bị cấm (ví dụ enum khi Node chỉ xóa kiểu), việc phải hỏi trước, việc không được làm.

    Hai nguyên tắc viết: cụ thể đến mức kiểm được ("chạy npm test trước khi commit" thay cho "kiểm thử cẩn thận"), và ngắn, vì file dài làm loãng các dòng quan trọng. Mô tả chính thức của Claude Code coi file này là ngữ cảnh, không phải cấu hình ép buộc, nên quy ước nào vi phạm thì hỏng thì hãy để một công cụ bắt (một cờ của trình biên dịch, một bước kiểm trong CI) rồi chỉ ghi lệnh đó vào file.

    Còn một bẫy: file hướng dẫn cũ đi. Tên script đổi hay file được chuyển mà file không sửa thì AI sẽ tin vào chỉ dẫn sai. Một script nhỏ kiểm các lệnh và đường dẫn nhắc tới còn tồn tại là cách rẻ để tránh chuyện này.

    Điểm cộng: thừa nhận rằng bạn khó đo AI tuân theo tốt hơn bao nhiêu, và đề xuất đo bằng một bộ nhiệm vụ cố định chạy trước và sau khi sửa file.

    Mở trang riêng của câu hỏi
  15. JuniorAI cho developerNhờ AI viết commit message từ git diff thì còn thiếu gì, và bạn kiểm tra kết quả thế nào?

    Người phỏng vấn muốn biết bạn dùng AI như một công cụ hỗ trợ có kiểm soát, và hiểu một commit message tốt nên chứa gì.

    Điều AI thiếu là lý do. Diff chỉ cho biết dòng nào được thêm hoặc xóa, tức là "đổi gì". Còn "vì sao đổi" nằm trong đầu bạn: lỗi nào người dùng gặp, vì sao chọn cách này thay vì cách khác. Nếu chỉ đưa diff, bạn thường nhận được kiểu message như "thêm bot.catch để xử lý lỗi", đúng theo nghĩa đen nhưng người đọc sau không biết chuyện gì đã xảy ra. Vì vậy mình luôn đưa thêm một hai câu ý định vào prompt.

    Mình kiểm tra kết quả theo ba bước. Thứ nhất, đọc từng câu và đối chiếu với diff: nếu message nói "đã thêm test" mà diff không có file test nào thì đó là lời bịa và phải xóa. Thứ hai, xem dòng đầu có đúng dạng của dự án không (ví dụ kiểu(phạm vi): mô tả, ngắn gọn, bắt đầu bằng động từ). Thứ ba, xem phần thân có giải thích vì sao thay vì kể lại từng dòng code.

    Một ý nữa là AI chỉ viết hay khi diff chỉ chứa một việc. Nếu bạn gộp spec, tính năng và test vào một commit thì không message nào mô tả tốt được. Nên tách commit theo ý nghĩa trước (git add -p giúp chọn từng đoạn thay đổi), rồi mới nhờ AI.

    Điểm cộng: nhắc rằng người chịu trách nhiệm cho nội dung commit là bạn chứ không phải công cụ, và với pull request thì mục "đã kiểm tra thế nào" phải do bạn điền từ kết quả thật.

    Mở trang riêng của câu hỏi
  16. JuniorBảo mậtXác thực (authentication) khác phân quyền (authorization) thế nào? Nên kiểm tra quyền ở đâu trong một ứng dụng web?

    Xác thực trả lời câu hỏi "bạn là ai", ví dụ đăng nhập bằng email và mật khẩu. Phân quyền trả lời câu hỏi "bạn được làm gì", ví dụ chỉ tác giả được sửa bài. Hai việc này tách rời nhau, và lỗi hay gặp nhất là coi việc thứ nhất như đã bao gồm việc thứ hai.

    Ví dụ cụ thể: một blog viết luật "ai đã đăng nhập thì được sửa bài", vì nghĩ rằng chỉ có tác giả mới có tài khoản. Nếu dịch vụ đăng nhập còn cho phép tự đăng ký, bất kỳ ai cũng tạo được tài khoản, đăng nhập hợp lệ, và sửa được bài. Người đó đã được xác thực đúng, nhưng chưa từng được phân quyền.

    Về chỗ kiểm tra quyền, câu trả lời tốt nêu được ba tầng và vai trò của từng tầng:

    1. Giao diện (ẩn nút, chuyển hướng): chỉ để người dùng thấy đúng thứ họ dùng được. Không bảo vệ gì, vì người ta gọi thẳng API.
    2. Server (API route, Server Action, middleware): mỗi điểm nhận request phải tự kiểm tra lại, không dựa vào việc người dùng đã đi qua trang nào.
    3. Database (ví dụ Row Level Security của PostgreSQL): lớp cuối cùng, vẫn đúng khi tầng trên viết thiếu một chỗ kiểm tra.

    Thêm hai ý cho thấy bạn đã làm thật:

    • Thông tin dùng để phân quyền phải nằm ở nơi người dùng không tự sửa được. Với Supabase, đó là app_metadata chứ không phải user_metadata.
    • Ở phía server, phải kiểm tra chữ ký của token chứ không chỉ đọc cookie, vì người dùng tự đặt được cookie trong trình duyệt của họ.

    Điểm cộng: nhắc tới mã 401 (chưa xác thực) và 403 (đã xác thực nhưng không có quyền), và việc quyền mới cấp hoặc mới thu hồi chỉ có hiệu lực khi token được làm mới.

    Mở trang riêng của câu hỏi
  17. JuniorDatabaseDatabase migration là gì? Thêm một cột NOT NULL vào bảng đang có dữ liệu thì cần lưu ý gì?

    Migration là một file (thường là SQL) mô tả một thay đổi của schema, ví dụ tạo bảng, thêm cột, thêm index. Các file được đặt tên theo thứ tự thời gian, commit vào Git và chạy lần lượt, nên database ở máy dev, staging và production luôn đi qua đúng cùng các bước. Thay đổi schema nhờ vậy cũng được review trong pull request như code.

    Với câu thêm cột NOT NULL, người phỏng vấn muốn nghe hai ý.

    Thứ nhất, alter table posts add column tags text[] not null sẽ lỗi trên bảng có dữ liệu: các dòng cũ không có giá trị cho cột mới, tức là null, vi phạm ràng buộc (PostgreSQL báo column "tags" of relation "posts" contains null values). Trên máy dev với bảng rỗng thì nó lại chạy được, nên lỗi chỉ lộ ra khi lên production. Cách sửa là cho giá trị mặc định: not null default '{}'. Nếu không có giá trị mặc định hợp lý, chia làm nhiều bước: thêm cột cho phép null, điền dữ liệu, rồi mới đặt not null.

    Thứ hai là tương thích ngược. Khi deploy, migration và code mới không đổi cùng một lúc. Thường có một khoảng thời gian bản code cũ chạy trên schema mới, nên migration không được làm hỏng bản cũ: không xóa hay đổi tên cột mà code cũ còn dùng, cột mới phải có mặc định để câu insert cũ vẫn chạy.

    Điểm cộng: nói rằng migration đã chạy trên production thì không sửa lại, muốn đổi thì viết migration mới.

    Mở trang riêng của câu hỏi
  18. JuniorDatabaseVì sao thường không lưu ảnh trực tiếp trong database? Bạn lưu ảnh ở đâu?

    Ảnh là dữ liệu lớn và hầu như không bao giờ cần truy vấn theo nội dung. Lưu thẳng vào database (dạng nhị phân hay base64) gây nhiều vấn đề:

    • Tốn dung lượng đắt nhất. Dung lượng database thường giới hạn và đắt hơn kho file. Ví dụ gói miễn phí của Supabase có 500 MB database: một bài viết dài chỉ vài chục KB chữ, trong khi một ảnh chụp điện thoại đã vài MB. Vài chục bài có ảnh là đầy.
    • Chậm và nặng. Mỗi truy vấn kéo theo dữ liệu lớn, sao lưu và khôi phục lâu hơn, bộ nhớ đệm của database bị chiếm chỗ.
    • Không tận dụng được CDN. Ảnh trong kho file (object storage) được phục vụ qua CDN, lưu đệm gần người dùng; ảnh trong database phải đi qua máy chủ ứng dụng.

    Cách làm phổ biến: lưu file ảnh ở object storage (Supabase Storage, Amazon S3, Cloudflare R2…), còn database chỉ lưu đường dẫn và thông tin mô tả (kích thước, mô tả alt, người tải lên). Thêm vào đó, nén ảnh trước khi tải lên (resize, đổi sang WebP/AVIF) để giảm cả dung lượng lưu trữ lẫn băng thông.

    Điểm cộng: nhắc tới việc dọn ảnh không còn được bài nào dùng, và phân quyền cho kho file, tức ai được tải lên, ai được xem.

    Mở trang riêng của câu hỏi
  19. JuniorDự án & kinh nghiệmBạn hãy giới thiệu về bản thân trong khoảng một phút.

    Câu này thường mở đầu buổi phỏng vấn, và người hỏi không cần nghe lại toàn bộ CV. Họ muốn biết trong một phút bạn chọn kể điều gì, và có kể rõ ràng được không. Câu trả lời tốt thường có bốn phần, mỗi phần một hai câu:

    1. Bạn là ai, làm gì: "Em là frontend developer mới tốt nghiệp, làm chủ yếu với React và TypeScript."
    2. Một bằng chứng cụ thể: một dự án, kèm phần khó nhất và kết quả. Ví dụ: "Em làm một app ghi chi tiêu bằng tin nhắn. Phần khó là đọc số tiền theo kiểu người Việt hay viết như 35k hay 1tr2, em giải quyết bằng một bảng ví dụ, mỗi dòng là một test. App có 21 test và vài bạn cùng lớp dùng hằng ngày."
    3. Điều bạn đang học hoặc muốn làm tốt hơn, nói cụ thể: "Em đang học viết test cho giao diện React."
    4. Vì sao bạn ở đây: nối với vị trí đang ứng tuyển, ví dụ công ty dùng React và TypeScript, sản phẩm có nhiều người dùng di động.

    Nên tránh:

    • Đọc lại CV theo thứ tự thời gian, từ trường cấp ba.
    • Dùng tính từ không kiểm chứng được như "năng động, ham học hỏi, chịu được áp lực".
    • Kể một dự án mà bạn không trả lời được câu hỏi tiếp theo về nó, vì người phỏng vấn thường hỏi sâu ngay vào dự án bạn vừa nhắc.

    Chuẩn bị trước: viết ra rồi tập nói thành lời vài lần, bấm giờ để giữ trong khoảng một phút. Nội dung nên khớp với tiêu đề và mục Giới thiệu trên LinkedIn của bạn, vì người phỏng vấn có thể vừa đọc chúng xong.

    Mở trang riêng của câu hỏi
  20. JuniorDự án & kinh nghiệmCuối buổi phỏng vấn, "Bạn có câu hỏi nào cho chúng tôi không?" — nên hỏi gì?

    Câu này không có đáp án đúng, nhưng có cách trả lời bỏ phí cơ hội: "dạ không ạ". Câu hỏi của bạn cho người phỏng vấn biết bạn quan tâm điều gì, và câu trả lời của họ giúp bạn quyết định có muốn làm ở đó không.

    Nên chuẩn bị sẵn 4–5 câu và chọn 2–3 câu tùy thời gian. Câu tốt thường gắn với một chi tiết trong tin tuyển dụng hoặc với điều người phỏng vấn vừa nói:

    • Về công việc: "Trong ba tháng đầu, một bạn junior thường làm những việc gì?", "Nhóm review code thế nào, một pull request thường mất bao lâu để được merge?"
    • Về việc học: "Người hướng dẫn gặp bạn mới bao lâu một lần?", "Theo anh/chị, một bạn junior làm tốt sau thử việc trông như thế nào?" Câu sau đặc biệt hữu ích, vì câu trả lời chính là tiêu chí bạn sẽ được đánh giá.
    • Về quy trình: "Sau buổi hôm nay còn vòng nào, và khi nào em có thể nhận phản hồi?"

    Nên tránh:

    • Câu mà tin tuyển dụng hoặc buổi nói chuyện đã trả lời rõ, vì nó cho thấy bạn không chú ý.
    • Hỏi về lương, ngày nghỉ ở vòng kỹ thuật với trưởng nhóm. Những chuyện này hỏi bộ phận tuyển dụng hoặc ở vòng sau.
    • Câu chung chung chép từ mạng mà bạn không thật sự quan tâm tới câu trả lời.

    Điểm cộng: hỏi tiếp một câu dựa trên điều người phỏng vấn vừa kể trong buổi, ví dụ "lúc nãy anh có nhắc nhóm đang chuyển sang Next.js, phần đó junior có được tham gia không?". Nó cho thấy bạn lắng nghe chứ không chỉ đọc câu đã chuẩn bị.

    Mở trang riêng của câu hỏi
  21. JuniorDự án & kinh nghiệmHãy kể về một dự án trong CV mà bạn tự hào nhất. Phần nào do chính bạn làm?

    Người phỏng vấn hỏi câu này để kiểm tra ba điều: dự án có thật sự do bạn làm không, bạn hiểu nó sâu đến đâu, và bạn có kể lại một việc kỹ thuật cho người khác hiểu được không.

    Một câu trả lời tốt dài khoảng 2 phút, theo thứ tự:

    1. Vấn đề: dự án giải quyết việc gì, cho ai. Một câu là đủ.
    2. Vai trò của bạn: với dự án nhóm, nói rõ phần nào bạn làm, ví dụ "mình làm toàn bộ phần đăng nhập và phân quyền". Đừng nói "chúng em làm" cho mọi thứ.
    3. Một quyết định kỹ thuật: bạn chọn cách làm nào, vì sao, và đã cân nhắc phương án nào khác. Ví dụ: "mình lưu ảnh trên storage và chỉ lưu đường dẫn trong database để không vượt giới hạn dung lượng".
    4. Một khó khăn và cách vượt qua: một lỗi cụ thể, bạn tìm ra nguyên nhân thế nào.
    5. Nếu làm lại: một điều bạn sẽ làm khác. Câu này cho thấy bạn biết tự nhìn lại.

    Nên tránh:

    • Kể lại danh sách tính năng như đọc README.
    • Nhận làm phần mà bạn không trả lời được câu hỏi tiếp theo về nó. Người phỏng vấn thường hỏi sâu thêm hai, ba tầng vào đúng phần bạn nhận.

    Chuẩn bị trước: với mỗi dự án trong CV, viết sẵn 5 ý trên và tập nói thành lời ít nhất một lần.

    Mở trang riêng của câu hỏi
  22. JuniorDự án & kinh nghiệmNếu được làm lại dự án cá nhân của mình từ đầu, bạn sẽ thay đổi điều gì?

    Câu này kiểm tra xem bạn có tự nhìn lại công việc của mình không. Trả lời "em thấy ổn rồi, không đổi gì" là câu yếu nhất, vì nó cho thấy bạn chưa học được gì từ dự án.

    Một câu trả lời tốt thường có ba phần:

    • Một quyết định cụ thể bạn sẽ đổi, kèm lý do. Ví dụ: "Em sẽ viết bảng ví dụ và test trước khi viết hàm đọc số tiền. Lần đầu em viết code trước, và một tin nhắn 'trà sữa 32K' bị đọc thành 32 tỷ."
    • Điều bạn sẽ giữ nguyên, để cho thấy bạn phân biệt được quyết định tốt và quyết định tệ. Ví dụ: "Em vẫn giữ việc từ chối khi tin nhắn có hai số tiền, vì ghi sai tiền tệ hơn bắt người dùng nhắn lại."
    • Bài học áp dụng được cho việc khác, như cắt phạm vi sớm, nghĩ tới múi giờ của máy chủ, hay hỏi người dùng thật sớm hơn.

    Tránh hai cái bẫy: chê công nghệ đã chọn mà không nêu được vì sao, và kể một thay đổi lớn đến mức nghe như đập đi làm lại toàn bộ.

    Điểm cộng: chuẩn bị trước câu này cho dự án đầu tiên trong CV, vì người phỏng vấn thường hỏi tiếp ngay sau câu "kể về dự án của bạn".

    Mở trang riêng của câu hỏi
  23. JuniorDự án & kinh nghiệmVì sao bạn ứng tuyển vào vị trí này ở công ty chúng tôi?

    Câu này kiểm tra một điều đơn giản: bạn có đọc tin tuyển dụng và tìm hiểu công ty không, hay đang gửi cùng một hồ sơ cho mọi nơi. Người hỏi không cần nghe lời khen công ty. Họ muốn thấy bạn nối được thứ họ cần với thứ bạn đã làm.

    Một câu trả lời tốt thường có ba ý, mỗi ý một hai câu:

    1. Một chi tiết cụ thể trong tin hoặc về sản phẩm. Ví dụ: "Tin ghi nhóm frontend viết test cho phần mình làm và có review code. Em đang tìm đúng một nơi như vậy."
    2. Bằng chứng bạn hợp với chi tiết đó. "Dự án cá nhân của em là một app ghi chi tiêu, viết bằng React và TypeScript, có 21 unit test. Em đã quen làm việc qua pull request trong đồ án nhóm."
    3. Điều bạn muốn học ở đây, nói thật và cụ thể: "Em chưa từng làm việc với một codebase lớn có nhiều người cùng sửa, và em muốn học cách làm đó từ nhóm."

    Nên tránh:

    • Câu chung chung dùng được cho mọi công ty: "môi trường năng động, chuyên nghiệp", "công ty lớn, nhiều cơ hội".
    • Chỉ nói về thứ bạn nhận được (lương, gần nhà) mà không nói bạn đóng góp gì. Những lý do đó hoàn toàn chính đáng, nhưng không nên là ý duy nhất.
    • Nói sai về sản phẩm của công ty. Nếu chưa dùng thử được, hãy nói mình đã đọc gì về nó.

    Chuẩn bị trước: đọc lại tin tuyển dụng, gạch chân hai yêu cầu bạn có bằng chứng rõ nhất, và dành mười phút xem trang web hoặc sản phẩm của công ty. Giữ lại bản CV bạn đã gửi cho đúng nơi này, vì người phỏng vấn sẽ hỏi dựa trên nó.

    Điểm cộng: kết thúc bằng một câu hỏi ngược lại về công việc hằng ngày, ví dụ nhóm đang review code theo cách nào.

    Mở trang riêng của câu hỏi
  24. JuniorGitGit merge và git rebase khác nhau thế nào? Khi nào nên dùng cái nào?

    Cả hai đều đưa thay đổi từ nhánh này sang nhánh khác, nhưng ghi lại lịch sử theo hai cách khác nhau.

    • git merge tạo một merge commit nối hai nhánh. Lịch sử giữ nguyên, thấy rõ nhánh đã tách và nhập lại khi nào.
    • git rebase viết lại các commit của nhánh bạn lên trên đầu nhánh đích. Lịch sử thành một đường thẳng, dễ đọc hơn, nhưng mã hash của các commit thay đổi.
    Cập nhật nhánh feature từ main
    git switch feat/login
    git fetch origin
    git rebase origin/main   # lịch sử thẳng, chỉ làm trên nhánh của riêng bạn

    Trong phỏng vấn, nói thêm được đánh đổi (lịch sử thật so với lịch sử dễ đọc) và quy tắc của team bạn đang dùng sẽ ghi điểm hơn là chỉ định nghĩa.

    Mở trang riêng của câu hỏi
  25. JuniorGitKhi nhận code review mà bạn không đồng ý với một góp ý, bạn làm gì?

    Người phỏng vấn hỏi câu này để xem bạn làm việc nhóm thế nào, không phải để xem bạn thắng tranh luận. Một câu trả lời tốt cho thấy bạn tách được cảm xúc khỏi kỹ thuật, và bạn quyết định dựa trên bằng chứng.

    Bạn có thể trả lời theo bốn bước:

    1. Hiểu đúng góp ý trước đã. Đọc lại, và nếu chưa rõ thì hỏi lại người review đang lo điều gì. Nhiều khi mình không đồng ý chỉ vì hiểu sai ý họ.
    2. Thử điều họ đề nghị. Chạy thử thay đổi đó, chạy test. Kết quả thường tự trả lời: hoặc góp ý đúng và bạn sửa theo, hoặc nó làm hỏng một trường hợp cụ thể.
    3. Trả lời bằng dữ liệu và kết thúc bằng câu hỏi. Ví dụ: "Em thử cách này rồi, 4 test từ chối bị đỏ vì '35,000' bị đọc thành 35000 mà không báo lỗi. Em muốn giữ cách cũ, anh thấy ổn không?". Câu hỏi để ngỏ khả năng người review biết điều bạn chưa biết.
    4. Không xong sau hai lượt thì nói chuyện trực tiếp. Mười bình luận qua lại tốn thời gian hơn năm phút gọi nhau. Nếu vẫn chưa thống nhất, theo quy ước của nhóm hoặc nhờ một người thứ ba.

    Nên nêu thêm hai ý. Một là phân biệt lỗi thật với sở thích cá nhân: với chuyện sở thích, mình nói rõ lý do của mình nhưng sẵn sàng theo cách của nhóm. Hai là góp ý đúng nhưng ngoài phạm vi thì ghi thành việc riêng, không nhồi vào pull request đang review.

    Điểm cộng: kể một lần cụ thể bạn từng không đồng ý, bạn đã thử gì, và kết quả ra sao, kể cả khi người review đúng.

    Mở trang riêng của câu hỏi
  26. JuniorGitMerge conflict là gì, bạn xử lý nó thế nào, và khi nào chọn merge, khi nào chọn rebase?

    Người phỏng vấn muốn biết bạn đã thật sự làm việc nhóm với Git chưa, và có sợ khi thấy xung đột hay không. Họ cũng muốn nghe cách bạn tránh làm mất code của đồng nghiệp.

    Một câu trả lời tốt giải thích merge conflict xảy ra khi hai nhánh cùng sửa một chỗ và Git không biết giữ bên nào, nên nó dừng lại và đánh dấu bằng ba dòng <<<<<<<, =======, >>>>>>>. Việc của bạn không phải xóa dấu mà là viết lại một đoạn đúng từ ý định của cả hai bên, chạy kiểm tra, rồi mới git add và hoàn tất. Chỉ xóa dòng đánh dấu và giữ cả hai đoạn thường tạo ra code hỏng hoặc trùng lặp. Nếu giữa chừng thấy rối, có thể hủy bằng git merge --abort hoặc git rebase --abort.

    Về khác biệt, merge giữ nguyên lịch sử và thêm một commit gộp, nên an toàn và xử lý xung đột một lần. Rebase đặt commit của bạn lên đầu mới nhất của nhánh kia và áp lại từng commit, cho lịch sử thẳng nhưng viết lại commit, mỗi commit có thể xung đột riêng, và nhánh đã đẩy lên máy chủ thì phải push bằng --force-with-lease. Vì vậy rebase hợp với nhánh riêng chưa ai dùng.

    Điểm cộng là biết rằng khi rebase, ours và theirs bị đảo vai so với merge, và biết dùng git reflog để tìm lại trạng thái trước khi làm hỏng.

    Mở trang riêng của câu hỏi
  27. JuniorGitVì sao không nên commit thẳng lên nhánh main? Một pull request tốt cần có những gì?

    Commit thẳng lên main nghĩa là thay đổi trở thành code chung ngay lập tức, trước khi có ai đọc và trước khi kiểm tra tự động (CI) chạy. Nếu main còn nối với việc deploy, một lỗi nhỏ sẽ lên thẳng production. Làm trên một nhánh riêng thì main luôn ở trạng thái chạy được, nhiều người làm song song không giẫm lên nhau, và mỗi thay đổi có một chỗ để thảo luận.

    Với phần thứ hai, người phỏng vấn muốn nghe bạn nghĩ cho người review. Một pull request tốt thường có:

    • Một mục đích duy nhất. Sửa một lỗi hoặc thêm một tính năng. PR trộn sửa lỗi với đổi tên hàng loạt và nâng thư viện thì rất khó review, và khó gỡ ra khi có sự cố.
    • Commit chia theo ý nghĩa, tên rõ ràng. Ví dụ một commit cho bản sửa, một commit cho test. File tự sinh như package-lock.json nằm cùng commit với thay đổi sinh ra nó.
    • Mô tả trả lời bốn câu: vấn đề là gì, đã đổi gì, kiểm tra bằng cách nào (lệnh cụ thể), và điều gì cố ý chưa làm.
    • Đã tự review trước. Đọc lại diff của chính mình để bỏ console.log để quên, file cấu hình cá nhân, code thử nghiệm.
    • Kích thước vừa phải. Vài chục tới vài trăm dòng có ý nghĩa. PR nghìn dòng thường chỉ nhận được một chữ "LGTM" mà không ai thật sự đọc.

    Nên kể một ví dụ của chính bạn, chẳng hạn một lần đọc lại diff và bắt được dòng log in ra số điện thoại khách hàng trước khi mở PR.

    Điểm cộng: nói rằng nhiều nhóm bật branch protection để chặn push thẳng lên main và bắt buộc CI xanh trước khi merge, và rằng PR nhỏ giúp tìm ra commit gây lỗi nhanh hơn.

    Mở trang riêng của câu hỏi
  28. JuniorHTTP & WebGET và POST khác nhau ra sao? Vì sao không nên dùng GET để xóa dữ liệu?

    GET dùng để đọc dữ liệu: tham số nằm trên URL, có thể được cache, lưu lịch sử và được trình duyệt hay công cụ quét tự gọi lại (prefetch, xem trước liên kết). POST dùng để gửi dữ liệu làm thay đổi trạng thái, dữ liệu nằm trong body.

    GET phải an toàn (không thay đổi gì) và idempotent (gọi nhiều lần cũng như một lần). Nếu một link GET xóa dữ liệu, chỉ cần một bot quét link hay trình duyệt prefetch là dữ liệu mất.

    Mở trang riêng của câu hỏi
  29. JuniorJavaScriptBreakpoint khác console.log thế nào khi debug? Khi nào bạn vẫn dùng console.log?

    Breakpoint dừng chương trình ở một dòng, cho bạn xem mọi biến đang có giá trị gì và kiểu gì, xem ai đã gọi hàm (call stack), và gõ thêm biểu thức để tính ngay trong ngữ cảnh đó. console.log chỉ in ra thứ bạn đoán trước là cần in.

    Một ví dụ cho thấy khác biệt: giá món ăn được lưu nhầm thành chuỗi "35.000". console.log(item.price) in ra 35.000, nhìn y như ba mươi lăm nghìn. Trong khung Variables của debugger, giá trị hiện kèm dấu nháy, và typeof trả về 'string'. Lỗi lộ ra ngay: "35.000" * 2 bằng 70 vì JavaScript đọc dấu chấm là dấu thập phân.

    Các công cụ nên kể được: breakpoint có điều kiện (chỉ dừng khi order.id === 1044), Watch, Debug Console, Call Stack, và logpoint (in log mà không dừng, không sửa code).

    console.log vẫn hợp lý khi:

    • viết script nhỏ chạy một lần;
    • lỗi phụ thuộc thời gian, việc dừng chương trình làm đổi thứ tự chạy;
    • trên production, nơi không gắn debugger được, và lúc đó nên ghi log có cấu trúc.

    Điểm cộng: biết log bằng JSON.stringify(value) hoặc { value } để thấy rõ chuỗi hay số.

    Mở trang riêng của câu hỏi
  30. JuniorJavaScriptKhác nhau giữa var, let và const trong JavaScript?
    • var có phạm vi hàm, bị hoisting (khai báo được đưa lên đầu hàm với giá trị undefined) và cho khai báo lại.
    • let có phạm vi khối { }, không cho khai báo lại trong cùng khối, và không dùng được trước dòng khai báo.
    • const giống let nhưng không gán lại được. Lưu ý: object hay mảng khai báo bằng const vẫn sửa được bên trong.
    const không có nghĩa là bất biến
    const user = { name: "An" };
    user.name = "Bình"; // được
    user = {};          // TypeError

    Thói quen tốt: mặc định dùng const, chỉ dùng let khi thật sự cần gán lại, và tránh var trong code mới.

    Mở trang riêng của câu hỏi
  31. JuniorJavaScriptStack trace là gì? Khi gặp "Cannot read properties of undefined", bạn tìm nguyên nhân thế nào?

    Stack trace là danh sách các hàm đang được gọi dở tại thời điểm lỗi xảy ra, kèm tên file, số dòng và số cột của từng lời gọi. Trong JavaScript, khung trên cùng là nơi lỗi xảy ra; các khung bên dưới là đường đi dẫn tới đó.

    Với lỗi TypeError: Cannot read properties of undefined (reading 'name'), một câu trả lời tốt đi theo các bước:

    1. Đọc thông điệp: thuộc tính đang đọc là name, nên thứ đứng ngay trước .name đang là undefined.
    2. Tìm khung đầu tiên thuộc code của mình: bỏ qua các khung trong node_modules hay node:internal, mở đúng file, dòng và cột.
    3. Lần ngược nguồn dữ liệu: dòng báo lỗi là nơi lỗi lộ ra, chưa chắc là nơi bắt đầu. Đi xuống các khung để xem giá trị undefined được truyền từ đâu: API chưa trả về, state ban đầu rỗng, hay dữ liệu thiếu trường.
    4. Tái hiện bằng ví dụ nhỏ nhất, rồi mới sửa đúng chỗ: sửa dữ liệu ở nguồn, hoặc xử lý trường hợp hợp lệ bằng giá trị mặc định hay ?..

    Điểm cộng nếu bạn nhắc thêm: khi bắt lỗi rồi ném lại, dùng new Error("…", { cause: err }) để không mất lỗi gốc; còn với code production đã nén, cần source map mới đọc được stack trace.

    Mở trang riêng của câu hỏi
  32. JuniorKỹ năng lập trìnhBạn sẽ viết README thế nào để một người mới vào dự án có thể chạy được nó, và bạn kiểm tra điều đó ra sao?

    Người phỏng vấn muốn xem bạn có nghĩ về người đọc sau mình không, và bạn có biết cách kiểm tra tài liệu thay vì tin rằng nó đúng.

    Một README đủ dùng trả lời bốn câu theo thứ tự: cần có gì trên máy (phiên bản Node, công cụ khác, kèm lý do), gõ lệnh nào để chạy lần đầu, thấy gì thì biết là chạy đúng, và gặp lỗi phổ biến thì làm gì. Phần "thấy gì là đúng" hay bị thiếu nhất, nhưng thiếu nó thì người mới không phân biệt được "đang chạy" với "chạy sai mà không báo". Phần "gặp lỗi" nên viết từ lỗi có thật bạn đã gặp khi chạy thử, không đoán.

    Hai thủ thuật đáng nhắc. Thứ nhất, nếu một bước là "mở file này, sao chép, điền giá trị", hãy thay bằng một lệnh làm giúp (ví dụ npm run setup tạo .env và điền khóa ngẫu nhiên), vì ít bước làm tay thì ít chỗ sai. Thứ hai, đừng chép lại thứ có nguồn khác: danh sách biến môi trường nên trỏ tới .env.example, vì hai nơi cùng liệt kê một thứ sớm muộn sẽ lệch nhau.

    Cách kiểm tra là làm theo README trong một thư mục trống, như vừa clone (không có node_modules, không có .env), tốt nhất bởi một người chưa biết dự án. Bạn cũng có thể viết script chạy các lệnh trong README và báo bước nào hỏng, nhưng script chỉ trả lời được "máy có làm theo được không", không trả lời được "người có hiểu không".

    Điểm cộng: nói được README sẽ cũ đi khi code đổi, và bạn có thể kiểm tự động việc đường dẫn và tên script nó nhắc tới còn tồn tại.

    Mở trang riêng của câu hỏi
  33. JuniorKỹ năng lập trìnhKhi nào ghi log ở mức warn, khi nào ở mức error, và không nên ghi gì vào log?

    Người phỏng vấn muốn biết bạn có xem log như công cụ để người đến sau tìm nguyên nhân, và có biết phân loại sự kiện để lỗi thật không bị chìm hay không.

    Cách phân loại đơn giản và dễ nhớ là hỏi "lỗi của ai". Mức info là chuyện bình thường, ví dụ một đơn hàng được nhận. Mức warn là người gọi gửi sai, như request bị từ chối vì dữ liệu không hợp lệ (mã 4xx): máy chủ làm đúng việc của nó. Mức error là máy chủ làm sai hoặc gặp tình huống không lường trước (mã 5xx), và cần có người xem.

    Lý do phải tách: nếu mọi thứ đều ghi error, một website gửi sai năm trăm đơn sẽ tạo năm trăm dòng đỏ che mất một lỗi thật duy nhất của máy chủ. Lọc theo error lúc hai giờ sáng chỉ hữu ích khi mức error có ý nghĩa.

    Một dòng log tốt trả lời được bốn câu: chuyện gì, lúc nào, của request nào, và quan trọng đến đâu. Vì vậy mỗi request nên có một mã riêng (request id) gắn vào mọi dòng của nó. Nếu bạn bọc một lỗi bằng lỗi khác, hãy giữ lỗi gốc trong cause để log vẫn nói được nguyên nhân.

    Về những gì không nên ghi: dữ liệu cá nhân như tên, số điện thoại, và bí mật như mật khẩu, token, khóa API. File log hay được chép sang nhiều nơi, nên thứ gì đã vào log coi như đã lộ ra ngoài dự án. Khi từ chối một request vì dữ liệu sai, hãy ghi tên trường và lý do, không ghi giá trị người dùng gửi lên.

    Điểm cộng: nhắc rằng không nên nuốt lỗi bằng khối catch rỗng, và khi trả lỗi 500 cho người gọi thì chỉ đưa một mã để tra, còn chi tiết nội bộ ở lại trong log.

    Mở trang riêng của câu hỏi
  34. JuniorKỹ năng lập trìnhRefactor là gì, làm sao chắc rằng sau khi refactor code vẫn chạy y như cũ?

    Người phỏng vấn muốn biết bạn có phân biệt được sửa cấu trúc với sửa hành vi không, và bạn có thói quen tự bảo vệ mình bằng test không. Refactor là thay đổi cấu trúc bên trong của code để dễ đọc, dễ sửa hơn mà không đổi hành vi bên ngoài. Đổi tên biến, tách một hàm dài thành các hàm nhỏ, gom đoạn code lặp vào một chỗ đều là refactor. Thêm tính năng hay sửa lỗi thì không phải, vì chúng đổi hành vi.

    Cách trả lời tốt đi theo thứ tự này:

    1. Có test trước khi động vào code. Nếu code chưa có test, viết test đặc tả: một test chụp lại code đang làm gì, kể cả khi hành vi đó hơi kỳ. Nó không hỏi "đúng hay sai", chỉ hỏi "sau khi sửa có khác trước không".
    2. Đi từng bước nhỏ, chạy test sau mỗi bước. Test đỏ thì biết ngay bước nào vừa gây ra, vì chỉ có một thay đổi nhỏ.
    3. Không trộn refactor với sửa hành vi. Nếu thấy một lỗi giữa chừng, ghi lại và sửa ở commit riêng. Tách commit đổi tên khỏi commit di chuyển code cũng giúp người review đọc nhanh hơn.
    4. Biết cái gì là "tên bên trong", cái gì là "tên bên ngoài". Đổi tên một biến cục bộ thì an toàn. Đổi tên một trường trong JSON mà API trả về thì làm hỏng mọi nơi đang đọc trường đó, dù code của bạn vẫn biên dịch được.

    Ví dụ cụ thể giúp câu trả lời thuyết phục hơn. Chẳng hạn bạn đang dọn một máy chủ nhỏ trả danh sách đơn hàng. Bạn đổi trường khach thành customer cho nhất quán với phần còn lại, và test đặc tả kiểm tra phản hồi của máy chủ lập tức đỏ, vì trang quản lý đang đọc đúng trường khach. Không có test đó thì lỗi chỉ lộ ra khi người dùng thấy cột trống.

    Điểm cộng: nói rõ khi nào bạn không refactor, ví dụ khi sắp có deadline mà code đó không phải chỗ đang sửa, hoặc khi chưa thể viết test cho nó. Refactor đúng lúc, trong phạm vi nhỏ, hợp với một pull request dễ review hơn là dọn cả dự án trong một lần.

    Mở trang riêng của câu hỏi
  35. JuniorNext.jsISR trong Next.js là gì, và khác gì so với trang tĩnh hoặc render ở mỗi request?

    Người phỏng vấn muốn biết bạn hiểu ba cách dựng trang và biết chọn cách phù hợp với từng loại nội dung, không chỉ thuộc tên viết tắt.

    Có ba cách. Trang tĩnh (SSG) được dựng một lần lúc build và giữ nguyên, nên rất nhanh nhưng chỉ đổi khi deploy lại. Render ở mỗi request (SSR, hay dynamic) dựng trang mới mỗi lần có người xem, nên luôn mới nhưng mỗi lượt xem đều tốn một lần xử lý và truy vấn dữ liệu. ISR (incremental static regeneration) nằm giữa: trang được lưu như trang tĩnh, và Next.js dựng lại nó ở nền theo một trong hai cách.

    1. Theo thời gian. Đặt export const revalidate = 60 thì sau 60 giây, request kế tiếp vẫn nhận bản cũ nhưng kích hoạt việc dựng lại. Khi bản mới xong, các request sau nhận bản mới. Nghĩa là người đọc không bao giờ phải chờ dữ liệu.
    2. Theo yêu cầu. Khi nội dung đổi, code gọi revalidatePath hoặc revalidateTag để đánh dấu cache là hết hạn, và lần truy cập kế tiếp dựng lại.

    Chọn thế nào: nội dung ít đổi mà nhiều người đọc, như bài blog hay trang sản phẩm, hợp với ISR. Nội dung riêng cho từng người dùng, như trang tài khoản, cần render ở mỗi request. Nội dung không bao giờ đổi, như trang giới thiệu, hợp với trang tĩnh.

    Hai điểm nên nêu thêm. Một là đánh đổi: ISR cho phép người đọc thấy bản cũ trong một khoảng ngắn, nên nó không hợp với dữ liệu phải chính xác từng giây như tồn kho hay số dư. Hai là nếu dùng route để làm mới theo yêu cầu thì phải bảo vệ nó bằng một mã bí mật, nếu không ai cũng gọi được và buộc máy chủ dựng lại liên tục.

    Điểm cộng: nói được ISR cần một máy chủ Node.js chạy được, nên không dùng được khi xuất blog thành file HTML tĩnh hoàn toàn.

    Mở trang riêng của câu hỏi
  36. JuniorNext.jsSitemap, thẻ canonical và JSON-LD dùng để làm gì trong SEO kỹ thuật?

    Người phỏng vấn muốn biết bạn hiểu mỗi thành phần giải quyết vấn đề gì, thay vì chỉ biết tên và chép cấu hình.

    Sơ đồ trang (sitemap) là một file XML liệt kê các địa chỉ trong site, giúp công cụ tìm kiếm tìm thấy trang mà không cần đi theo từng liên kết. Hướng dẫn của Google nói họ bỏ qua priority và changefreq, và chỉ dùng lastmod khi giá trị đó luôn khớp với lần sửa thật của trang. Mọi giá trị trong file phải được escape ký tự đặc biệt của XML.

    Thẻ canonical nói địa chỉ nào là bản chính của một nội dung. Khi cùng một bài có thể truy cập bằng nhiều địa chỉ (có tham số theo dõi, có hay không dấu gạch chéo cuối), canonical giúp công cụ tìm kiếm gom chúng về một. Với trang có nhiều ngôn ngữ, mỗi bản trỏ canonical về chính nó và liệt kê các bản kia bằng hreflang.

    JSON-LD là khối dữ liệu có cấu trúc (từ vựng schema.org), nói rõ trang là một bài viết, tác giả là ai, đăng ngày nào. Khi nhúng vào thẻ script, cần thay ký tự < bằng <, vì JSON.stringify không làm sạch chuỗi và một tiêu đề chứa </script> có thể thoát khỏi thẻ.

    Một lưu ý thực tế: không thành phần nào bảo đảm lên top, chúng chỉ giúp công cụ tìm kiếm hiểu đúng trang. Môi trường xem trước (preview) cần noindex, và chỉ production mới được index.

    Điểm cộng: nhắc rằng sitemap cũng có thể sai theo cách khó thấy, ví dụ bị phục vụ như file tĩnh nên không cập nhật khi đăng bài mới, và nên có cách kiểm tra việc đó trên môi trường thật.

    Mở trang riêng của câu hỏi
  37. JuniorNext.jsTrong Next.js App Router, nhóm route (folder) và thư mục riêng tư _folder khác nhau thế nào? Khi nào dùng cái nào?

    Cả hai đều là quy ước đặt tên thư mục trong app, nhưng giải quyết hai vấn đề khác nhau.

    Nhóm route là thư mục có tên trong ngoặc tròn, như (site) hay (shop). Next.js dùng nó để gom các trang lại nhưng không đưa tên nhóm vào URL: app/(site)/bai-viet/page.tsx vẫn là /bai-viet. Công dụng chính là cho một nhóm trang dùng chung một layout riêng. Ví dụ, trang công khai của blog cần header, footer và được tạo sẵn, còn trang quản trị cần khung riêng và tạo mới ở mọi lượt truy cập. Đặt trang công khai vào (site) có layout riêng thì hai bên tách bạch mà URL không đổi.

    Thư mục riêng tư có tên bắt đầu bằng gạch dưới, như _components. Next.js bỏ qua thư mục này và mọi thư mục con khi tạo đường dẫn, nên nó dùng để đặt code không phải trang (component, hàm phụ) ngay cạnh trang.

    Một điểm hay bị hỏi thêm: ngay cả không có gạch dưới, file trong app cũng không thành trang nếu không phải page hoặc route. Vì vậy _folder chủ yếu để nói rõ ý đồ và tránh trùng tên với các file đặc biệt của Next.js.

    Điểm cộng: nhắc tới việc hai layout có thể cùng ứng với / (layout gốc và layout của nhóm) nhưng bọc phạm vi khác nhau.

    Mở trang riêng của câu hỏi
  38. JuniorNode.jsBạn quản lý cấu hình và bí mật của một ứng dụng Node.js thế nào để dự án chạy giống nhau trên mọi máy?

    Người phỏng vấn muốn biết bạn phân biệt được code với cấu hình, biết giữ bí mật ngoài Git, và đã từng gặp những lỗi kiểu "máy em chạy mà".

    Nguyên tắc cơ bản: mọi thứ thay đổi giữa các môi trường (cổng, đường dẫn, khóa API, chuỗi kết nối) nằm ngoài code, và code đọc chúng từ biến môi trường qua process.env. Hai điều cần nhớ là giá trị trong process.env luôn là chuỗi, và biến có thể không tồn tại. Từ đó sinh ra các lỗi quen thuộc: Number("abc") ra NaN, biến đặt rỗng được Number("") đổi thành 0, và Boolean("false") lại là true.

    Cách làm tốt là gom việc đọc cấu hình vào một chỗ, kiểm tra từng biến bằng một thư viện schema như Zod, đổi sang đúng kiểu, và nếu sai thì dừng ngay lúc khởi động với thông báo nói rõ biến nào sai. Bí mật thì không có giá trị mặc định, vì mặc định kiểu "changeme" chẳng khác gì để cửa mở.

    Về file: dùng .env cho máy phát triển và nạp bằng node --env-file=.env (hoặc thư viện dotenv với Node cũ), thêm .env vào .gitignore, và commit một file .env.example liệt kê mọi biến để người mới biết cần điền gì. Trên máy chủ thật, đặt biến trong hệ thống deploy hoặc kho bí mật của CI, không dùng file.

    Điểm cộng: nhắc rằng giá trị đặt trong terminal thắng giá trị trong file .env, nên một biến bạn đã export từ hôm trước có thể âm thầm lờ đi file, và nói được nếu lỡ commit một khóa thật thì việc đầu tiên là thu hồi nó.

    Mở trang riêng của câu hỏi
  39. JuniorPhỏng vấnTrong buổi phỏng vấn live coding, bạn làm gì trước khi viết dòng code đầu tiên?

    Người phỏng vấn hỏi câu này để xem bạn có quy trình hay chỉ gõ theo phản xạ. Câu trả lời tốt cho thấy bạn dành vài phút đầu để hiểu đề, vì đó là phần rẻ nhất của cả buổi.

    Bạn có thể trả lời theo ba bước:

    1. Nhắc lại đề bằng lời của mình và hỏi những chỗ mơ hồ. Ví dụ với đề "đếm ghi chú xuất hiện nhiều nhất": "Chữ hoa và chữ thường có tính là một không? Hai ghi chú bằng số lần thì xếp thế nào? Mảng có thể rỗng hay có phần tử trống không?" Câu hỏi của bạn cho thấy bạn nhìn ra được chỗ đề chưa rõ, và câu trả lời chốt luôn các luật của bài.
    2. Đi qua một ví dụ bằng tay, vừa đi vừa nói. Cách này thường làm lộ ra một luật mà bạn chưa xử lý, trước khi bạn viết một dòng code.
    3. Nói cách làm trước khi viết. Một hai câu, ví dụ "em sẽ chuẩn hóa mỗi ghi chú, đếm bằng một Map rồi sắp xếp giảm dần". Nếu hướng đó lệch, người phỏng vấn có cơ hội chỉnh bạn trước khi bạn mất mười phút.

    Nên nêu thêm lý do: hiểu sai đề thì phần code phía sau là công cốc, và người phỏng vấn đánh giá cách bạn nghĩ chứ không chỉ kết quả. Nếu bạn bí thì nói ra mình đang kẹt ở đâu và thử hướng nào, thay vì im lặng.

    Điểm cộng: kể một lỗi mà bước đi qua ví dụ bằng tay đã giúp bạn bắt được sớm, chẳng hạn code so sánh chuỗi nguyên văn nên "Cà phê " và "cà phê" bị đếm thành hai ghi chú.

    Mở trang riêng của câu hỏi
  40. JuniorSự nghiệpBạn ước lượng thời gian cho một task thế nào, và bạn làm gì khi nhận ra mình sẽ trễ?

    Người phỏng vấn muốn biết bạn có thói quen ước lượng có căn cứ và có dám nói sớm khi việc chạy chậm hay không. Họ không mong bạn ước lượng chính xác; họ mong bạn đáng tin.

    Về ước lượng, hãy nói rằng bạn chia task thành các việc nhỏ (gồm cả việc ngoài code như đọc code cũ, viết test, sửa theo review, chờ người khác), ước lượng từng việc, rồi trả lời bằng một khoảng thay vì một con số. Nếu bạn có ghi lại lịch sử ước lượng và thực tế của mình, bạn dùng nó để hiệu chỉnh: ví dụ nếu các việc trước tốn gấp 1,3 đến 1,8 lần dự đoán, bạn nhân lên tương ứng. Bạn cũng nên nói thẳng rằng ước lượng là dự đoán chứ không phải lời hứa, và bạn cập nhật nó khi có thông tin mới.

    Về chuyện trễ, quy tắc quan trọng nhất là báo sớm. Cuối mỗi ngày bạn ước lượng lại phần còn lại, cộng với giờ đã dùng, và nếu dự kiến lệch nhiều so với ban đầu (nhóm có thể đặt ngưỡng, ví dụ 25%) thì báo ngay hôm đó, không đợi tới hạn hay đợi bị hỏi. Một bản báo tốt có năm phần: đã xong gì, đang làm gì, vướng ở đâu, dự kiến xong vào ngày cụ thể nào, và khi nào sẽ báo lại. Câu "sắp xong rồi" không cho người nghe gì để hành động.

    Điểm cộng: kể một ví dụ thật bạn từng ước lượng sai và bài học, và nói rằng báo sớm cho người khác thời gian sắp xếp lại và cho bạn cơ hội xin giúp đỡ.

    Mở trang riêng của câu hỏi
  41. JuniorSự nghiệpBạn xin feedback từ người đi trước như thế nào, và bạn làm gì sau khi nhận được?

    Người phỏng vấn muốn biết bạn có chủ động học từ người khác không, và bạn có làm cho việc cho feedback trở nên dễ dàng hay chỉ chờ người ta tự nói.

    Một câu trả lời tốt cho thấy bạn xin feedback có chuẩn bị. Thay vì hỏi "anh thấy em làm thế nào", vốn chỉ nhận lại chữ "ổn", bạn gửi một việc cụ thể đã xong kèm đường dẫn, hỏi một hai điều bạn thật sự chưa chắc, nói rõ người kia cần bỏ khoảng bao lâu và trả lời bằng cách nào cho tiện (ví dụ comment thẳng vào pull request). Làm vậy là giảm công sức của người trả lời xuống mức họ làm được ngay trong một khoảng nghỉ.

    Phần quan trọng không kém là sau khi nhận feedback. Bạn nghe mà không biện minh, hỏi lại khi chưa hiểu, ghi xuống trong vòng vài phút, biến nó thành việc cụ thể có hạn, làm xong rồi báo lại cho người đã góp ý kèm kết quả. Người đó thấy nhận xét của mình có tác dụng thì lần sau sẵn lòng góp ý tiếp.

    Điểm cộng là cho một ví dụ thật từ dự án hoặc thực tập của bạn, và thừa nhận rằng không phải ai cũng phản hồi nhanh, nên bạn biết thử một câu hỏi hẹp hơn hoặc hỏi một người khác. Nên tránh trả lời rằng bạn thường chờ đến buổi đánh giá cuối kỳ.

    Mở trang riêng của câu hỏi
  42. JuniorSự nghiệpCâu hỏi hành vi là gì, và phương pháp STAR giúp trả lời nó thế nào?

    Người phỏng vấn muốn biết bạn có kể được một chuyện thật, có cấu trúc, và nói rõ chính bạn đã làm gì, thay vì trả lời chung chung.

    Câu hỏi hành vi là câu hỏi dạng "kể về một lần bạn…": làm việc nhóm gặp mâu thuẫn, gặp lỗi khó, mắc sai lầm. Người hỏi muốn dùng hành vi trong quá khứ để đoán cách bạn sẽ làm việc sau này. Không có đáp án đúng để kiểm tra bằng test, nên cách bạn kể chuyện quyết định ấn tượng.

    STAR là khung bốn phần để kể:

    1. Situation (Tình huống): bối cảnh ngắn, đủ để hiểu chuyện gì đang xảy ra.
    2. Task (Nhiệm vụ): trách nhiệm của bạn trong tình huống, tức vì sao chuyện này là việc của bạn.
    3. Action (Hành động): những bước cụ thể bạn đã làm, dùng "tôi" hay "em" thay vì "chúng tôi", vì người nghe đang đánh giá bạn.
    4. Result (Kết quả): chuyện xảy ra sau đó, tốt nhất đo được bằng con số, kèm điều bạn rút ra.

    Lỗi hay gặp là kể quá dài phần bối cảnh rồi nói về Hành động quá ngắn, nói về cả nhóm thay vì phần của mình, và bỏ quên Kết quả hoặc nêu một kết quả mơ hồ như "mọi chuyện ổn hơn". Một cách chuẩn bị tốt là có sẵn khoảng ba chuyện thật, mỗi chuyện ghi bốn dòng STAR, rồi chọn chuyện gần nhất với câu hỏi thay vì nghĩ chuyện mới.

    Ý quan trọng để nêu: đừng học thuộc câu chữ, và đừng bịa số liệu. Bạn chỉ cần nhớ bốn dòng của mỗi chuyện, nói thành lời mỗi lần một cách khác, và dùng con số bạn thật sự có.

    Điểm cộng: nhắc rằng với câu hỏi về điểm yếu hay sai lầm, bạn kể một việc thật cùng một việc bạn đã làm để sửa, thay vì một "điểm yếu" nghe an toàn như "em quá cầu toàn".

    Mở trang riêng của câu hỏi
  43. JuniorSự nghiệpKhi bị kẹt ở một lỗi, bạn tự thử bao lâu trước khi hỏi người đi trước, và bạn hỏi thế nào?

    Người phỏng vấn muốn biết bạn có cân bằng được giữa tự lực và tôn trọng thời gian của đồng nghiệp: không hỏi vội khi chưa thử gì, cũng không im lặng cả buổi vì ngại.

    Một câu trả lời tốt cho thấy bạn có quy tắc chứ không làm theo cảm giác. Ví dụ: với yêu cầu chưa rõ thì hỏi ngay trước khi làm, vì làm sai hướng tốn hơn nhiều so với một câu hỏi. Với lỗi kỹ thuật thì tự thử trong một khoảng thời gian định trước (nhiều người chọn khoảng ba mươi phút, và con số này tùy nhóm), hoặc sớm hơn nếu đã thử hai hướng khác nhau mà đều không ra. Những thứ tra được trong vài phút ở tài liệu của dự án thì tra trước.

    Về cách hỏi, hãy gửi một tin nhắn đủ để người đọc trả lời ngay mà không phải hỏi lại: bạn đang làm gì, lỗi hoặc kết quả dán nguyên văn dạng chữ, bạn mong đợi gì và thực tế ra sao, ít nhất hai việc bạn đã thử kèm kết quả, môi trường (hệ điều hành, phiên bản, nhánh hoặc commit), và một câu hỏi cụ thể ở cuối. Nếu có giả thuyết, hãy nêu luôn.

    Sau khi có câu trả lời, nên quay lại báo kết quả, ghi lại điều đã học, và nếu thứ bạn vấp là chỗ tài liệu còn thiếu thì thêm nó vào tài liệu để người sau không vấp nữa.

    Điểm cộng: kể một ví dụ cụ thể về một lần bạn đã hỏi tốt, và nói được cách bạn biết văn hóa hỏi của nhóm mới, như hỏi trực tiếp người hướng dẫn xem họ thích hỏi qua kênh nào.

    Mở trang riêng của câu hỏi
  44. JuniorSự nghiệpKhi làm việc từ xa và người hướng dẫn chưa trả lời, bạn làm gì, và bạn viết tin nhắn nhờ việc thế nào?

    Người phỏng vấn muốn biết bạn có làm việc được khi không có ai ngồi cạnh: không bị dừng lại vì chờ, nhưng cũng không tự quyết những việc không thuộc phần mình.

    Một câu trả lời tốt bắt đầu từ thói quen viết. Tin nhắn nhờ việc cần tự đứng được, vì người đọc có thể mở nó sau vài giờ và ở múi giờ khác. Nên nêu luôn việc cần trong tin đầu thay vì hỏi "anh rảnh chưa", đính kèm đường dẫn tới pull request hay log, nói bạn đã thử gì và kết quả ra sao, đặt một câu hỏi cụ thể (tốt nhất là chọn giữa hai hướng), và ghi hạn bằng giờ cụ thể kèm múi giờ thay vì "chiều nay" hay "cuối ngày".

    Phần mà nhiều người bỏ quên là nói trước mình sẽ làm gì trong lúc chờ và làm gì nếu hết hạn mà chưa có trả lời. Điều đó giúp bạn không ngồi chờ, và người trả lời không bị áp lực vì biết nếu họ chưa kịp xem thì không ai bị kẹt, còn nếu họ không đồng ý thì biết phải lên tiếng trước giờ nào. Việc nào bạn không có quyền tự quyết thì mặc định nên là bản dễ lùi nhất, và được ghi rõ để người có thẩm quyền xem lại.

    Ngoài ra, chủ động cho người khác biết mình đang ở đâu bằng một cập nhật ngắn cuối ngày, và nếu một chủ đề đã qua lại vài lượt mà vẫn chưa hiểu nhau thì chuyển sang một cuộc gọi ngắn rồi ghi kết luận lại bằng chữ.

    Điểm cộng: kể một ví dụ cụ thể về một lần bạn viết tin nhắn tốt và nó giúp tiết kiệm thời gian, và nhắc rằng mỗi nhóm có quy ước riêng nên bạn sẽ hỏi quy ước đó trong tuần đầu.

    Mở trang riêng của câu hỏi
  45. JuniorSự nghiệpMức lương bạn mong muốn là bao nhiêu, và bạn trả lời thế nào khi offer thấp hơn kỳ vọng?

    Người phỏng vấn muốn biết bạn có chuẩn bị, có hiểu giá trị của mình, và biết nói chuyện lương một cách bình tĩnh, không né tránh cũng không đòi vô lý.

    Trước khi trả lời, bạn cần làm bài tập về nhà. Nếu tin tuyển dụng đã ghi khoảng lương thì đó là dữ liệu tốt nhất: bạn biết công ty sẵn sàng trả trong khoảng nào. Nếu không, hãy tham khảo mức phổ biến cho vị trí, cấp độ và địa điểm của bạn từ các báo cáo lương và tin tuyển dụng tương tự. Công ty thường xây lương theo khoảng và theo bậc, nên có giới hạn mà người tuyển dụng không vượt được.

    Khi được hỏi, bạn nên nêu một khoảng có cơ sở, gắn với những gì bạn mang lại (dự án, test, kỹ năng khớp với yêu cầu), thay vì một con số trơn hay lý do cá nhân như tiền trọ. Nếu chưa muốn nói con số, có thể hỏi lại khoảng lương của vị trí rồi trả lời trong khoảng đó.

    Khi nhận offer thấp hơn kỳ vọng, đừng nhận hay từ chối ngay trong cuộc gọi. Cảm ơn, nói rõ bạn quan tâm, xin thư mời bằng văn bản và một hai ngày để cân nhắc. Đọc kỹ các mục như lương chính thức, phần trăm lương thử việc, ngày kết thúc thử việc, lương đóng bảo hiểm, thưởng và ngày xem xét lại lương, rồi phản hồi bằng email lịch sự với đề nghị cụ thể và lý do. Ở Việt Nam, lương thử việc phải ít nhất bằng 85% lương của công việc đó theo Bộ luật Lao động 2019.

    Nếu công ty không tăng được lương, bạn có thể hỏi thêm về những thứ khác như ngày xem xét lại lương ghi bằng văn bản, lịch học việc, hay số ngày làm tại nhà.

    Điểm cộng: nhắc rằng so sánh các phương án bằng tổng thu nhập năm đầu giúp thấy điều nào đáng đề nghị, và rằng thương lượng lịch sự thường là chuyện bình thường trong tuyển dụng.

    Mở trang riêng của câu hỏi
  46. JuniorSự nghiệpNếu được nhận, tuần đầu đi làm bạn sẽ làm những gì?

    Người phỏng vấn muốn biết bạn có kế hoạch thực tế cho giai đoạn đầu, biết ưu tiên học cách nhóm làm việc, và không hứa những điều không thể.

    Một câu trả lời tốt chia thành vài mục tiêu cụ thể, thay vì nói chung chung kiểu "em sẽ học hỏi nhiều".

    Thứ nhất, chạy được dự án trên máy của mình trong vài ngày đầu, và ghi lại những chỗ tài liệu hướng dẫn thiếu hoặc sai. Một người mới thấy những chỗ vấp mà người cũ không còn nhìn ra, và sửa chúng có ích cho cả nhóm.

    Thứ hai, hiểu cách nhóm làm việc: quy ước đặt tên nhánh, pull request cần mấy người duyệt, kiểm tra tự động nào phải xanh, ai review. Cách nhanh là đọc các pull request đã đóng gần đây.

    Thứ ba, nhận một việc nhỏ đầu tiên, như sửa tài liệu hay một lỗi nhỏ đã được chọn sẵn, để học quy trình mà không có áp lực của một bài toán khó.

    Thứ tư, hẹn người hướng dẫn một buổi góp ý giữa kỳ và hỏi họ mong đợi gì ở mình ở các mốc của thời gian thử việc. Bạn cũng có thể nói mình giữ một cuốn nhật ký ngắn cuối ngày để ghi điều chưa hiểu và điều đã thử trước khi hỏi.

    Điểm cộng: nhắc rằng thời gian thử việc tính theo ngày lịch nhưng số ngày làm việc ít hơn, nên mục tiêu cần đúng cỡ, và bạn chưa cần biết hết mã nguồn trong tuần đầu.

    Mở trang riêng của câu hỏi
  47. JuniorTestingUnit test là gì? Làm sao biết một test bạn viết thật sự có tác dụng?

    Unit test kiểm tra một phần nhỏ của code (một hàm, một module) một cách tự động: với đầu vào này, kết quả phải là kia. Nó chạy nhanh, không cần bật máy chủ hay database, nên có thể chạy ở mỗi lần lưu file và trước mỗi lần merge.

    Phần thứ hai của câu hỏi mới là phần người phỏng vấn muốn nghe. Một test có tác dụng khi nó đỏ lúc code sai. Vài dấu hiệu nên nêu:

    • Kết quả mong đợi đến từ bên ngoài code. Viết expect(orderTotal(don)).toBe(70000) vì bạn biết 2 ly × 35.000 là 70.000. Đừng tính lại kết quả mong đợi bằng đúng công thức đang được kiểm tra, vì khi đó test so sánh code với chính nó và luôn xanh.
    • Thử làm hỏng code. Sau khi sửa một lỗi, tạm bỏ bản sửa và chạy lại test. Test vẫn xanh nghĩa là nó chưa kiểm tra đúng chỗ.
    • Có cả trường hợp bị từ chối, không chỉ trường hợp chạy đúng. Ví dụ một hàm đọc giá phải báo lỗi với "35,000" thay vì đoán.
    • Tên test kể được chuyện gì đang được bảo vệ, như "2 ly cà phê muối giá 35.000 là 70.000 đồng", để người làm đỏ test hiểu ngay mà không cần hỏi.

    Điểm cộng: nói rằng test cho từng hàm nhỏ là chưa đủ. Nên có thêm test đi qua cả đường dữ liệu thật (đọc dữ liệu thô → chuẩn hóa → tính tổng), vì lỗi hay nằm ở chỗ một hàm đúng nhưng không được gọi.

    Mở trang riêng của câu hỏi
  48. JuniorTypeScriptVì sao phải kiểm tra dữ liệu đầu vào ở phía server dù TypeScript đã có kiểu?

    Người phỏng vấn muốn biết bạn có phân biệt được kiểu lúc biên dịch với dữ liệu thật lúc chạy, và có nghĩ tới việc dữ liệu từ bên ngoài không đáng tin hay không.

    Kiểu TypeScript chỉ tồn tại khi bạn viết và biên dịch code. Khi chương trình chạy, kiểu đã bị xóa, nên JSON.parse(body) trả về bất cứ thứ gì người gửi đưa vào, và việc viết as Order chỉ là bạn tự hứa với trình biên dịch, không kiểm tra gì cả. Dữ liệu từ request, từ file, từ API khác đều là dữ liệu không tin cậy cho tới khi được kiểm tra.

    Hậu quả của việc không kiểm tra:

    1. Lỗi lặng lẽ. Số lượng là chữ làm phép nhân ra NaN, rồi thành null khi chuyển sang JSON, và tổng tiền sai mà không có lỗi nào.
    2. Lỗi lan rộng. Một bản ghi thiếu trường làm hàm xử lý ném lỗi, và nếu hàm đó chạy trên cả danh sách thì một dữ liệu hỏng làm hỏng mọi người.
    3. Lỗ hổng bảo mật. Người gửi có thể đặt cả những trường họ không được phép đặt, như id hay quyền hạn.

    Cách làm đúng là kiểm tra ngay ở nơi dữ liệu đi vào hệ thống, bằng một thư viện schema như Zod: khai báo hình dạng hợp lệ, gọi safeParse, trả lỗi 400 kèm danh sách lỗi theo từng trường nếu sai. Sau bước đó, phần còn lại của code dùng kiểu do schema suy ra và tin vào nó. Nên khai báo dữ liệu nhận về là unknown thay vì any, để trình biên dịch buộc bạn kiểm tra trước khi dùng.

    Ý quan trọng để nêu: kiểm tra một lần ở cửa, không rải khắp nơi, và phân biệt lỗi do người gửi (400) với lỗi của máy chủ (500).

    Điểm cộng: nhắc tới việc giới hạn kích thước nội dung gửi lên, và chọn có cho phép trường lạ hay không (từ chối hẳn thường an toàn hơn lặng lẽ bỏ qua).

    Mở trang riêng của câu hỏi
  49. JuniorWebVì sao nên nén ảnh trước khi lưu, và nén ở trình duyệt hay ở máy chủ?

    Người phỏng vấn muốn biết bạn hiểu ảnh ảnh hưởng thế nào đến dung lượng lưu trữ và tốc độ tải trang, và có nghĩ tới các chi tiết dễ sai khi xử lý ảnh hay không.

    Lý do chính là ảnh gốc từ điện thoại hay ảnh chụp màn hình thường nặng vài MB đến hàng chục MB, trong khi trang chỉ hiển thị chúng ở chiều rộng vài trăm điểm ảnh. Lưu nguyên bản làm đầy kho lưu trữ nhanh (gói miễn phí thường chỉ có vài trăm MB đến vài GB) và bắt người đọc trên mạng di động tải một lượng dữ liệu họ không cần. Phần giảm nhiều nhất thường đến từ việc thu nhỏ kích thước về mức đủ dùng, ví dụ 1600 px cạnh dài để vẫn nét trên màn hình retina. Đổi định dạng sang WebP hay AVIF giảm thêm, nhưng mức giảm phụ thuộc vào ảnh nên cần đo trên ảnh thật.

    Còn chọn nơi nén thì có hai hướng. Nén ở trình duyệt (vẽ lại ảnh lên canvas) tiết kiệm băng thông lúc tải lên và không tốn tài nguyên máy chủ, hợp với trường hợp tác giả đáng tin. Nén ở máy chủ (ví dụ bằng sharp) hợp khi bạn không tin người tải lên, hoặc cần nhiều kích thước cho nhiều màn hình, nhưng tốn thời gian và bộ nhớ của hàm xử lý. Thành phần ảnh của framework (như next/image) chỉ tối ưu lúc phục vụ, còn ảnh gốc vẫn nằm trong kho.

    Những chi tiết dễ sai: ảnh chụp dọc có thẻ EXIF Orientation, nên phải xoay pixel trước khi bỏ metadata, nếu không ảnh hiện nằm ngang. GIF có thể là ảnh động nên không nên nén lại như ảnh tĩnh. Ảnh nhỏ sẵn có thể nặng hơn sau khi nén, vì vậy cần giữ bản gốc khi bản nén không nhỏ hơn.

    Điểm cộng: nhắc rằng bỏ metadata còn che được vị trí GPS trong ảnh điện thoại, và đặt giới hạn kích thước ở phía kho lưu trữ làm lớp bảo vệ thứ hai.

    Mở trang riêng của câu hỏi
  50. MiddleAIEmbedding là gì, và tìm kiếm bằng embedding khác tìm theo từ khóa ở điểm nào?

    Người phỏng vấn muốn biết bạn hiểu embedding như một công cụ cụ thể, có ưu và nhược, chứ không phải một từ khóa nghe cho oai. Họ cũng muốn xem bạn có biết cách kiểm tra xem nó có thật sự tốt hơn hay không.

    Embedding là cách biến một đoạn chữ thành một vector, tức một dãy số, bằng một mô hình đã được huấn luyện để hai đoạn gần nghĩa thì vector gần nhau. Để tìm kiếm, bạn nhúng sẵn các đoạn tài liệu và lưu vector lại. Khi có câu hỏi, bạn nhúng câu hỏi bằng cùng mô hình rồi lấy những đoạn có vector gần nhất, thường đo bằng cosine similarity.

    Khác biệt chính so với tìm theo từ khóa:

    1. Từ khóa so chữ, embedding so nghĩa. Khách viết "đền tiền", tài liệu viết "hoàn tiền": đếm từ thấy chữ quan trọng (đền với hoàn) khác nhau, còn embedding vẫn xếp đoạn đó cao.
    2. Điểm của embedding chỉ có ý nghĩa tương đối. Nhiều mô hình cho điểm dồn trong khoảng hẹp, nên bạn so thứ hạng chứ không đọc điểm như phần trăm.
    3. Từ khóa mạnh với mã, tên riêng, chuỗi chính xác. Tìm một mã sản phẩm hay một tên hàm thì khớp chữ chính xác lại đáng tin hơn so nghĩa.

    Ý quan trọng để nêu: đừng tin là embedding luôn tốt hơn. Cách làm đúng là có một bộ câu hỏi mẫu kèm đoạn tài liệu đúng cho từng câu, chạy cả hai cách và đếm câu nào được tìm đủ thông tin. Bộ câu hỏi cũng giúp bạn phát hiện những giả định sai, ví dụ nhúng thêm tiêu đề mục tưởng là có lợi nhưng đo ra lại kém hơn.

    Điểm cộng: nhắc rằng mô hình embedding có giới hạn độ dài đầu vào (nhiều mô hình cắt ở khoảng 512 token), nên đoạn tài liệu cần tách vừa đủ nhỏ, và nhắc rằng dùng đúng tiền tố mô hình yêu cầu (như query: và passage: ở họ e5) cũng ảnh hưởng chất lượng.

    Mở trang riêng của câu hỏi
  51. MiddleAI AgentContext window là gì? Chatbot "nhớ" cuộc trò chuyện bằng cách nào, và làm gì khi hội thoại quá dài?

    Context window là toàn bộ phần chữ mô hình đọc được trong một lần gọi: system prompt, định nghĩa công cụ, mọi tin nhắn gửi kèm và cả câu trả lời nó đang viết. Kích thước tính bằng token và mỗi mô hình có một giới hạn riêng.

    Ý người phỏng vấn muốn nghe đầu tiên là API của mô hình không có trạng thái. Nó không nhớ gì giữa hai lần gọi. Chatbot "nhớ" được là vì code của bạn giữ mảng tin nhắn và gửi lại toàn bộ ở mỗi lượt. Quên gửi thì tin nhắn thứ hai của khách đến với mô hình như câu đầu tiên của một người lạ.

    Hệ quả là lịch sử càng dài thì mỗi lần gọi càng tốn. Lần gọi thứ N gửi lại cả N−1 lượt trước, nên tổng số token của cả cuộc trò chuyện tăng nhanh hơn nhiều so với số tin nhắn. Với các mô hình có context window lớn, bạn thường gặp chi phí và độ trễ trước khi gặp giới hạn.

    Khi hội thoại dài, có ba cách thường dùng, và có thể kết hợp:

    • Cửa sổ trượt: chỉ gửi vài lượt gần nhất. Phải cắt ở ranh giới lượt, vì một kết quả công cụ (tool_result) phải đứng ngay sau lời gọi công cụ (tool_use) tương ứng. Cắt theo số tin nhắn rất dễ chẻ đôi một cặp và bị API từ chối.
    • Ghi nhớ có cấu trúc: code tự rút những dữ kiện quan trọng (mã đơn, tên khách) từ kết quả công cụ rồi đưa vào system prompt. Ít thông tin nhưng không sai.
    • Tóm tắt: nhờ mô hình tóm tắt phần cũ thành một đoạn ngắn. Nhớ được nhiều hơn nhưng có thể bỏ sót hoặc ghi sai chi tiết, nên không dùng cho những con số phải chính xác.

    Điểm cộng: nói rằng bạn sẽ đo số token đầu vào thật (trường usage trong phản hồi) trước khi tối ưu, lưu lịch sử đầy đủ ở database và chỉ cắt bản gửi đi, và biết phân biệt bộ nhớ trong một cuộc trò chuyện với bộ nhớ lâu dài giữa các phiên.

    Mở trang riêng của câu hỏi
  52. MiddleAI AgentKhi nào một AI agent chăm sóc khách hàng nên chuyển cuộc trò chuyện cho người thật, và bạn thiết kế việc chuyển đó thế nào?

    Người phỏng vấn muốn biết bạn coi việc chuyển cho người là một phần của thiết kế hệ thống, có quy tắc và có kiểm thử, chứ không phải một câu "xin lỗi, hãy liên hệ nhân viên" gắn vào cuối.

    Bạn nên mở bằng tiêu chí chuyển. Sắp theo mức rủi ro thì có: chuyện an toàn của khách (dị ứng, sức khỏe), chuyện tiền (hoàn tiền, đền bù, hủy đơn), cảm xúc của khách (khiếu nại, đồ bị hỏng, giao trễ), và các tín hiệu về chính agent như khách phải hỏi lại gần y nguyên một câu hay mô hình không chắc. Điểm quan trọng là quyết định này nên nằm trong code (một hàm thuần có test) thay vì trong prompt, vì code thì bạn chạy được trên một bộ tin nhắn mẫu và biết nó bỏ sót hay chuyển thừa bao nhiêu tin.

    Phần thiết kế việc chuyển có ba ý. Một, cuộc trò chuyện cần một trạng thái (agent đang xử lý, chờ người, người đang xử lý), và khi đã chuyển thì agent phải im lặng, nếu không nó sẽ tiếp tục trả lời những tin mà người thật đang nhận. Hai, người nhận cần một phiếu có tóm tắt (lý do, mã đơn, tin cuối của khách) để khỏi đọc lại từ đầu. Ba, mọi bước ghi vào nhật ký có mã cuộc trò chuyện, để sau này trả lời được "vì sao tin này bị chuyển hoặc không", và số điện thoại bị che trước khi ghi vì nhật ký sống lâu và nhiều người đọc.

    Điểm cộng: nhắc rằng lời hẹn gửi khách phải đúng sự thật (quán đóng cửa thì nói nhân viên trả lời sau giờ mở cửa), rằng quy tắc từ khóa dễ bị lọt nên cần đo trên dữ liệu thật do người khác gán nhãn, và rằng một hàng đợi không ai xem còn tệ hơn không có hàng đợi.

    Mở trang riêng của câu hỏi
  53. MiddleAI AgentLàm sao biết một AI agent tốt lên hay tệ đi sau mỗi lần sửa, và vì sao phải chạy mỗi ca thử nhiều lần?

    Người phỏng vấn muốn biết bạn có một phương pháp đo được sự thay đổi của agent, thay vì đọc thử vài câu trả lời rồi kết luận "trông ổn".

    Câu trả lời tốt bắt đầu bằng khái niệm eval (bộ đánh giá): một danh sách cố định các tin nhắn thử, chạy lại sau mỗi thay đổi như sửa prompt, đổi mô hình hay thêm công cụ. Nó giống unit test, chỉ khác ở chỗ agent có phần ngẫu nhiên, nên có hai quyết định riêng là chấm cái gì và chạy mấy lần.

    Về chấm điểm, nên tránh so nguyên văn câu trả lời, vì cùng một ý có nhiều cách diễn đạt và bộ so sẽ đỏ vô cớ. Thay vào đó hãy chấm những điều kiểm được trên "vết" của lần chạy: agent có gọi đúng công cụ với đúng tham số không, có chuyển cho người khi cần không, câu trả lời có chứa thứ bắt buộc hoặc thứ bị cấm (lộ dữ liệu người khác, hứa hoàn tiền) không. Chấm bằng code thì nhanh, rẻ và cho kết quả giống nhau mỗi lần. Những thứ code không chấm được, như giọng điệu, có thể dùng một mô hình khác làm giám khảo hoặc người chấm, nhưng giám khảo cần được kiểm tra độ tin cậy trước.

    Về số lần chạy, mô hình thật có thể trả lời lệch giữa hai lần, nên mỗi ca cần chạy nhiều lần và đếm tỉ lệ đạt. Một lỗi hiếm, ví dụ một lần trong mười, sẽ không hiện ra nếu chỉ chạy năm lần mỗi ca. Mỗi lần chạy là một lần gọi mô hình, tức là tiền và thời gian, nên hãy chia theo mức thiệt hại: ca về bảo mật và hoàn tiền phải đạt trọn vẹn, ca thường ngày cho phép ngưỡng thấp hơn.

    Điểm cộng: nhắc việc thêm mỗi lỗi do khách báo thành một ca mới, và nhận ra eval chỉ đo được những gì bạn đã nghĩ tới để thử.

    Mở trang riêng của câu hỏi
  54. MiddleAI AgentLàm thế nào để giảm chi phí khi chạy AI agent cho khách thật mà không làm agent tệ đi?

    Người phỏng vấn muốn biết bạn hiểu tiền của một agent đi đâu, và có biết cắt chi phí mà không đánh đổi chất lượng hay độ an toàn hay không.

    Câu trả lời tốt bắt đầu từ chỗ mô hình không có trí nhớ giữa các lượt gọi: mỗi lượt bạn gửi lại cả hướng dẫn, mô tả công cụ và dữ liệu cố định, trong khi tin nhắn của khách chỉ vài token. Từ đó có ba hướng. Hướng thứ nhất là cache prompt: phần đầu giống hệt nhau giữa các lượt được nhà cung cấp nhớ giúp và tính giá đọc thấp hơn nhiều giá thường. Điều kiện là phần đầu phải giống hệt từng ký tự, nên những thứ đổi liên tục như giờ hiện tại hay tên khách phải đặt sau phần cố định; đặt nhầm chỗ thì cache gần như không bao giờ trúng và còn đắt hơn khi không dùng. Hướng thứ hai là chọn mô hình theo tin nhắn: mặc định mô hình lớn, chỉ câu hỏi chắc chắn đơn giản và không dính tới tiền hay dữ liệu cá nhân mới đi mô hình nhỏ. Hướng thứ ba là đặt trần: số lượt gọi tối đa cho mỗi tin nhắn và số token đầu ra, khi chạm trần thì chuyển cho người thay vì để agent lặp mãi.

    Điểm cộng là nói được những cách tiết kiệm nghe hợp lý nhưng sai: ví dụ nhớ câu trả lời theo nội dung tin nhắn cho mọi tin, khiến khách này nhận được thông tin đơn hàng của khách kia. Câu trả lời cuối cùng nên nhắc rằng mỗi thay đổi đều phải chạy lại bộ eval để chắc agent không tệ đi.

    Mở trang riêng của câu hỏi
  55. MiddleAI AgentMCP là gì, và nó khác tool calling ở điểm nào?

    Người phỏng vấn muốn biết bạn phân biệt được cơ chế mô hình yêu cầu chạy công cụ với cách đóng gói và chia sẻ công cụ giữa các ứng dụng.

    Tool calling là khả năng của mô hình: bạn mô tả công cụ (tên, mô tả, schema đầu vào) trong yêu cầu gửi cho mô hình, và nó trả về một khối cho biết muốn gọi công cụ nào với đầu vào gì. Chính ứng dụng của bạn mới chạy công cụ rồi gửi kết quả lại. Mô hình không tự chạy gì cả.

    MCP (Model Context Protocol) là một chuẩn mở để ứng dụng AI kết nối với công cụ và dữ liệu bên ngoài. Công cụ được viết một lần trong một MCP server, và mọi ứng dụng có MCP client đều liệt kê và gọi được nó. Hai thứ không loại trừ nhau: ứng dụng lấy danh sách công cụ từ MCP server, đổi sang định dạng mà API của mô hình nhận (tool calling), rồi khi mô hình yêu cầu thì chuyển lời gọi sang server. Nói gọn: tool calling là cách mô hình đòi chạy công cụ, còn MCP là cách công cụ được đóng gói để nhiều ứng dụng cùng dùng.

    Khi nào đáng dùng MCP: khi một bộ công cụ phục vụ nhiều hơn một ứng dụng, hoặc khi bạn muốn dùng server có sẵn của người khác. Nếu chỉ có một ứng dụng và công cụ ít đổi, một hàm thường trong cùng code đơn giản hơn, vì MCP thêm một tiến trình và một lớp giao thức.

    Về an toàn, đặc tả của MCP khuyến nghị luôn có con người có thể từ chối lời gọi công cụ, và coi các gợi ý như readOnlyHint là không đáng tin trừ khi server đáng tin. Server vẫn phải tự kiểm tra đầu vào và quyền, vì đầu vào là do mô hình viết ra.

    Điểm cộng: nhắc rằng với stdio, stdout dành cho giao thức nên log phải ra stderr, và phân biệt lỗi giao thức với lỗi khi chạy công cụ (trả về kết quả kèm isError).

    Mở trang riêng của câu hỏi
  56. MiddleAI AgentPrompt injection là gì, khác nhau thế nào giữa tấn công trực tiếp và gián tiếp, và bạn phòng chống ra sao?

    Người phỏng vấn muốn biết bạn có nhận ra mô hình không phân biệt được chỉ dẫn của bạn với văn bản người khác viết, và biết đặt biện pháp bảo vệ ở nơi đáng tin hơn là lời dặn trong prompt.

    Prompt injection là khi văn bản do người khác kiểm soát lọt vào ngữ cảnh và làm mô hình hành xử ngoài ý muốn. Tấn công trực tiếp là khi chính tin nhắn của người dùng làm việc đó, ví dụ "bỏ qua mọi hướng dẫn trước đó". Tấn công gián tiếp là khi nội dung nằm trong dữ liệu mà agent đọc, như một ô ghi chú, một trang web hay một tài liệu, và người dùng thậm chí không viết nó. OWASP xếp đây là rủi ro đầu tiên của ứng dụng dùng mô hình ngôn ngữ, và ghi rằng chưa rõ có cách ngăn chặn tuyệt đối.

    Vì vậy biện pháp nên đặt trong code: quyền tối thiểu (agent không có công cụ làm việc nhạy cảm như hoàn tiền, chỉ chuyển cho người), kiểm tra quyền trong từng công cụ bằng danh tính do ứng dụng xác thực chứ không do mô hình khai, chỉ đưa vào ngữ cảnh những trường cần thiết, lọc đầu ra, và cho người duyệt các việc rủi ro cao. Lời dặn trong system prompt và việc đánh dấu nội dung không đáng tin vẫn nên làm nhưng chỉ là lớp bổ sung.

    Cần nói thêm: bộ lọc đầu ra bằng biểu thức chính quy luôn có thể bị lách bằng cách diễn đạt khác, và có thể chặn nhầm câu đúng, nên phải thử cả câu đúng lẫn câu sai.

    Điểm cộng: nhắc việc biến các tấn công đã biết thành bộ test chạy lại được, và phân biệt rằng test với mô hình giả chỉ chứng minh code xung quanh, không chứng minh hành vi của mô hình thật.

    Mở trang riêng của câu hỏi
  57. MiddleAI AgentStreaming câu trả lời của LLM hoạt động thế nào, và vì sao nó làm giao diện chat tốt hơn dù tổng thời gian không đổi?

    Người phỏng vấn muốn biết bạn hiểu streaming là thay đổi về cách gửi dữ liệu và về cảm nhận của người dùng, chứ không phải cách làm mô hình nhanh hơn, và bạn biết những chỗ dễ sai khi tự làm.

    Một mô hình sinh câu trả lời từng mảnh nhỏ (token). Không streaming thì server đợi mô hình sinh xong cả câu rồi mới trả một cục, nên người dùng nhìn khung chat trống cho tới lúc đó. Có streaming thì server đẩy từng mảnh về trình duyệt ngay khi mô hình vừa sinh ra, thường bằng Server-Sent Events (SSE): một kết nối HTTP được giữ mở và gửi các sự kiện nối nhau. Tổng thời gian gần như không đổi. Thứ giảm là thời gian tới chữ đầu tiên, và đó là khoảng lặng người dùng cảm nhận được.

    Trong Next.js, Route Handler trả về một Response bọc một ReadableStream. Phía trình duyệt, nếu cần gửi POST thì dùng fetch rồi đọc response.body, vì EventSource chỉ gửi được GET.

    Những chỗ hay sai: một chunk mạng có thể cắt giữa một sự kiện hoặc giữa một ký tự nhiều byte như chữ tiếng Việt, nên cần bộ đệm và TextDecoder với stream: true. Khi khách đóng tab thì server phải dừng việc gọi mô hình, nếu không bạn trả tiền cho chữ không ai đọc. Và proxy hay bộ nén có thể gom chữ lại, nên thường thêm header Cache-Control: no-cache, no-transform.

    Điểm cộng: nhắc rằng nên đo thời gian tới token đầu tiên riêng với tổng thời gian, và nói được rằng khi agent gọi công cụ giữa chừng thì cần một sự kiện trạng thái để khách biết nó đang làm gì.

    Mở trang riêng của câu hỏi
  58. MiddleAI AgentVòng lặp agent (agent loop) hoạt động thế nào? Làm sao để agent không chạy mãi?

    Vòng lặp agent là đoạn code bao quanh mô hình, cho phép nó làm nhiều bước liên tiếp thay vì trả lời một lần. Mỗi vòng gồm bốn việc:

    1. Gửi toàn bộ lịch sử messages cho mô hình, kèm danh sách công cụ.
    2. Thêm nguyên văn phản hồi của mô hình vào lịch sử.
    3. Nếu mô hình không xin công cụ (với Claude là stop_reason khác tool_use), đó là câu trả lời cuối và vòng lặp kết thúc.
    4. Nếu có, chạy mọi công cụ được xin, gom kết quả vào một tin nhắn, thêm vào lịch sử rồi lặp lại.

    Ý quan trọng là số bước do mô hình quyết định, không phải code. Ví dụ khách nhắn "tìm đơn theo số điện thoại, nếu chưa giao thì thêm một bạc xỉu": mô hình tự gọi lần lượt tìm đơn, xem trạng thái, thêm món, rồi mới trả lời. Code chỉ lặp.

    Vì vậy phần thứ hai của câu hỏi mới là phần người phỏng vấn muốn nghe. Nên nêu:

    • Giới hạn số lần gọi mô hình (ví dụ 6). Chạm giới hạn thì dừng, trả một câu trả lời an toàn và ghi log để người kiểm tra, thay vì ném lỗi hay lặp tiếp.
    • Luật nằm trong công cụ, không chỉ trong prompt. Hàm thêm món tự từ chối đơn đang giao, dù mô hình có gọi sai thứ tự.
    • Lỗi công cụ trả về cho mô hình (is_error) để nó đổi hướng, nhưng cần đề phòng mô hình thử lại mãi cùng một lời gọi.
    • Chi phí tăng theo số vòng: mỗi vòng gửi lại cả lịch sử, nên câu 4 bước tốn nhiều token hơn 4 lần câu 1 bước.

    Điểm cộng: nói thêm rằng nên có giới hạn thời gian hoặc ngân sách token bên cạnh giới hạn số vòng, và các SDK thường có sẵn tool runner chạy hộ vòng lặp này.

    Mở trang riêng của câu hỏi
  59. MiddleAI cho developerKhi đưa một mô hình ngôn ngữ vào ứng dụng, bạn làm gì để câu trả lời sai hoặc độc hại của nó không làm hỏng dữ liệu?

    Người phỏng vấn muốn biết bạn coi đầu ra của mô hình là dữ liệu không đáng tin, thay vì một hàm luôn trả đúng, và bạn có nghĩ tới các trường hợp mô hình hỏng hay chậm hay không.

    Câu trả lời tốt nêu một nguyên tắc: AI chỉ đề xuất, code mới quyết định. Với một tác vụ như phân loại, bạn giới hạn đầu ra vào một danh sách nhãn cố định, yêu cầu dạng JSON và tự kiểm tra bằng code: nhãn không nằm trong danh sách thì bỏ, kể cả khi nó nghe rất hợp lý. Nhờ vậy, ngay cả khi người dùng cài một câu lệnh vào nội dung, kết quả chỉ có thể là một nhãn hợp lệ hoặc rỗng.

    Phần còn lại là chuyện vận hành. Cuộc gọi cần có hạn chót, và thao tác chính (ghi dữ liệu của người dùng) phải làm trước, không phụ thuộc vào việc mô hình trả lời được hay không. Chỉ gửi cho mô hình phần dữ liệu thật cần thiết, bỏ số điện thoại, mã người dùng, số tiền nếu bước đó không cần. Phân biệt các loại lỗi (sai định dạng, quá giờ, lỗi mạng) để đếm được mô hình hỏng kiểu nào nhiều nhất.

    Điểm cộng là nhắc rằng hạn chót phải nằm dưới thời gian chờ của webhook, nếu không nền tảng sẽ gửi lại yêu cầu và khoản dữ liệu bị ghi hai lần, và biết rằng độ chính xác của mô hình chỉ đo được bằng một tập mẫu đã gán nhãn.

    Mở trang riêng của câu hỏi
  60. MiddleAI cho developerTrong công việc hằng ngày, bạn chọn việc nào để tự động hóa bằng script và việc nào để một AI agent làm?

    Người phỏng vấn muốn biết bạn có tiêu chí để chọn công cụ, thay vì dùng AI cho mọi thứ hay không dùng gì cả.

    Một cách phân loại đơn giản dùng hai câu hỏi. Thứ nhất, mọi lần chạy câu trả lời có giống nhau không? Thứ hai, máy có tự kiểm được kết quả là đúng hay sai không? Nếu cả hai đều có thì dùng script: chạy kiểm kiểu và test, kiểm quy ước, kiểm định dạng tin nhắn commit. Script rẻ, nhanh, và cho kết quả giống nhau mỗi lần nên tin được. Nếu việc cần hiểu ý hoặc có nhiều đáp án đúng, như viết nội dung một tin nhắn commit nói đúng thay đổi hay sửa một lỗi mà mỗi lần một khác, thì đó mới là chỗ agent giúp được, và nên có người xem lại.

    Bạn nên kể cách đưa tự động hóa vào quy trình: gom các bước vào một lệnh để cả người và AI cùng dùng, rồi cài Git hook để Git tự chạy nó trước mỗi commit. Nhưng hãy nói rõ giới hạn: hook bị bỏ qua bằng --no-verify, chạy trên cây làm việc chứ không phải đúng thứ đã stage, và nếu nó chậm thì người ta sẽ quen tay bỏ qua. Vì vậy kiểm tra cần chạy lại ở CI.

    Điểm cộng: nhắc rằng bạn chưa chắc một agent tự sửa tới khi xanh có che mất lỗi thật hay không, nên với việc đó bạn vẫn xem lại kết quả, thay vì tuyên bố nó an toàn khi chưa thử.

    Mở trang riêng của câu hỏi
  61. MiddleBảo mậtLàm sao cho người lạ đăng ký email trên website mà không để lộ danh sách người đã đăng ký?

    Người phỏng vấn muốn xem bạn nghĩ tới dữ liệu của người khác ngay từ thiết kế, và biết rằng "cho thêm nhưng không cho đọc" có những kẽ hở tinh vi hơn vẻ ngoài.

    Cách đầu tiên ai cũng nghĩ tới là cho vai trò công khai (chưa đăng nhập) thêm dòng vào bảng. Nếu bạn lỡ cho cả quyền đọc, ai có khóa công khai của trang cũng lấy được cả danh sách. Bỏ quyền đọc thì chưa đủ: nếu cột email có ràng buộc duy nhất, việc thêm một email đã có trả về lỗi trùng, còn email mới thì im lặng, nên người ta dò được từng địa chỉ có trong danh sách hay không.

    Cách tốt hơn là đóng hẳn bảng với vai trò công khai và chỉ mở một hàm. Hàm đó chạy với quyền cao hơn (kiểu security definer trong Postgres), kiểm tra và chuẩn hóa email, và trả về cùng một kết quả dù email mới hay đã có, nên không lộ gì. Chỉ tác giả hoặc quản trị viên đọc được bảng, thông qua chính sách của hàng. Thêm vào đó là lớp lọc robot rẻ tiền (một ô ẩn mà người thật không điền, thời gian điền form) và một giới hạn tần suất.

    Cần nói thêm về quyền riêng tư: chỉ thu thứ thật sự cần, cho người đăng ký đường hủy không cần đăng nhập (một token ngẫu nhiên), và nói rõ email dùng để làm gì.

    Điểm cộng: nhắc rằng đăng ký một bước dễ bị robot nhồi địa chỉ của người khác, và nếu cần gửi thư thật thì xác nhận hai bước qua email là bước tiếp theo.

    Mở trang riêng của câu hỏi
  62. MiddleDatabaseIndex trong database là gì? Có nên đánh index cho mọi cột không?

    Index là một cấu trúc dữ liệu phụ (thường là B-tree) giúp tìm hàng theo giá trị một cột mà không phải quét toàn bảng, giống mục lục của một cuốn sách.

    Không nên đánh index mọi cột vì:

    • Mỗi lần INSERT, UPDATE, DELETE đều phải cập nhật thêm index, ghi chậm hơn.
    • Index tốn dung lượng.
    • Cột ít giá trị khác nhau (như status chỉ có 2 giá trị) thường không được lợi nhiều.
    Đánh index cho truy vấn thật sự hay chạy
    create index posts_published_at_idx on posts (published_at desc)
      where status = 'published';

    Câu trả lời tốt sẽ nhắc tới việc đo trước bằng EXPLAIN ANALYZE rồi mới thêm index cho đúng truy vấn chậm.

    Mở trang riêng của câu hỏi
  63. MiddleDatabaseRow Level Security (RLS) trong PostgreSQL là gì? Nó khác gì với GRANT?

    PostgreSQL có hai lớp kiểm soát quyền trên một bảng, và câu trả lời tốt phân biệt được chúng.

    GRANT quyết định một vai được làm thao tác gì trên bảng: select, insert, update, delete. Nó không phân biệt các dòng, đã cho select là đọc được mọi dòng.

    Row Level Security quyết định vai đó được đụng tới những dòng nào. Sau khi chạy alter table ... enable row level security, mỗi thao tác phải thỏa một policy, tức một điều kiện SQL kiểm tra trên từng dòng. Ví dụ cho blog:

    sql
    create policy "Ai cũng đọc được bài đã đăng" on posts for select
    to anon using (status = 'published' and published_at <= now());

    Vài ý nên nêu để cho thấy bạn đã dùng thật:

    • Bật RLS mà không có policy thì mặc định từ chối tất cả. Câu select không báo lỗi mà trả về 0 dòng, nên trang trống dù bảng có dữ liệu. Đây là lỗi rất hay gặp.
    • Thao tác ghi bị chặn im lặng: update và delete trên dòng không được thấy sẽ ảnh hưởng 0 dòng, còn insert vi phạm policy thì báo lỗi.
    • Chủ bảng, superuser và vai có BYPASSRLS bỏ qua RLS. Vì vậy thử policy phải dùng đúng vai của ứng dụng (ví dụ set role anon), và khóa quản trị không bao giờ được nằm ở trình duyệt.
    • Vì sao cần RLS khi đã lọc trong code: với các nền tảng cho trình duyệt gọi thẳng database qua API (như Supabase), khóa nằm ở phía client nên ai cũng gọi được. Bộ lọc trong code không bảo vệ được gì, policy trong database mới là thứ chặn.

    Điểm cộng: nói rằng nên dùng cả hai lớp, chỉ grant đúng thao tác cần thiết, để một lần cấp nhầm quyền không làm lộ hay mất dữ liệu.

    Mở trang riêng của câu hỏi
  64. MiddleJavaScriptCatastrophic backtracking trong regex là gì, và khi nào bạn không nên dùng regex?

    Người phỏng vấn muốn biết bạn hiểu regex chạy thế nào bên dưới, nhận ra rủi ro với dữ liệu mình không kiểm soát, và biết đâu là giới hạn của công cụ này.

    Phần lớn bộ máy regex (kể cả của JavaScript) khớp bằng cách thử một hướng, nếu hỏng thì quay lui và thử hướng khác. Với một số biểu thức, đặc biệt là biểu thức có lặp lồng trong lặp như ^(\d+\.?)+$, số hướng cần thử tăng gấp đôi cho mỗi ký tự thêm vào khi chuỗi không khớp. Đó là catastrophic backtracking: một chuỗi chỉ dài vài chục ký tự có thể làm một dòng code chạy hàng giây, hàng giờ. Khi regex chạy trên dữ liệu do người ngoài gửi, nó trở thành một lỗ hổng từ chối dịch vụ (ReDoS), và OWASP có một trang riêng về chuyện này.

    Cách phòng: tránh lặp lồng hoặc hai phần lặp chồng lên nhau, viết sao cho mỗi ký tự chỉ có một cách khớp, giới hạn độ dài đầu vào trước khi chạy regex, và thử biểu thức với một chuỗi dài không khớp thay vì chỉ thử các chuỗi đúng. Ở những nơi quan trọng, có thể dùng bộ máy đảm bảo thời gian tuyến tính hoặc có giới hạn thời gian.

    Còn khi nào không nên dùng regex: với cấu trúc có trạng thái hoặc lồng nhau, như tách ô CSV có ngoặc kép hay đọc HTML, một vòng lặp qua từng ký tự hoặc một thư viện đọc đúng định dạng rõ ràng và an toàn hơn. Regex hợp để chuẩn hóa từng mẩu nhỏ như số điện thoại hay ngày tháng, sau khi đã tách đúng ô.

    Điểm cộng: kể một ví dụ chuẩn hóa dữ liệu bẩn bằng regex kèm báo cáo các dòng bị loại, thay vì bỏ qua lỗi âm thầm.

    Mở trang riêng của câu hỏi
  65. MiddleKiểm thửBộ test của bạn đã xanh hết, làm sao biết nó thật sự bảo vệ được code, và CI giúp gì thêm?

    Người phỏng vấn muốn biết bạn hiểu test xanh chỉ nói rằng các ca đã viết đều qua, không nói rằng những ca quan trọng đã được viết. Họ cũng muốn thấy bạn biết CI làm gì và không làm gì.

    Cách trả lời tốt bắt đầu bằng một thí nghiệm: làm hỏng code có chủ đích. Bạn sửa một chỗ nhỏ (đổi < thành <=, bỏ một lần trim(), cho phép thêm một ký tự) rồi chạy test. Nếu không test nào đỏ, lỗi đó có thể vào code thật mà không ai biết. Người ta gọi kỹ thuật này là mutation testing, và có công cụ tự sinh hàng loạt lỗi như vậy. Thường lỗi lọt qua nằm ở ranh giới (254 hay 255 ký tự, đúng 1,5 giây) và ở ca đặc biệt (hai dấu @, chuỗi toàn khoảng trắng), nên viết test ngay hai bên ranh giới là cách sửa rẻ nhất.

    Về CI: nó chạy cùng bộ lệnh trên một máy sạch mỗi lần có code mới, nên không phụ thuộc việc người đẩy code có nhớ chạy hay không. Bạn nên nhắc npm ci thay vì npm install (cài đúng theo lockfile và báo lỗi khi lệch), việc hủy lượt chạy cũ khi cùng nhánh có lượt mới, và giới hạn quyền của token. Quan trọng không kém là quy tắc của nhóm: CI đỏ thì không merge, vì nếu ai cũng bỏ qua thì CI chỉ là đồ trang trí.

    Điểm cộng: nói được rằng Git hook trên máy cá nhân có thể bị bỏ qua bằng --no-verify, nên kiểm tra phải chạy lại ở CI, và rằng CI chỉ cho biết code không phá những gì đã kiểm, chưa cho biết trang thật có chạy ổn hay không.

    Mở trang riêng của câu hỏi
  66. MiddleLàm việc nhómBạn xử lý thế nào khi không đồng ý với góp ý trong code review?

    Người phỏng vấn muốn biết bạn có tách được cái tôi khỏi code hay không.

    1. Hỏi lại để hiểu đúng lý do của reviewer trước khi phản hồi.
    2. Trả lời bằng dữ kiện: đo đạc, tài liệu, quy ước của team, không bằng cảm giác.
    3. Nếu vẫn khác ý và vấn đề nhỏ, chọn cách của reviewer hoặc ghi lại để bàn sau, đừng chặn PR.
    4. Nếu vấn đề lớn, chuyển sang trao đổi trực tiếp thay vì tranh luận dài trong comment.

    Kể một ví dụ thật, ngắn gọn, có kết quả cụ thể sẽ thuyết phục hơn liệt kê nguyên tắc.

    Mở trang riêng của câu hỏi
  67. MiddleNext.jsLàm thế nào để mỗi bài viết trong Next.js có ảnh chia sẻ (OG image) riêng, và cần chú ý gì khi tiêu đề dài hoặc có dấu tiếng Việt?

    Người phỏng vấn muốn biết bạn hiểu cơ chế tạo ảnh bằng code của Next.js và biết những chỗ hay sai khi đưa nó vào một blog thật.

    Trong App Router, bạn đặt một file opengraph-image.tsx cạnh trang bài viết, export một hàm mặc định trả về ImageResponse (từ next/og) và có thể export thêm alt, size, contentType. Next.js tự thêm các thẻ og:image vào <head>. Với route động như [slug], hàm nhận params, mà từ Next.js 16 là một Promise nên phải await. Theo tài liệu, ảnh được tạo lúc build và lưu lại trừ khi bạn dùng dữ liệu hay API chỉ có ở lúc yêu cầu, nên không phải vẽ lại cho từng người xem.

    Những chỗ cần chú ý: ImageResponse chỉ hỗ trợ flexbox và một phần CSS (không có display: grid), font phải là ttf, otf hoặc woff, và có giới hạn kích thước gói 500 KB tính cả JSX, CSS, font lẫn ảnh. Font mặc định chỉ có một độ đậm, nên muốn tiêu đề đậm và có nhận diện riêng thì phải nhúng font và đọc nó một lần ở cấp module. Về dấu tiếng Việt, đừng đoán mà hãy thử với một tiêu đề có nhiều dấu chồng, rồi mở ảnh ra xem.

    Với tiêu đề dài, cỡ chữ cố định sẽ làm chữ tràn khỏi ảnh hoặc đè lên phần chân. Cách đơn giản là giảm cỡ chữ theo độ dài tiêu đề, và thử với một tiêu đề dài bất thường chứ không chỉ tiêu đề đẹp.

    Điểm cộng: nhắc rằng Facebook thường giữ bản xem trước đã lấy, nên sau khi đổi ảnh cần dùng công cụ Sharing Debugger để lấy lại, và nhắc việc kiểm tra kích thước, dung lượng bằng test.

    Mở trang riêng của câu hỏi
  68. MiddleNode.jsMột API Node.js trả lời chậm, bạn tìm nguyên nhân thế nào trước khi sửa?

    Người phỏng vấn muốn biết bạn có làm việc dựa trên số đo thay vì đoán, và có hiểu vì sao một đoạn code chạy nhanh với dữ liệu nhỏ lại chậm khi dữ liệu lớn.

    Bước đầu tiên là biết chậm ở đâu ở mức request. Nếu máy chủ có log ghi thời gian xử lý mỗi request, bạn lọc ra đường nào chậm. Nếu chưa có, hãy đo bằng performance.now() hoặc thêm log đó trước. Bước thứ hai là kiểm tra giả thuyết bằng cách đo riêng từng phần nghi ngờ (định dạng dữ liệu, chuyển JSON, truy vấn cơ sở dữ liệu): nếu một phần chỉ chiếm vài mili giây trên tổng vài giây thì đừng tối ưu nó. Bước thứ ba là dùng profiler, ví dụ cờ --cpu-prof của Node.js ghi file .cpuprofile, để xem hàm nào tốn nhiều thời gian riêng nhất.

    Một nguyên nhân hay gặp là độ phức tạp tăng theo bình phương: dùng find hoặc includes trên một mảng ngay trong vòng lặp. Với vài trăm phần tử thì không thấy, với hàng chục nghìn thì chậm hẳn. Cách sửa thường là dùng Map hoặc Set để tìm theo khóa.

    Sau khi sửa, kiểm chứng hai lần: kết quả có còn đúng không (giữ bản cũ để so, hoặc viết test) và nhanh hơn bao nhiêu (đo nhiều lần, lấy trung vị, ở nhiều cỡ dữ liệu để thấy hình dạng của đường tăng).

    Điểm cộng: nhắc rằng không phải đoạn code nào cũng đáng tối ưu, bản dễ đọc ở quy mô nhỏ là quyết định hợp lý, và số đo chỉ đúng với máy và dữ liệu bạn đo.

    Mở trang riêng của câu hỏi
  69. MiddleReactuseEffect trong React chạy khi nào? Mảng phụ thuộc dùng để làm gì?

    useEffect chạy sau khi React đã cập nhật giao diện. Nó dùng để đồng bộ component với thứ bên ngoài React: gọi API, đăng ký sự kiện, hẹn giờ.

    • Không có mảng phụ thuộc: chạy sau mỗi lần render.
    • Mảng rỗng []: chạy một lần sau lần render đầu tiên.
    • Có giá trị [id]: chạy lại khi id thay đổi.

    Hàm trả về từ effect là hàm dọn dẹp, chạy trước lần effect tiếp theo và khi component bị gỡ, ví dụ để hủy hẹn giờ hay gỡ sự kiện.

    Mở trang riêng của câu hỏi
  70. MiddleWebBạn thiết kế việc đếm lượt xem bài viết thế nào để tôn trọng quyền riêng tư và không bị robot làm sai số?

    Người phỏng vấn muốn xem bạn có xuất phát từ câu hỏi cần trả lời hay không, thay vì ghi mọi thứ có thể ghi, và có biết các cách dữ liệu đếm bị sai trong thực tế không.

    Một câu trả lời tốt bắt đầu bằng việc xác định mình cần biết gì. Nếu chỉ cần biết bài nào được đọc nhiều thì một con số cho mỗi bài mỗi ngày là đủ, không cần IP, trình duyệt hay lịch sử của từng người. Cách ghi nhật ký từng lượt truy cập vừa phải bảo vệ dữ liệu cá nhân vừa cho con số sai, vì tải lại trang, robot và lượt lướt qua đều thành một dòng.

    Về thiết kế, bảng chỉ có khóa (bài, ngày) và một cột đếm, tăng bằng một câu lệnh upsert nguyên tử để hai người đọc cùng lúc không làm mất lượt. Trình duyệt chờ vài giây trước khi báo, mỗi tab chỉ báo một lần cho mỗi bài, và bỏ qua trình duyệt tự động. Máy chủ kiểm tra lại: chỉ nhận slug đúng dạng và là bài đã đăng, vì hàm có thể bị gọi thẳng mà không qua giao diện. Bản xem thử không được đếm, nhất là khi dùng chung cơ sở dữ liệu với bản thật.

    Điểm cộng là nói được giới hạn: không đếm được người đọc duy nhất, một người mở hai tab bị đếm hai lần, và không biết nguồn truy cập. Biết mình chọn không đo gì, và vì sao, là dấu hiệu của người đã suy nghĩ chứ không chỉ làm theo hướng dẫn.

    Mở trang riêng của câu hỏi
  71. MiddleWebXSS là gì, và làm sao hiển thị nội dung Markdown do người dùng nhập mà không bị XSS?

    Người phỏng vấn muốn biết bạn có hiểu XSS là chuyện nội dung không tin cậy được trình duyệt chạy như mã, và bạn có biết nó lọt vào từ những chỗ nào, không chỉ thuộc lòng tên.

    XSS (cross-site scripting) xảy ra khi dữ liệu của người dùng được đưa vào trang theo cách trình duyệt hiểu là HTML hoặc mã thay vì là chữ. Khi đó kẻ tấn công chạy được mã trong trang của bạn, với quyền của người đang xem: đọc cookie, gọi API thay họ, đổi nội dung trang.

    Với Markdown, rủi ro chính đến từ ba chỗ:

    1. HTML thô. Markdown cho phép viết HTML nên các thư viện chuyển nó sang đầu ra nguyên văn, kể cả thẻ như <img onerror=...>. Thẻ <script> chèn qua innerHTML không chạy, nhưng nhiều cách khác vẫn chạy.
    2. Liên kết và ảnh có giao thức nguy hiểm. [xem](javascript:...) chạy mã khi bấm.
    3. Bộ lọc cấu hình sai. Danh sách cho phép quá rộng, hoặc lọc bằng regex tự viết, rất dễ bị lách.

    Có hai hướng xử lý đáng nhắc tới. Hướng một là chuyển Markdown sang HTML rồi lọc bằng thư viện sanitize chuyên dụng theo danh sách cho phép (DOMPurify, sanitize-html), chỉ giữ những thẻ và thuộc tính cần. Hướng hai là không tạo ra chuỗi HTML: phân tích Markdown thành các khối có kiểu rồi dựng bằng framework như React, nơi chữ được đặt làm nội dung phần tử nên tự được thoát ký tự. Với hướng hai, bạn vẫn phải tự kiểm tra địa chỉ của liên kết và ảnh, chỉ nhận http, https và đường dẫn trong site.

    Ý quan trọng để nêu: đừng dùng dangerouslySetInnerHTML (hoặc innerHTML) cho nội dung chưa được lọc, và mọi đường đưa nội dung ra trang, kể cả ô xem trước trong trang quản trị, phải đi qua cùng một bộ xử lý an toàn.

    Điểm cộng: nhắc tới Content Security Policy như lớp bảo vệ thứ hai, hạn chế nơi mã được phép chạy, và nhắc rằng một điều kiện an toàn thì nên có test, ví dụ test cho các địa chỉ javascript: và data:.

    Mở trang riêng của câu hỏi
  72. SeniorKiến trúcBạn sẽ thiết kế giới hạn tốc độ (rate limiting) cho một API công khai thế nào?

    Bắt đầu bằng câu hỏi làm rõ: giới hạn theo ai (IP, API key, người dùng), mức nào, chặn cứng hay giảm tốc, hệ thống chạy một hay nhiều máy chủ.

    Các thuật toán thường gặp:

    • Fixed window: đếm theo khung phút, đơn giản nhưng dồn request ở mép khung.
    • Sliding window: mượt hơn, tốn bộ nhớ hơn.
    • Token bucket: cho phép bùng nổ ngắn, phổ biến nhất cho API.

    Với nhiều máy chủ, bộ đếm cần nằm ở chỗ dùng chung như Redis với thao tác nguyên tử. Trả về mã 429 Too Many Requests kèm header Retry-After, và ghi log để phát hiện lạm dụng.

    Ở mức senior, nói thêm về đánh đổi: độ chính xác so với chi phí, xử lý khi Redis lỗi (cho qua hay chặn hết), và giới hạn khác nhau cho từng gói khách hàng.

    Mở trang riêng của câu hỏi
  73. SeniorKiến trúcKhi bắt đầu một dự án mới, bạn chọn công nghệ dựa trên những tiêu chí nào?

    Bắt đầu từ bài toán và ràng buộc, không bắt đầu từ công nghệ đang hot.

    • Ai sẽ vận hành và bảo trì sau 6 tháng, team đã quen gì.
    • Ràng buộc thật: ngân sách, hạn chót, SEO, yêu cầu offline, quy mô người dùng.
    • Chi phí chuyển đổi nếu chọn sai, và có lối thoát hay không.
    • Hệ sinh thái, tài liệu, khả năng tuyển người.

    So sánh tối đa ba lựa chọn theo đúng các tiêu chí đó và ghi lại quyết định (một tài liệu ADR ngắn) để lần sau xem lại có còn đúng không.

    Mở trang riêng của câu hỏi