Việc làm

28/09 · Hồ Chí Minh · Tại văn phòng

Junior Backend Developer (PHP/ NodeJS/ Ruby on Rails)

Motorist Pte Ltd · Backend Developer

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

10 câu hỏi có thể gặp khi phỏng vấn

Chọn theo kỹ năng và cấp của tin (Node.js, SQL & Database, Cloud (AWS, Azure, GCP)). Gợi ý trả lời do Drama Công Ty biên soạn để bạn ôn nhanh, không phải đề của công ty.

  1. 01CommonJS và ES Modules khác nhau thế nào? Dự án mới nên dùng loại nào?Node.js · Junior

    Gợi ý trả lời

    CommonJS dùng require/module.exports, nạp đồng bộ lúc chạy. ES Modules dùng import/export, là chuẩn của ngôn ngữ, được phân tích tĩnh nên hỗ trợ tree-shaking và top-level await.

    • Bật ESM bằng "type": "module" trong package.json hoặc đuôi .mjs.
    • Trong ESM không có sẵn __dirname; dùng import.meta.dirname (Node mới) hoặc tính từ import.meta.url.
    • ESM import được CommonJS. Chiều ngược lại trước đây phải dùng import() động; các bản Node gần đây đã cho require() module ESM đồng bộ (trừ module có top-level await).

    Dự án mới nên dùng ESM, vì cả hệ sinh thái đang chuyển dần sang đó.

    Hay sai: khi viết ESM thuần, quên ghi đuôi file trong đường dẫn import tương đối (./utils.js), dẫn đến lỗi không tìm thấy module.

    Ôn thêm câu hỏi Node.js →
  2. 02Middleware trong Express là gì? Viết error-handling middleware thế nào?Node.js · Junior

    Gợi ý trả lời

    Middleware là hàm (req, res, next) chạy theo thứ tự đăng ký. Mỗi middleware có thể sửa req/res, trả response luôn, hoặc gọi next() để chuyển tiếp. Ví dụ: parse JSON, log, xác thực, giới hạn tần suất.

    Error-handling middleware có bốn tham số và đặt sau các route:

    app.use((err, req, res, next) => {
      logger.error(err);
      const status = err.status ?? 500;
      res.status(status).json({ message: status < 500 ? err.message : "Internal error" });
    });
    
    • Gọi next(err) để nhảy thẳng tới error handler.
    • Express 5 tự chuyển Promise bị reject trong handler async sang error handler; Express 4 thì phải tự try/catch rồi gọi next(err).

    Hay sai: quên gọi next() và cũng không trả response, khiến request treo đến khi timeout. Hoặc gửi response hai lần, gây lỗi "headers already sent".

    Ôn thêm câu hỏi Node.js →
  3. 03Xử lý lỗi trong code bất đồng bộ của Node thế nào? Chuyện gì xảy ra với Promise bị reject mà không ai bắt?Node.js · Junior

    Gợi ý trả lời

    Với async/await, dùng try/catch như code đồng bộ. Với Promise, dùng .catch(). Lỗi ném trong callback kiểu cũ thì không bắt được bằng try/catch bên ngoài, nên callback dùng quy ước (err, result).

    • Từ Node 15, Promise bị reject mà không có handler (unhandled rejection) sẽ làm process thoát.
    • process.on("uncaughtException") chỉ nên dùng để log rồi thoát; trạng thái app lúc đó không còn đáng tin.
    async function getUser(id) {
      try {
        return await db.users.findById(id);
      } catch (err) {
        throw new Error("Load user failed", { cause: err });
      }
    }
    

    Hay sai: gọi hàm async mà không await hay .catch() ("fire and forget"). Lỗi xảy ra sau đó có thể làm sập process. Lỗi khác hay gặp: return promise thay vì return await promise bên trong try, khiến catch không bắt được lỗi.

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

    Gợi ý trả lời

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

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

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

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

    Ôn thêm câu hỏi SQL & Database →
  5. 05Transaction là gì? Giải thích ACID.SQL & Database · Junior

    Gợi ý trả lời

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

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

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

    Ôn thêm câu hỏi SQL & Database →
  6. 06Object storage (S3, Blob Storage, Cloud Storage) khác block storage và file storage thế nào?Cloud (AWS, Azure, GCP) · Junior

    Gợi ý trả lời

    Object storage lưu file dạng đối tượng truy cập qua HTTP API, dung lượng gần như không giới hạn và rẻ; block storage là ổ đĩa gắn vào máy ảo; file storage là ổ mạng dùng chung qua NFS/SMB.

    • Object: ảnh, video, backup, data lake, static website. Không sửa một phần file được, phải ghi đè cả object.
    • Block (EBS, Managed Disk, Persistent Disk): ổ hệ điều hành, database tự quản; độ trễ thấp.
    • File (EFS, Azure Files, Filestore): nhiều máy cùng đọc ghi một thư mục.

    Hay sai: để bucket public để tiện chia sẻ. Nên giữ bucket private, chia sẻ bằng presigned URL hoặc CDN có kiểm soát.

    Ôn thêm câu hỏi Cloud (AWS, Azure, GCP) →
  7. 07Event loop của Node gồm những phase nào? process.nextTick, Promise, setImmediate và setTimeout chạy theo thứ tự nào?Node.js

    Gợi ý trả lời

    Event loop của Node (libuv) đi qua các phase chính: timers (setTimeout, setInterval), poll (chờ và xử lý I/O), check (setImmediate), close callbacks.

    Giữa các callback, Node chạy hết hàng đợi process.nextTick trước, rồi đến microtask của Promise.

    setTimeout(() => console.log("timeout"));
    setImmediate(() => console.log("immediate"));
    Promise.resolve().then(() => console.log("promise"));
    process.nextTick(() => console.log("nextTick"));
    // nextTick, promise, rồi timeout/immediate
    
    • Thứ tự giữa setTimeout(0) và setImmediate ở module chính là không chắc chắn; nhưng nếu cả hai được đặt trong một callback I/O thì setImmediate luôn chạy trước.

    Hay sai: gọi process.nextTick đệ quy, khiến event loop không bao giờ sang được phase I/O.

    Ôn thêm câu hỏi Node.js →
  8. 08Stream trong Node là gì? Backpressure là gì? Làm sao xử lý file vài GB mà không hết RAM?Node.js

    Gợi ý trả lời

    Stream xử lý dữ liệu theo từng chunk thay vì đọc hết vào bộ nhớ. Có bốn loại: Readable, Writable, Duplex, Transform.

    Backpressure: khi bên ghi chậm hơn bên đọc, dữ liệu dồn lại trong bộ đệm. write() trả về false để báo nên dừng đọc cho đến khi có sự kiện drain.

    Dùng pipeline để tự xử lý backpressure, lỗi và dọn dẹp:

    import { pipeline } from "node:stream/promises";
    import { createGzip } from "node:zlib";
    
    await pipeline(
      fs.createReadStream("big.csv"),
      createGzip(),
      fs.createWriteStream("big.csv.gz"),
    );
    

    Ứng dụng: upload/download file lớn, xuất CSV từ DB theo con trỏ, proxy dữ liệu.

    Hay sai: dùng fs.readFile cho file lớn, hoặc .pipe() nối nhiều bước mà không bắt lỗi ở từng stream, khiến file descriptor bị rò.

    Ôn thêm câu hỏi Node.js →
  9. 09Một endpoint làm tác vụ nặng CPU (xử lý ảnh, tạo báo cáo) làm cả server chậm theo. Bạn xử lý thế nào?Node.js

    Gợi ý trả lời

    Tác vụ CPU chạy trên main thread sẽ chặn event loop, nên mọi request khác phải chờ. Có vài hướng:

    • worker_threads: chạy tính toán ở thread khác trong cùng process. Nên dùng một pool worker (ví dụ thư viện Piscina) thay vì tạo worker mới cho mỗi request.
    • Hàng đợi công việc: API chỉ nhận yêu cầu, đẩy job vào queue (BullMQ trên Redis, RabbitMQ), trả về 202 kèm mã job; worker riêng xử lý, client hỏi lại trạng thái hoặc nhận thông báo.
    • Tách service: nếu việc nặng là trọng tâm, viết bằng ngôn ngữ phù hợp hơn hoặc dùng dịch vụ chuyên dụng.

    cluster hay PM2 chạy nhiều process giúp tận dụng nhiều core, nhưng không giải quyết việc một request nặng chặn process của nó.

    Người phỏng vấn muốn nghe: bạn đo được độ trễ event loop (perf_hooks.monitorEventLoopDelay) để phát hiện vấn đề này.

    Ôn thêm câu hỏi Node.js →
  10. 10Vì sao bạn muốn rời công ty hiện tại?Câu hỏi hành vi & văn hoá

    Gợi ý trả lời

    Trả lời thật nhưng hướng về phía trước: nói điều bạn đang tìm kiếm, không kể xấu nơi cũ.

    Lý do chấp nhận được: muốn thử thách mới, muốn làm sản phẩm, dự án kết thúc, công ty tái cấu trúc, thu nhập chưa tương xứng (nói khéo).

    Câu mẫu: "Em đã làm 2 năm ở dự án outsourcing, học được nhiều về quy trình và làm việc với khách nước ngoài. Giờ em muốn gắn bó lâu dài với một sản phẩm để thấy được tác động của những gì mình làm, và vị trí bên mình đúng hướng đó."

    Hay sai: chê sếp, chê đồng nghiệp, kể chuyện drama. Người phỏng vấn sẽ nghĩ bạn cũng sẽ nói về công ty họ như vậy. Nếu nhảy việc nhiều, chuẩn bị lời giải thích cho từng lần.

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

Bạn đã phỏng vấn ở Motorist Pte Ltd?

Kể lại các vòng và câu hỏi đã gặp (ẩn danh) để người sau ôn đúng trọng tâm.

Chia sẻ kinh nghiệm phỏng vấn