Chuẩn bị phỏng vấn junior developer trong một tuần

Chuẩn bị phỏng vấn junior developer khi chỉ còn một tuần: đọc lại tin tuyển dụng, chia việc ôn thành ba nhóm, lịch 7 ngày, luyện code và câu hỏi để hỏi lại.

Chuẩn bị phỏng vấn junior developer khi chỉ còn một tuần thì ôn gì? Đừng ôn lại cả chương trình. Bạn đọc lại tin tuyển dụng, chia việc thành ba nhóm là kể dự án, kiến thức nền và code trực tiếp, rồi xếp vào bảy ngày. Bạn từng ôn dàn trải chưa? Cùng mình xem Minh làm nhé!

Minh còn đúng một tuần

Ở bài trước, Minh nộp mười đơn sau khi đọc và chấm kỹ từng tin tuyển dụng. Minh là nhân vật minh họa, ghép từ những tình huống quen thuộc của sinh viên IT mới ra trường, và công ty A trong bài cũng vậy. Hai tuần sau, công ty A mời Minh phỏng vấn kỹ thuật vào thứ Năm tuần sau. Minh nhắn cho mình: "Còn đúng một tuần. Em ôn gì trước?".

Phản xạ của Minh là mở một danh sách "100 câu hỏi phỏng vấn JavaScript" và đọc từ câu số 1. Mình hiểu phản xạ đó, vì nó cho cảm giác đang làm việc. Nhưng một tuần không đủ để học 100 câu, và phần lớn 100 câu đó không liên quan tới công ty A. Cách đọc lướt cũng có một vấn đề lớn hơn. Đọc một câu trả lời thì khác hẳn với tự nói ra câu trả lời đó trước mặt người khác, và trong phòng phỏng vấn bạn chỉ có việc thứ hai.

Vì vậy mình đề nghị Minh làm ngược lại. Bắt đầu từ thứ công ty A sẽ hỏi, rồi mới tới thứ cần ôn.

Đọc lại email và tin tuyển dụng thì tìm gì?

Minh có hai nguồn thông tin. Nguồn thứ nhất là tin tuyển dụng của công ty A mà bài trước đã chấm 10 trên 10 điểm. Đây là phần yêu cầu của tin, chép nguyên văn từ demo của bài trước:

Tin A, phần yêu cầu (minh họa)
Công việc: phát triển tính năng cho ứng dụng quản lý chi tiêu trên web; viết test cho phần mình làm;
  tham gia review code cùng nhóm 4 frontend.
Yêu cầu: React, TypeScript, Git; hiểu REST API; 0–1 năm kinh nghiệm hoặc dự án cá nhân tương đương.
Ưu tiên: Next.js; đã viết unit test.

Nguồn thứ hai là email mời phỏng vấn. Email ghi buổi phỏng vấn kéo dài 60 phút và diễn ra online. Nó không ghi buổi đó gồm những phần nào.

Việc đầu tiên Minh làm, ngay trong ngày nhận email, là trả lời xác nhận giờ và hỏi thêm ba điều: ai sẽ phỏng vấn, có phần code trực tiếp không, và nếu có thì dùng công cụ gì. Nhiều bạn ngại hỏi vì sợ bị đánh giá là chưa chuẩn bị.

Mình nghĩ ngược lại. Câu hỏi về hình thức phỏng vấn cho thấy bạn nghiêm túc, và người tuyển dụng thường sẵn lòng trả lời vì họ muốn bạn thể hiện tốt nhất. Họ trả lời rằng sẽ có một trưởng nhóm frontend, buổi gồm nói về dự án, vài câu hỏi kiến thức, và một bài code nhỏ khoảng 20 phút trên một trình soạn code online.

Có câu trả lời đó, Minh biết thời gian một tuần phải chia cho ba thứ, và không có thứ nào là "đọc 100 câu hỏi".

Chia việc ôn thành mấy nhóm?

Minh chia mọi thứ vào ba nhóm, đúng theo ba phần của buổi phỏng vấn, và ghi vào một file tên danh-sach-on-tap.md.

Nhóm thứ nhất là dự án của chính Minh. Đây là phần mà người phỏng vấn dùng để biết bạn đã thật sự làm gì, và cũng là phần duy nhất mà không ai chuẩn bị thay bạn được. Dự án chính của Minh là app ghi chi tiêu bằng tin nhắn, dự án mà bài về dự án cá nhân đã chọn và đã chấm điểm.

Nhóm thứ hai là kiến thức nền, và đây là chỗ dễ ôn dàn trải nhất. Minh không lấy câu hỏi từ danh sách 100 câu, mà lấy từ từng yêu cầu trong tin A. Mỗi yêu cầu sinh ra vài câu, và tổng cộng là 15:

danh-sach-on-tap.md (nhóm 2)
JavaScript / TypeScript
1. `let`, `const`, `var` khác nhau thế nào?
2. `==` và `===`.
3. Promise và `async/await`: đoạn code chạy theo thứ tự nào?
4. `map`, `filter`, `reduce`: viết lại một vòng `for` bằng chúng.
5. Closure là gì, ví dụ trong code của mình.
6. `type` và `interface` trong TypeScript.
7. `unknown` khác `any` thế nào.
8. Đọc một stack trace: tìm dòng đầu tiên thuộc code của mình.
React / Next.js
9. Vì sao không sửa trực tiếp state.
10. `useEffect` chạy khi nào, mảng phụ thuộc để làm gì.
11. `key` trong danh sách để làm gì.
12. Server Component và Client Component trong Next.js.
Git / REST / test
13. Một pull request tốt cần gì.
14. GET, POST, PUT, DELETE và mã 200, 400, 401, 404, 500.
15. Unit test là gì, làm sao biết một test có tác dụng.

Mười lăm câu nghe có vẻ ít. Nhưng mỗi câu Minh phải nói được câu trả lời trong khoảng một phút, kèm một ví dụ từ code của mình, và đó là việc cần vài lần thử mới làm được. Nguồn để ôn thì mình khuyên dùng tài liệu chính thống, như phần Learn của React và hướng dẫn JavaScript của MDN, vì người phỏng vấn thường hỏi theo đúng cách mà tài liệu giải thích. Vài câu trong danh sách cũng có sẵn trong ngân hàng câu hỏi của blog, ví dụ let, const và var hay làm sao biết một unit test có tác dụng.

Nhóm thứ ba là code trực tiếp. Phần này không ôn bằng cách đọc được, chỉ luyện được bằng cách làm, và Minh dành riêng cho nó một ngày.

Kể về dự án trong hai phút thế nào?

Câu hỏi "kể về dự án của bạn" gần như chắc chắn xuất hiện, và nó thường là câu đầu tiên. Minh viết sẵn câu trả lời vào file cau-chuyen-du-an.md, không phải để học thuộc mà để biết mình định nói những ý nào, theo thứ tự nào:

cau-chuyen-du-an.md (minh họa)
# Câu chuyện dự án, 2 phút (minh họa: Minh)

Vấn đề: Em hay quên ghi chi tiêu vì mở app ghi chép mất nhiều bước. Em làm một app cho phép ghi
bằng một tin nhắn, ví dụ "cà phê 35k", và xem tổng tháng bằng lệnh /tong.

Em làm gì: Em viết bằng Next.js và TypeScript. Phần đọc số tiền là phần khó nhất, vì người dùng
viết "35k", "35.000", "1tr2" hay "35 nghìn". Em viết 21 unit test cho phần này trước khi nối giao diện.

Quyết định kỹ thuật (chọn 1 nếu bị hỏi sâu):
1. Tách hàm đọc tin nhắn ra khỏi giao diện, để test được mà không cần trình duyệt.
2. Tin nhắn có nhiều hơn một số tiền thì từ chối và hỏi lại, thay vì đoán.
3. Tính tháng theo giờ Việt Nam, vì máy chủ chạy giờ UTC.

Lỗi khó nhất: Một khoản chi lúc 6:30 sáng ngày 1/12 bị tính vào tháng 11. Trên máy em thì đúng,
lên máy chủ thì sai, vì máy chủ chạy giờ UTC, mà 6:30 sáng ở Việt Nam là 23:30 hôm trước theo UTC.
Em tái hiện bằng cách chạy test với TZ=UTC, rồi sửa để luôn tính tháng theo giờ Việt Nam.

Nếu làm lại: Em sẽ lưu dữ liệu vào database ngay từ đầu, thay vì giữ trong bộ nhớ.

Khung này có năm phần, và mỗi phần trả lời một câu mà người phỏng vấn sẽ hỏi tiếp nếu bạn không nói. Phần "vấn đề" cho biết vì sao dự án tồn tại. Phần "em làm gì" cho biết phần nào do chính bạn làm.

Phần "quyết định kỹ thuật" là chỗ để người phỏng vấn đào sâu, và Minh chuẩn bị ba quyết định để họ chọn. Phần "lỗi khó nhất" cho thấy cách bạn xử lý khi mọi thứ không chạy. Phần "nếu làm lại" cho thấy bạn tự nhìn ra điểm yếu.

Phần lỗi khó nhất đáng được chuẩn bị kỹ nhất. Một câu chuyện lỗi tốt có triệu chứng cụ thể ("6:30 sáng ngày 1/12 bị tính vào tháng 11"), nguyên nhân giải thích được, cách tái hiện, và cách sửa. Nếu bạn chỉ nói "em gặp lỗi múi giờ và đã sửa", người phỏng vấn không phân biệt được bạn với một người chỉ chép bản sửa trên mạng.

Ngày thứ hai của tuần ôn, Minh làm thêm một việc tưởng thừa. Minh mở lại code của dự án, chạy lại test, và đọc lại README như một người lạ. Dự án làm xong từ vài tuần trước, và chi tiết thì quên rất nhanh. Bị hỏi "hàm đọc số tiền xử lý '1tr2' thế nào" mà phải mở code ra mới nhớ là một khoảnh khắc khó chịu, và nó hoàn toàn tránh được. Câu hỏi kể về một dự án trong CV trong ngân hàng câu hỏi có thêm gợi ý cho phần này.

Luyện code trực tiếp khi chưa biết đề?

Minh không biết bài code 20 phút sẽ là gì. Nhưng với một vị trí junior frontend ở một công ty làm ứng dụng quản lý chi tiêu, khả năng cao là một bài xử lý dữ liệu nhỏ: lọc, nhóm, sắp xếp một danh sách. Minh tự đặt một bài đúng cỡ đó, bấm giờ 20 phút, và vừa làm vừa nói to mình đang nghĩ gì.

Đề bài là cho một danh sách khoản chi, mỗi khoản có ngày và số tiền, rồi trả về tổng tiền từng tháng, tháng mới nhất đứng trước. Đây là bản Minh viết lần đầu, kèm dữ liệu thử ở cuối:

bai-luyen/ban-dau.ts
// Bản Minh viết lần đầu, trong 20 phút bấm giờ. Chạy: node bai-luyen/ban-dau.ts
type KhoanChi = { ngay: string; soTien: number };

function tongTheoThang(khoanChi: KhoanChi[]) {
  const tong: Record<number, number> = {};
  for (const { ngay, soTien } of khoanChi) {
    const thang = new Date(ngay).getMonth() + 1; // chỉ lấy số tháng
    tong[thang] = (tong[thang] ?? 0) + soTien;
  }
  return tong;
}

// Hai khoản ở tháng 12 của hai năm khác nhau, và một khoản tháng 1.
console.log(tongTheoThang([
  { ngay: "2025-12-31", soTien: 10000 },
  { ngay: "2026-01-01", soTien: 20000 },
  { ngay: "2026-12-05", soTien: 30000 },
]));
node bai-luyen/ban-dau.ts
{ '1': 20000, '12': 40000 }

Kết quả có tháng 12 với 40.000đ, tức khoản tháng 12 năm 2025 và khoản tháng 12 năm 2026 bị cộng chung, vì khóa của bảng chỉ là số tháng. Thứ tự cũng không theo đề bài, vì object trong JavaScript tự xếp các khóa dạng số theo thứ tự tăng dần. Minh hết 20 phút mà không nhận ra lỗi nào, vì chỉ thử với dữ liệu trong cùng một năm.

Bài học của buổi luyện không phải là cú pháp. Nó là thói quen hỏi lại đề trước khi code. Một câu "dữ liệu có thể trải qua nhiều năm không ạ?" ở phút đầu tiên sẽ tránh được cả hai lỗi. Trong phòng phỏng vấn thật, câu hỏi đó còn cho người phỏng vấn thấy cách bạn nghĩ, và đó là điều họ chấm nhiều hơn là việc code chạy ngay lần đầu.

Bản sửa dùng chuỗi "năm-tháng" làm khóa, và Minh viết thêm test cho đúng trường hợp đã làm sai:

bai-luyen/tong-theo-thang.ts
// Bài luyện 20 phút: cho danh sách khoản chi, trả về tổng tiền từng tháng, tháng mới nhất trước.
// Minh tự đặt bài này vì nó gần với sản phẩm của công ty A (ứng dụng quản lý chi tiêu).
export type KhoanChi = { ngay: string; soTien: number }; // ngay dạng "2026-11-03"

export function tongTheoThang(khoanChi: KhoanChi[]): { thang: string; tong: number }[] {
  const tong = new Map<string, number>();
  for (const { ngay, soTien } of khoanChi) {
    const thang = ngay.slice(0, 7); // "2026-11-03" → "2026-11"
    tong.set(thang, (tong.get(thang) ?? 0) + soTien);
  }
  // Chuỗi "YYYY-MM" so sánh theo thứ tự chữ cũng đúng thứ tự thời gian, nên sắp xếp chuỗi là đủ.
  return [...tong].map(([thang, tong]) => ({ thang, tong })).sort((a, b) => b.thang.localeCompare(a.thang));
}
bai-luyen/tong-theo-thang.test.ts
// Chạy: node bai-luyen/tong-theo-thang.test.ts   (Node.js 22.18+, không cần cài gì)
import assert from "node:assert/strict";
import { test } from "node:test";

import { tongTheoThang } from "./tong-theo-thang.ts";

test("danh sách rỗng thì không có tháng nào", () => {
  assert.deepEqual(tongTheoThang([]), []);
});

test("cộng các khoản cùng tháng, tháng mới nhất đứng trước", () => {
  const ketQua = tongTheoThang([
    { ngay: "2026-10-30", soTien: 50000 },
    { ngay: "2026-11-03", soTien: 35000 },
    { ngay: "2026-11-15", soTien: 32000 },
  ]);
  assert.deepEqual(ketQua, [
    { thang: "2026-11", tong: 67000 },
    { thang: "2026-10", tong: 50000 },
  ]);
});

test("tháng 12 năm trước đứng sau tháng 1 năm sau", () => {
  const ketQua = tongTheoThang([
    { ngay: "2025-12-31", soTien: 10000 },
    { ngay: "2026-01-01", soTien: 20000 },
  ]);
  assert.deepEqual(ketQua.map((dong) => dong.thang), ["2026-01", "2025-12"]);
});
node bai-luyen/tong-theo-thang.test.ts (các dòng kết quả)
ok 1 - danh sách rỗng thì không có tháng nào
ok 2 - cộng các khoản cùng tháng, tháng mới nhất đứng trước
ok 3 - tháng 12 năm trước đứng sau tháng 1 năm sau
# pass 3
# fail 0

Bản sửa lấy tháng bằng cách cắt chuỗi thay vì dùng new Date. Ngày ở đây đã có dạng "2026-11-03", nên cắt chuỗi vừa đơn giản vừa tránh được chuyện múi giờ, đúng loại lỗi mà Minh đã gặp trong dự án. Mỗi test có một lý do: danh sách rỗng là trường hợp biên ai cũng nên thử, test thứ hai kiểm tra phép cộng và thứ tự, còn test thứ ba khóa đúng lỗi của bản đầu.

Minh làm hai bài như vậy trong ngày Chủ Nhật. Điều Minh nói với mình sau buổi đó là việc nói to khó hơn việc code. Lúc im lặng gõ thì quen, còn vừa gõ vừa giải thích thì Minh liên tục quên mình đang ở dòng nào. Đó chính là lý do phải luyện trước. Phần code trực tiếp sẽ còn được nói kỹ ở các bài viết sau.

Lịch bảy ngày trông thế nào?

Đây là lịch mà Minh dán lên bàn. Mỗi ngày khoảng hai giờ, vì Minh vẫn đang làm một công việc bán thời gian, và mình nghĩ hai giờ tập trung mỗi ngày đáng giá hơn một ngày cuối học tám giờ.

ke-hoach-7-ngay.md (minh họa)
# Kế hoạch 7 ngày trước buổi phỏng vấn kỹ thuật (minh họa: Minh, công ty A)

Buổi phỏng vấn: thứ Năm, 60 phút, online. Mỗi ngày khoảng 2 giờ.

Thứ Năm (hôm nhận email)
- Trả lời email xác nhận giờ. Hỏi: ai phỏng vấn, có code trực tiếp không, dùng công cụ gì.
- Chép yêu cầu của tin A vào `danh-sach-on-tap.md`, chia ba nhóm.

Thứ Sáu — dự án của mình
- Viết `cau-chuyen-du-an.md`: 2 phút giới thiệu, 3 quyết định kỹ thuật, 1 lỗi khó đã sửa.
- Mở lại code, chạy lại test, đọc lại README như người lạ.

Thứ Bảy — kiến thức nền, phần 1
- 8 câu trong `danh-sach-on-tap.md` (JavaScript, TypeScript). Nói to câu trả lời, ghi âm, nghe lại.

Chủ Nhật — code trực tiếp
- 2 bài 20 phút, bấm giờ, vừa làm vừa nói to. Viết test sau mỗi bài.

Thứ Hai — kiến thức nền, phần 2
- 7 câu còn lại (React, Git, REST, test). Câu nào nói vấp thì viết lại 3 dòng tóm tắt.

Thứ Ba — phỏng vấn thử
- Nhờ một người bạn hỏi 30 phút: 1 câu giới thiệu, 4 câu kiến thức, 1 bài code nhỏ.
- Ghi lại 3 chỗ trả lời chưa tốt, ôn lại đúng 3 chỗ đó.

Thứ Tư — chuẩn bị hậu cần, nghỉ sớm
- Thử link họp, micro, camera, chia sẻ màn hình, mở sẵn trình soạn code.
- Viết `cau-hoi-hoi-lai.md`. Không học thêm gì mới.

Có ba chỗ trong lịch mà mình muốn giải thích, vì chúng hay bị bỏ.

Thứ nhất là ghi âm. Nghe lại giọng mình thì ngại, nhưng đó là cách nhanh nhất để biết mình nói "ờ" bao nhiêu lần, hay trả lời một câu đơn giản mất ba phút. Minh phát hiện câu về useEffect mình nói vòng vo gần hai phút mà chưa tới ý chính.

Thứ hai là phỏng vấn thử với người thật. Một người bạn cùng học là đủ, họ không cần giỏi hơn bạn. Mục đích không phải là để họ chấm đúng sai, mà để bạn quen với cảm giác bị nhìn khi đang nghĩ. Ghi lại đúng ba chỗ chưa tốt và chỉ ôn ba chỗ đó, thay vì ôn lại tất cả.

Thứ ba là ngày cuối không học thêm gì mới. Kiến thức nhồi vào tối hôm trước hiếm khi dùng được, còn một đêm ngủ đủ thì chắc chắn có ích. Ngày cuối dành cho hậu cần. Một buổi phỏng vấn online có thể hỏng vì micro không nhận, vì trình duyệt chặn chia sẻ màn hình, hay vì link họp yêu cầu cài phần mềm, và tất cả đều kiểm tra được từ hôm trước.

Cuối buổi, hỏi lại nhà tuyển dụng điều gì?

Gần như buổi phỏng vấn nào cũng kết thúc bằng câu "bạn có câu hỏi gì cho chúng tôi không?". Trả lời "dạ không ạ" không sai, nhưng bỏ phí một cơ hội. Câu hỏi của bạn cho thấy bạn quan tâm tới đ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. Minh chuẩn bị sẵn năm câu, định chọn hai hoặc ba tùy thời gian còn lại:

cau-hoi-hoi-lai.md (minh họa)
# Câu hỏi để hỏi lại nhà tuyển dụng (chọn 2–3, minh họa cho tin A)

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 frontend review code thế nào: ai review, mất bao lâu thì một PR được merge?

Về việc học
- Người hướng dẫn trong 2 tháng đầu gặp em 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?

Về quy trình
- Sau buổi hôm nay còn vòng nào nữa, và khi nào em có thể nhận phản hồi?

Mỗi câu đều gắn với một chi tiết trong tin A. Tin ghi "có người hướng dẫn trong 2 tháng đầu", nên Minh hỏi người đó gặp mình bao lâu một lần, vì một người hướng dẫn chỉ có tên trên giấy thì không giúp được gì. Tin ghi "tham gia review code cùng nhóm 4 frontend", nên Minh hỏi review diễn ra thế nào. Câu "một bạn junior làm tốt trông như thế nào" là câu mình thích nhất, vì câu trả lời chính là tiêu chí bạn sẽ được đánh giá khi hết thử việc.

Có hai loại câu nên tránh ở vòng này. Một là những câu mà tin tuyển dụng đã trả lời, vì nó cho thấy bạn chưa đọc kỹ. Hai là câu hỏi về lương ở vòng kỹ thuật, khi người đối diện là trưởng nhóm chứ không phải bộ phận tuyển dụng. Chuyện lương có lúc và có người phù hợp hơn để hỏi.

Sáng thứ Năm, một trình soạn code trống

Thứ Năm, Minh vào buổi họp sớm năm phút. Phần giới thiệu dự án trôi chảy hơn Minh nghĩ, vì Minh đã nói nó to lên tới bốn lần trong tuần. Hai câu hỏi kiến thức nằm đúng trong danh sách 15 câu, một câu thì không, và Minh trả lời được một nửa rồi nói thật là phần còn lại mình chưa chắc.

Rồi trưởng nhóm frontend chia sẻ màn hình. Trên đó là một trình soạn code online, một đoạn mô tả đề bài dài bốn dòng, và một đồng hồ đếm ngược 20 phút. Người phỏng vấn nói: "Em cứ vừa làm vừa nói to nhé, anh muốn nghe em nghĩ". Vượt qua 20 phút đó thế nào, từ lúc đọc đề tới lúc viết dòng code cuối, là chủ đề của các bài viết sau.

Bạn theo dõi cả hành trình ở series Hành trình junior. Nếu muốn luyện trước, câu giới thiệu bản thân trong một phút và các câu khác có trong ngân hàng câu hỏi phỏng vấn.

Nhận bài tiếp theo qua email.

Một email mỗi tuần, chỉ khi có điều đáng đọc. Không spam.