23 câu hỏi
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.
AI agent khác chatbot và workflow ở điểm nào? Khi nào không nên dùng agent?
Mở trang riêng của câu hỏiCâ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.
LLM 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?
Mở trang riêng của câu hỏiHallucination 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.
Tool 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?
Mở trang riêng của câu hỏiTool 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:
- Ứ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.
- Nếu cần, mô hình trả về khối
tool_use(tên công cụ và đối số) vớistop_reasonlàtool_use, thay vì câu trả lời. - Ứ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_resultcó cùngid. - 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.
Bạ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?
Mở trang riêng của câu hỏiCâ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.
Bạ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?
Mở trang riêng của câu hỏiNgườ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ể:
- Đị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.
- 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.
- Đọ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.
- 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.
- 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ũ.
- 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.
Khi nhờ AI viết hoặc sửa code, bạn viết prompt thế nào để kết quả dùng được?
Mở trang riêng của câu hỏiNgườ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õ:
- 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). - 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.
- 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.
- 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
Database 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ì?
Mở trang riêng của câu hỏiMigration 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 nullsẽ 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áocolumn "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 đặtnot 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
insertcũ 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.
Vì sao thường không lưu ảnh trực tiếp trong database? Bạn lưu ảnh ở đâu?
Mở trang riêng của câu hỏiẢ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.
Bạn hãy giới thiệu về bản thân trong khoảng một phút.
Mở trang riêng của câu hỏiCâ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:
- 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."
- 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."
- Đ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."
- 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.
Hã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?
Mở trang riêng của câu hỏiNgườ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ự:
- Vấn đề: dự án giải quyết việc gì, cho ai. Một câu là đủ.
- 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ứ.
- 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".
- 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.
- 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.
Nế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ì?
Mở trang riêng của câu hỏiCâ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".
Git merge và git rebase khác nhau thế nào? Khi nào nên dùng cái nào?
Mở trang riêng của câu hỏiCả 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 mergetạ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 rebaseviế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.
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ạnTrong 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.
GET và POST khác nhau ra sao? Vì sao không nên dùng GET để xóa dữ liệu?
Mở trang riêng của câu hỏiGET 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.
Breakpoint khác console.log thế nào khi debug? Khi nào bạn vẫn dùng console.log?
Mở trang riêng của câu hỏiBreakpoint 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.logchỉ 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 ra35.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àtypeoftrả về'string'. Lỗi lộ ra ngay:"35.000" * 2bằ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.logvẫ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ố.Khác nhau giữa var, let và const trong JavaScript?
Mở trang riêng của câu hỏivarcó 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.letcó 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.constgiốngletnhưng không gán lại được. Lưu ý: object hay mảng khai báo bằngconstvẫn sửa được bên trong.
const user = { name: "An" }; user.name = "Bình"; // được user = {}; // TypeErrorThói quen tốt: mặc định dùng
const, chỉ dùngletkhi thật sự cần gán lại, và tránhvartrong code mới.Stack trace là gì? Khi gặp "Cannot read properties of undefined", bạn tìm nguyên nhân thế nào?
Mở trang riêng của câu hỏiStack 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:- Đọc thông điệp: thuộc tính đang đọc là
name, nên thứ đứng ngay trước.nameđang làundefined. - Tìm khung đầu tiên thuộc code của mình: bỏ qua các khung trong
node_moduleshaynode:internal, mở đúng file, dòng và cột. - 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. - 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.- Đọc thông điệp: thuộc tính đang đọc là
Trong 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?
Mở trang riêng của câu hỏiCả 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.tsxvẫ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
appcũng không thành trang nếu không phảipagehoặcroute. Vì vậy_folderchủ 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.Unit test là gì? Làm sao biết một test bạn viết thật sự có tác dụng?
Mở trang riêng của câu hỏiUnit 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.
- Kết quả mong đợi đến từ bên ngoài code. Viết
Index trong database là gì? Có nên đánh index cho mọi cột không?
Mở trang riêng của câu hỏiIndex 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ư
statuschỉ có 2 giá trị) thường không được lợi nhiều.
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 ANALYZErồi mới thêm index cho đúng truy vấn chậm.- Mỗi lần
Bạn xử lý thế nào khi không đồng ý với góp ý trong code review?
Mở trang riêng của câu hỏiNgườ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.
- Hỏi lại để hiểu đúng lý do của reviewer trước khi phản hồi.
- 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.
- 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.
- 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.
useEffect trong React chạy khi nào? Mảng phụ thuộc dùng để làm gì?
Mở trang riêng của câu hỏiuseEffectchạ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 khiidthay đổ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.
Bạn sẽ thiết kế giới hạn tốc độ (rate limiting) cho một API công khai thế nào?
Mở trang riêng của câu hỏiBắ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 Requestskèm headerRetry-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.
Khi 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?
Mở trang riêng của câu hỏiBắ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.