คำถามสัมภาษณ์ Backend Node.js: คู่มือฉบับสมบูรณ์ 2026

25 คำถามสัมภาษณ์ Backend Node.js ที่พบบ่อยที่สุด Event loop, async/await, streams, clustering และประสิทธิภาพอธิบายพร้อมคำตอบโดยละเอียด

คำถามสัมภาษณ์ Backend Node.js - คู่มือฉบับสมบูรณ์

การสัมภาษณ์ Backend Node.js ประเมินความเข้าใจเกี่ยวกับกลไกภายในของ runtime ความชำนาญในรูปแบบ asynchronous และความสามารถในการออกแบบแอปพลิเคชันที่มีประสิทธิภาพสูง คู่มือนี้ครอบคลุมคำถามที่ถูกถามบ่อยที่สุด ตั้งแต่พื้นฐานจนถึงแนวคิดระดับ production ขั้นสูง

เคล็ดลับการสัมภาษณ์

ผู้สัมภาษณ์จะประทับใจกับคำตอบที่ผสมผสานทฤษฎีกับตัวอย่างจริง สำหรับแต่ละคำถาม การยกตัวอย่างโค้ดหรือกรณีใช้งานจริงแสดงให้เห็นถึงประสบการณ์ในโลกจริง

พื้นฐาน Node.js

คำถามที่ 1: Event Loop คืออะไรและทำงานอย่างไร?

Event Loop คือกลไกหลักที่ทำให้ Node.js สามารถจัดการการทำงานแบบ asynchronous ในลักษณะ non-blocking แม้จะทำงานบน thread เดียว กลไกนี้จัดเรียงการทำงานของโค้ด JavaScript, callback และ event

event-loop-demo.jsjavascript
// Demonstration of Event Loop execution order

console.log('1. Script start (synchronous)');

// setTimeout goes to the Timer Queue
setTimeout(() => {
  console.log('5. setTimeout callback (Timer Queue)');
}, 0);

// setImmediate goes to the Check Queue
setImmediate(() => {
  console.log('6. setImmediate callback (Check Queue)');
});

// Promise goes to the Microtask Queue (priority)
Promise.resolve().then(() => {
  console.log('3. Promise.then (Microtask Queue)');
});

// process.nextTick has the highest priority
process.nextTick(() => {
  console.log('2. process.nextTick (nextTick Queue)');
});

console.log('4. Script end (synchronous)');

// Output order: 1, 4, 2, 3, 5, 6

Event Loop ปฏิบัติตามลำดับที่แน่นอนในการประมวลผลคิว: โค้ดแบบ synchronous ก่อน จากนั้น nextTick, microtask (Promise), timer, callback I/O, setImmediate และสุดท้ายคือ close callback

คำถามที่ 2: ความแตกต่างระหว่าง process.nextTick() และ setImmediate() คืออะไร?

คำถามนี้ประเมินความเข้าใจอย่างละเอียดเกี่ยวกับลำดับความสำคัญของการทำงานใน Event Loop

nextTick-vs-immediate.jsjavascript
// Behavior comparison

// process.nextTick executes BEFORE the next Event Loop phase
process.nextTick(() => {
  console.log('nextTick 1');
  process.nextTick(() => {
    console.log('nextTick 2 (nested)');
  });
});

// setImmediate executes in the Check phase of the Event Loop
setImmediate(() => {
  console.log('setImmediate 1');
  setImmediate(() => {
    console.log('setImmediate 2 (nested)');
  });
});

// Output: nextTick 1, nextTick 2, setImmediate 1, setImmediate 2

process.nextTick() จะถูกประมวลผลทันทีหลังจากการทำงานปัจจุบัน ก่อนที่ Event Loop จะดำเนินต่อ การใช้มากเกินไปอาจทำให้ Event Loop ถูกบล็อก setImmediate() คาดเดาได้ง่ายกว่าและแนะนำให้ใช้สำหรับการเลื่อนการทำงาน

ระวัง Starvation

การเรียก process.nextTick() แบบ recursive อาจทำให้ Event Loop ขาดแคลนและป้องกันการประมวลผล I/O ควรใช้ setImmediate() สำหรับการทำงานที่ไม่สำคัญ

คำถามที่ 3: Node.js จัดการ error ในโค้ด asynchronous อย่างไร?

การจัดการ error แบบ asynchronous แตกต่างอย่างมากจากโค้ดแบบ synchronous หากไม่จัดการอย่างเหมาะสม error สามารถทำให้แอปพลิเคชันล่มได้

error-handling.jsjavascript
// Asynchronous error handling patterns

// Pattern 1: Callbacks with error-first convention
function readFileCallback(path, callback) {
  const fs = require('fs');
  fs.readFile(path, 'utf8', (err, data) => {
    if (err) {
      // Error is ALWAYS the first argument
      return callback(err, null);
    }
    callback(null, data);
  });
}

// Pattern 2: Promises with catch
async function readFilePromise(path) {
  const fs = require('fs').promises;
  try {
    const data = await fs.readFile(path, 'utf8');
    return data;
  } catch (err) {
    // Centralized error handling
    console.error(`File read error: ${err.message}`);
    throw err; // Re-throw for propagation
  }
}

// Pattern 3: Global handling of unhandled rejections
process.on('unhandledRejection', (reason, promise) => {
  console.error('Unhandled rejection:', reason);
  // In production: log and graceful shutdown
});

// Pattern 4: Handling uncaught exceptions
process.on('uncaughtException', (err) => {
  console.error('Uncaught exception:', err);
  // CRITICAL: always terminate the process after
  process.exit(1);
});

ในสภาพแวดล้อม production ทุก Promise ต้องมี .catch() หรืออยู่ภายในบล็อก try/catch ตัวจัดการ global ทำหน้าที่เป็นตาข่ายนิรภัย ไม่ใช่วิธีแก้ปัญหาหลัก

การเขียนโปรแกรมแบบ Asynchronous และ Concurrency

คำถามที่ 4: อธิบายความแตกต่างระหว่าง Parallelism และ Concurrency ใน Node.js

Node.js มีคุณสมบัติ concurrent แต่ไม่ใช่ parallel โดยค่าเริ่มต้น ความแตกต่างนี้เป็นพื้นฐานสำคัญในการทำความเข้าใจประสิทธิภาพ

concurrency-vs-parallelism.jsjavascript
// CONCURRENCY: multiple tasks progress by alternating (single-thread)
async function concurrentTasks() {
  console.time('concurrent');

  // These calls are concurrent, not parallel
  const results = await Promise.all([
    fetch('https://api.example.com/users'),    // Non-blocking I/O
    fetch('https://api.example.com/products'), // Non-blocking I/O
    fetch('https://api.example.com/orders'),   // Non-blocking I/O
  ]);

  console.timeEnd('concurrent'); // ~time of the longest request
  return results;
}

// PARALLELISM: with Worker Threads for CPU-bound tasks
const { Worker, isMainThread, parentPort } = require('worker_threads');

if (isMainThread) {
  // Main thread delegates CPU-intensive work
  async function parallelComputation() {
    console.time('parallel');

    const workers = [
      createWorker({ start: 0, end: 1000000 }),
      createWorker({ start: 1000000, end: 2000000 }),
      createWorker({ start: 2000000, end: 3000000 }),
    ];

    const results = await Promise.all(workers);
    console.timeEnd('parallel');
    return results.reduce((a, b) => a + b, 0);
  }

  function createWorker(data) {
    return new Promise((resolve, reject) => {
      const worker = new Worker(__filename, { workerData: data });
      worker.on('message', resolve);
      worker.on('error', reject);
    });
  }
} else {
  // Code executed in the Worker Thread
  const { workerData } = require('worker_threads');
  let sum = 0;
  for (let i = workerData.start; i < workerData.end; i++) {
    sum += Math.sqrt(i); // CPU-intensive calculation
  }
  parentPort.postMessage(sum);
}

สำหรับการทำงานที่ผูกกับ I/O (เครือข่าย, ไฟล์) concurrency ที่มีอยู่ก็เพียงพอ สำหรับงานที่ผูกกับ CPU (การคำนวณหนัก, การเข้ารหัส) Worker Threads ทำให้สามารถทำงานแบบ parallel ได้จริง

คำถามที่ 5: โมดูล Cluster ทำงานอย่างไร?

โมดูล Cluster ช่วยให้สร้าง process Node.js หลายตัวที่ใช้ port เดียวกันร่วมกัน จึงใช้ประโยชน์จาก CPU core ทั้งหมดที่มี

cluster-example.jsjavascript
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;

if (cluster.isPrimary) {
  console.log(`Primary ${process.pid} is running`);
  console.log(`Forking ${numCPUs} workers...`);

  // Fork one worker per CPU core
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }

  // Handle crashing workers
  cluster.on('exit', (worker, code, signal) => {
    console.log(`Worker ${worker.process.pid} died (${signal || code})`);
    console.log('Starting a new worker...');
    cluster.fork(); // Automatic restart
  });

  // Inter-process communication
  cluster.on('message', (worker, message) => {
    console.log(`Message from worker ${worker.id}:`, message);
  });

} else {
  // Workers share the TCP port
  http.createServer((req, res) => {
    res.writeHead(200);
    res.end(`Handled by worker ${process.pid}\n`);

    // Send stats to primary
    process.send({ type: 'request', pid: process.pid });
  }).listen(8000);

  console.log(`Worker ${process.pid} started`);
}

การกระจายโหลดจะถูกจัดการโดยอัตโนมัติโดยระบบปฏิบัติการ (round-robin บน Linux/macOS) ในสภาพแวดล้อม production PM2 ทำให้การจัดการนี้ง่ายขึ้นด้วยโหมด cluster ที่มีอยู่ในตัว

พร้อมที่จะพิชิตการสัมภาษณ์ Node.js / NestJS แล้วหรือยังครับ?

ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ

Streams และ Buffers

คำถามที่ 6: ควรใช้ Streams แทนวิธีแบบดั้งเดิมเมื่อใด?

Streams ช่วยให้ประมวลผลข้อมูลเป็นส่วนย่อย (chunk) แทนที่จะโหลดทั้งหมดลงในหน่วยความจำ สิ่งนี้จำเป็นอย่างยิ่งสำหรับไฟล์ขนาดใหญ่และสถานการณ์ streaming

streams-comparison.jsjavascript
const fs = require('fs');

// ❌ BAD: loads entire file into memory
async function readEntireFile(path) {
  const data = await fs.promises.readFile(path); // Blocks if file > RAM
  return processData(data);
}

// ✅ GOOD: chunk-based processing with Stream
function readWithStream(path) {
  return new Promise((resolve, reject) => {
    const chunks = [];
    const readStream = fs.createReadStream(path, {
      highWaterMark: 64 * 1024, // 64KB per chunk
    });

    readStream.on('data', (chunk) => {
      // Progressive processing, constant memory
      chunks.push(processChunk(chunk));
    });

    readStream.on('end', () => resolve(chunks));
    readStream.on('error', reject);
  });
}

// ✅ BEST: pipeline for chaining transformations
const { pipeline } = require('stream/promises');
const zlib = require('zlib');

async function compressFile(input, output) {
  await pipeline(
    fs.createReadStream(input),   // Source
    zlib.createGzip(),            // Transform
    fs.createWriteStream(output)  // Destination
  );
  // Automatic error handling and backpressure management
}

ควรใช้ Streams เมื่อใดก็ตามที่ขนาดข้อมูลอาจเกินหลาย MB หรือสำหรับการประมวลผลแบบ real-time (อัปโหลด, ล็อก, ข้อมูลเครือข่าย)

คำถามที่ 7: อธิบายแนวคิดของ Backpressure

Backpressure เกิดขึ้นเมื่อผู้ผลิตข้อมูลเร็วกว่าผู้บริโภค หากไม่มีการจัดการ หน่วยความจำจะล้น

backpressure-demo.jsjavascript
const fs = require('fs');

// ❌ Problem: no backpressure handling
function badCopy(src, dest) {
  const readable = fs.createReadStream(src);
  const writable = fs.createWriteStream(dest);

  readable.on('data', (chunk) => {
    // If write() returns false, the internal buffer is full
    // But here reading continues anyway → memory leak
    writable.write(chunk);
  });
}

// ✅ Solution: respect the writable signal
function goodCopy(src, dest) {
  const readable = fs.createReadStream(src);
  const writable = fs.createWriteStream(dest);

  readable.on('data', (chunk) => {
    const canContinue = writable.write(chunk);

    if (!canContinue) {
      // Pause reading until buffer drains
      readable.pause();
    }
  });

  writable.on('drain', () => {
    // Buffer drained, resume reading
    readable.resume();
  });

  readable.on('end', () => writable.end());
}

// ✅ BEST: pipe() handles everything automatically
function bestCopy(src, dest) {
  const readable = fs.createReadStream(src);
  const writable = fs.createWriteStream(dest);

  // pipe() handles backpressure natively
  readable.pipe(writable);
}

เมธอด pipe() หรือ pipeline() จัดการ backpressure โดยอัตโนมัติ สำหรับกรณีที่ซับซ้อน จำเป็นต้องสร้างลอจิก pause/resume เอง

ประสิทธิภาพและการเพิ่มประสิทธิภาพ

คำถามที่ 8: จะระบุและแก้ไข Memory Leak ได้อย่างไร?

Memory leak เป็นปัญหาที่พบบ่อยใน Node.js การรู้วิธีตรวจจับและแก้ไขเป็นสิ่งจำเป็นในสภาพแวดล้อม production

memory-leak-patterns.jsjavascript
// ❌ Leak 1: closures that retain references
function createLeakyHandler() {
  const hugeData = Buffer.alloc(100 * 1024 * 1024); // 100MB

  return function handler(req, res) {
    // hugeData remains in memory as long as handler exists
    res.end('Hello');
  };
}

// ✅ Fix: limit the scope
function createSafeHandler() {
  return function handler(req, res) {
    // Data created and released on each request
    const data = fetchData();
    res.end(data);
  };
}

// ❌ Leak 2: event listeners not cleaned up
class LeakyClass {
  constructor() {
    // Added on each instantiation, never removed
    process.on('message', this.handleMessage);
  }
  handleMessage(msg) { /* ... */ }
}

// ✅ Fix: explicit cleanup
class SafeClass {
  constructor() {
    this.boundHandler = this.handleMessage.bind(this);
    process.on('message', this.boundHandler);
  }
  handleMessage(msg) { /* ... */ }

  destroy() {
    // Mandatory cleanup
    process.removeListener('message', this.boundHandler);
  }
}

// Diagnostics with native tools
function diagnoseMemory() {
  const used = process.memoryUsage();
  console.log({
    heapUsed: `${Math.round(used.heapUsed / 1024 / 1024)}MB`,
    heapTotal: `${Math.round(used.heapTotal / 1024 / 1024)}MB`,
    external: `${Math.round(used.external / 1024 / 1024)}MB`,
    rss: `${Math.round(used.rss / 1024 / 1024)}MB`,
  });
}

// Enable manual garbage collector for testing
// node --expose-gc app.js
if (global.gc) {
  global.gc();
  diagnoseMemory();
}

ในสภาพแวดล้อม production ควรใช้เครื่องมืออย่าง clinic.js, heap snapshot จาก Chrome DevTools หรือโซลูชัน APM (Application Performance Monitoring) เช่น DataDog หรือ New Relic

คำถามที่ 9: จะเพิ่มประสิทธิภาพ API Node.js ได้อย่างไร?

คำถามนี้ประเมินความรู้เกี่ยวกับเทคนิคการเพิ่มประสิทธิภาพในหลายระดับ

performance-optimization.jsjavascript
// 1. CACHING: reduce expensive calls
const NodeCache = require('node-cache');
const cache = new NodeCache({ stdTTL: 300 }); // 5-minute TTL

async function getCachedUser(id) {
  const cacheKey = `user:${id}`;
  let user = cache.get(cacheKey);

  if (!user) {
    user = await db.users.findById(id);
    cache.set(cacheKey, user);
  }

  return user;
}

// 2. CONNECTION POOLING: reuse DB connections
const { Pool } = require('pg');
const pool = new Pool({
  max: 20,                // Max simultaneous connections
  idleTimeoutMillis: 30000,
  connectionTimeoutMillis: 2000,
});

// 3. COMPRESSION: reduce response size
const compression = require('compression');
app.use(compression({
  filter: (req, res) => {
    // Only compress if > 1KB
    return compression.filter(req, res);
  },
  threshold: 1024,
}));

// 4. BATCHING: group operations
async function batchInsert(items) {
  const BATCH_SIZE = 1000;

  for (let i = 0; i < items.length; i += BATCH_SIZE) {
    const batch = items.slice(i, i + BATCH_SIZE);
    await db.items.insertMany(batch);
  }
}

// 5. LAZY LOADING: load on demand
async function getUserWithPosts(userId, includePosts = false) {
  const user = await db.users.findById(userId);

  if (includePosts) {
    user.posts = await db.posts.findByUserId(userId);
  }

  return user;
}

การเพิ่มประสิทธิภาพควรได้รับการชี้นำด้วย profiling ควรวัดผลก่อนเพิ่มประสิทธิภาพเพื่อระบุ bottleneck ที่แท้จริง

กฎ 80/20

80% ของปัญหาด้านประสิทธิภาพมาจาก 20% ของโค้ด ควรใช้ profiling เพื่อระบุพื้นที่วิกฤตเหล่านี้ก่อนที่จะเพิ่มประสิทธิภาพแบบสุ่ม

ความปลอดภัย

คำถามที่ 10: จะปกป้อง API Node.js จากการโจมตีทั่วไปได้อย่างไร?

ความปลอดภัยเป็นหัวข้อสัมภาษณ์ที่พบบ่อย การแสดงความรู้เกี่ยวกับช่องโหว่ OWASP เป็นสิ่งที่คาดหวัง

security-best-practices.jsjavascript
const express = require('express');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const mongoSanitize = require('express-mongo-sanitize');
const xss = require('xss-clean');

const app = express();

// 1. SECURITY HEADERS with Helmet
app.use(helmet());

// 2. RATE LIMITING against brute-force attacks
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100,                  // 100 requests per IP
  message: 'Too many requests, please try again later',
  standardHeaders: true,
  legacyHeaders: false,
});
app.use('/api/', limiter);

// 3. SANITIZATION against NoSQL injections
app.use(mongoSanitize());

// 4. XSS PROTECTION
app.use(xss());

// 5. STRICT INPUT VALIDATION
const { body, validationResult } = require('express-validator');

app.post('/api/users',
  [
    body('email').isEmail().normalizeEmail(),
    body('password').isLength({ min: 8 }).escape(),
    body('name').trim().escape(),
  ],
  (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }
    // Continue processing
  }
);

// 6. SQL INJECTION PROTECTION (with parameters)
async function safeQuery(userId) {
  // ✅ Parameterized query
  const result = await pool.query(
    'SELECT * FROM users WHERE id = $1',
    [userId]
  );
  return result.rows;
}

// ❌ NEVER string concatenation
async function unsafeQuery(userId) {
  // Vulnerable to SQL injection
  const result = await pool.query(
    `SELECT * FROM users WHERE id = ${userId}`
  );
}

ในสภาพแวดล้อม production ควรเพิ่ม: CORS ที่เข้มงวด, HTTPS บังคับ, การบันทึกความปลอดภัย, การหมุนเวียน secret และการตรวจสอบ dependency เป็นประจำ (npm audit)

สถาปัตยกรรมและรูปแบบการออกแบบ

คำถามที่ 11: อธิบาย Repository Pattern ใน Node.js

Repository Pattern แยกการเข้าถึงข้อมูลออกจากกันและช่วยให้การทดสอบและการบำรุงรักษาง่ายขึ้น

repository-pattern.jsjavascript
// Abstract interface (for TypeScript, or documentation)
class UserRepository {
  async findById(id) { throw new Error('Not implemented'); }
  async findByEmail(email) { throw new Error('Not implemented'); }
  async create(userData) { throw new Error('Not implemented'); }
  async update(id, userData) { throw new Error('Not implemented'); }
  async delete(id) { throw new Error('Not implemented'); }
}

// Concrete implementation with Prisma
class PrismaUserRepository extends UserRepository {
  constructor(prisma) {
    super();
    this.prisma = prisma;
  }

  async findById(id) {
    return this.prisma.user.findUnique({ where: { id } });
  }

  async findByEmail(email) {
    return this.prisma.user.findUnique({ where: { email } });
  }

  async create(userData) {
    return this.prisma.user.create({ data: userData });
  }

  async update(id, userData) {
    return this.prisma.user.update({
      where: { id },
      data: userData,
    });
  }

  async delete(id) {
    return this.prisma.user.delete({ where: { id } });
  }
}

// Implementation for testing
class InMemoryUserRepository extends UserRepository {
  constructor() {
    super();
    this.users = new Map();
    this.idCounter = 1;
  }

  async findById(id) {
    return this.users.get(id) || null;
  }

  async create(userData) {
    const user = { id: this.idCounter++, ...userData };
    this.users.set(user.id, user);
    return user;
  }
  // ... other methods
}

// Service using the repository (dependency injection)
class UserService {
  constructor(userRepository) {
    this.userRepository = userRepository;
  }

  async getUser(id) {
    const user = await this.userRepository.findById(id);
    if (!user) throw new Error('User not found');
    return user;
  }
}

รูปแบบนี้ช่วยให้เปลี่ยนการ implement การจัดเก็บข้อมูลได้โดยไม่ต้องแก้ไข business logic

คำถามที่ 12: จะ implement ระบบ Job Queue ได้อย่างไร?

คิวช่วยให้สามารถเลื่อนงานหนักและรับประกันการทำงานที่เชื่อถือได้

job-queue.jsjavascript
const Queue = require('bull');

// Create queue with Redis as backend
const emailQueue = new Queue('email', {
  redis: {
    host: 'localhost',
    port: 6379,
  },
  defaultJobOptions: {
    attempts: 3,          // Number of attempts
    backoff: {
      type: 'exponential',
      delay: 2000,        // Initial delay between attempts
    },
    removeOnComplete: 100, // Keep last 100 completed jobs
  },
});

// Producer: add jobs to the queue
async function sendWelcomeEmail(userId, email) {
  await emailQueue.add('welcome', {
    userId,
    email,
    template: 'welcome',
  }, {
    priority: 1,          // High priority
    delay: 5000,          // 5-second delay
  });
}

// Consumer: process jobs
emailQueue.process('welcome', async (job) => {
  const { userId, email, template } = job.data;

  // Update progress
  job.progress(10);

  const html = await renderTemplate(template, { userId });
  job.progress(50);

  await sendEmail(email, 'Welcome!', html);
  job.progress(100);

  return { sent: true, email };
});

// Event handling
emailQueue.on('completed', (job, result) => {
  console.log(`Job ${job.id} completed:`, result);
});

emailQueue.on('failed', (job, err) => {
  console.error(`Job ${job.id} failed:`, err.message);
});

// Recurring jobs (cron)
emailQueue.add('newsletter', { type: 'weekly' }, {
  repeat: {
    cron: '0 9 * * MON', // Every Monday at 9am
  },
});

Bull กับ Redis เป็นโซลูชันที่ได้รับความนิยมมากที่สุด สำหรับความต้องการที่เรียบง่ายกว่า agenda หรือ bee-queue เป็นทางเลือกที่เบากว่า

คำถามขั้นสูง

คำถามที่ 13: โมดูล Native N-API ทำงานอย่างไร?

N-API ช่วยให้สร้างโมดูล native ด้วย C/C++ โดยมี API ที่เสถียรข้ามเวอร์ชัน Node.js

native-module.cppcpp
// Native module for CPU-intensive calculations

#include <napi.h>

// Synchronous function exposed to JavaScript
Napi::Number Fibonacci(const Napi::CallbackInfo& info) {
  Napi::Env env = info.Env();

  // Argument validation
  if (info.Length() < 1 || !info[0].IsNumber()) {
    Napi::TypeError::New(env, "Number expected")
      .ThrowAsJavaScriptException();
    return Napi::Number::New(env, 0);
  }

  int n = info[0].As<Napi::Number>().Int32Value();

  // Iterative Fibonacci calculation
  long long a = 0, b = 1;
  for (int i = 0; i < n; i++) {
    long long temp = a + b;
    a = b;
    b = temp;
  }

  return Napi::Number::New(env, static_cast<double>(a));
}

// Module initialization
Napi::Object Init(Napi::Env env, Napi::Object exports) {
  exports.Set(
    Napi::String::New(env, "fibonacci"),
    Napi::Function::New(env, Fibonacci)
  );
  return exports;
}

NODE_API_MODULE(native_module, Init)
javascript
// Usage from JavaScript
const native = require('./build/Release/native_module');

// 10x faster than JavaScript equivalent
const result = native.fibonacci(50);

โมดูล native มีประโยชน์สำหรับการคำนวณเชิงลึก การรวม library C/C++ ที่มีอยู่ หรือการเข้าถึง API ระบบ

คำถามที่ 14: อธิบาย V8 Garbage Collector

การเข้าใจ GC ช่วยในการเขียนโค้ดที่ลดการหยุดชั่วคราวและการใช้หน่วยความจำ

gc-optimization.jsjavascript
// V8 GC uses two spaces: Young and Old Generation

// 1. Young Generation: short-lived objects
function shortLivedObjects() {
  for (let i = 0; i < 1000; i++) {
    const temp = { data: i }; // Allocated then collected quickly
  }
  // Minor GC (Scavenge) very fast
}

// 2. Old Generation: objects that survive multiple GCs
const cache = new Map(); // Survives, promoted to Old Generation

// ❌ Problematic pattern: many promoted objects
function createManyLongLived() {
  const objects = [];
  for (let i = 0; i < 100000; i++) {
    objects.push({ id: i, data: new Array(100).fill(0) });
  }
  return objects; // All promoted to Old Gen = slow major GC
}

// ✅ Optimized pattern: object reuse
class ObjectPool {
  constructor(factory, size = 100) {
    this.pool = Array.from({ length: size }, factory);
    this.available = [...this.pool];
  }

  acquire() {
    return this.available.pop() || this.pool[0];
  }

  release(obj) {
    // Reset and return to pool
    Object.keys(obj).forEach(k => obj[k] = null);
    this.available.push(obj);
  }
}

// GC monitoring
const v8 = require('v8');

function getHeapStats() {
  const stats = v8.getHeapStatistics();
  return {
    totalHeap: `${Math.round(stats.total_heap_size / 1024 / 1024)}MB`,
    usedHeap: `${Math.round(stats.used_heap_size / 1024 / 1024)}MB`,
    heapLimit: `${Math.round(stats.heap_size_limit / 1024 / 1024)}MB`,
  };
}

flag --max-old-space-size ช่วยให้เพิ่มขีดจำกัดของ Old Generation สำหรับแอปพลิเคชันที่ใช้หน่วยความจำมาก

คำถามที่ 15: จะ implement Graceful Shutdown ได้อย่างไร?

Graceful shutdown ช่วยให้สามารถจัดการ request ที่กำลังดำเนินอยู่ให้เสร็จและปิดการเชื่อมต่ออย่างถูกต้องก่อนหยุด server

graceful-shutdown.jsjavascript
const http = require('http');

const server = http.createServer((req, res) => {
  // Simulate a long request
  setTimeout(() => {
    res.writeHead(200);
    res.end('Done');
  }, 2000);
});

// Tracking active connections
let connections = new Set();

server.on('connection', (conn) => {
  connections.add(conn);
  conn.on('close', () => connections.delete(conn));
});

// Graceful shutdown function
async function shutdown(signal) {
  console.log(`${signal} received, starting graceful shutdown...`);

  // 1. Stop accepting new connections
  server.close(() => {
    console.log('HTTP server closed');
  });

  // 2. Close idle connections
  for (const conn of connections) {
    conn.end();
  }

  // 3. Close DB connections, queues, etc.
  await Promise.all([
    database.disconnect(),
    redisClient.quit(),
    messageQueue.close(),
  ]);

  // 4. Safety timeout
  setTimeout(() => {
    console.error('Forced shutdown after timeout');
    process.exit(1);
  }, 30000);

  console.log('Graceful shutdown completed');
  process.exit(0);
}

// Listen for termination signals
process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('SIGINT', () => shutdown('SIGINT'));

// Start server
server.listen(3000, () => {
  console.log('Server running on port 3000');
});

ในสภาพแวดล้อม production ที่ใช้ container (Docker, Kubernetes) graceful shutdown มีความสำคัญอย่างยิ่งสำหรับการ deploy แบบ zero-downtime

พร้อมที่จะพิชิตการสัมภาษณ์ Node.js / NestJS แล้วหรือยังครับ?

ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ

คำถามเชิงพฤติกรรม

คำถามที่ 16: อธิบายปัญหาด้านประสิทธิภาพที่เคยแก้ไข

คำถามนี้ประเมินประสบการณ์จริง คำตอบควรจัดโครงสร้างตามรูปแบบ STAR (Situation, Task, Action, Result)

ตัวอย่างคำตอบที่มีโครงสร้าง:

text
Situation: A reporting API was timing out on requests
            exceeding 100,000 records.

Task:       Reduce response time from 45s to under 5s.

Action:
1. Profiling with clinic.js → identified JSON serialization as bottleneck
2. Implemented streaming with Transform streams
3. Database-side pagination
4. Added Redis caching for frequent queries

Result:     Response time reduced to 2s, memory usage decreased by 10x.

คำถามที่ 17: จัดการ dependency และการอัปเดตอย่างไร?

package.json - Versioning best practicesjavascript
{
  "dependencies": {
    // ✅ Exact versions for production
    "express": "4.18.2",

    // ✅ Caret for compatible minor updates
    "lodash": "^4.17.21",

    // ❌ Avoid latest or *
    // "some-lib": "*"
  },
  "devDependencies": {
    // Quality tools
    "npm-check-updates": "^16.0.0"
  },
  "scripts": {
    // Vulnerability check
    "audit": "npm audit --audit-level=moderate",

    // Interactive update
    "update:check": "ncu",
    "update:apply": "ncu -u && npm install"
  },
  "engines": {
    // Specify required Node.js version
    "node": ">=20.0.0"
  }
}

ควรกล่าวถึงการใช้ package-lock.json, Dependabot หรือ Renovate สำหรับระบบอัตโนมัติ และการทดสอบ regression ก่อนการอัปเดตหลักทุกครั้ง

บทสรุป

การสัมภาษณ์ Backend Node.js ประเมินทั้งความเข้าใจทางทฤษฎีเกี่ยวกับกลไกภายในและความสามารถในการแก้ปัญหา production จริง การเชี่ยวชาญ Event Loop, รูปแบบ asynchronous และเทคนิคการเพิ่มประสิทธิภาพเป็นพื้นฐานที่คาดหวังสำหรับตำแหน่ง backend developer ระดับอาวุโส

รายการตรวจสอบการเตรียมตัว

  • ✅ เข้าใจการทำงานของ Event Loop และเฟสต่างๆ
  • ✅ เชี่ยวชาญความแตกต่างระหว่าง callback, Promise และ async/await
  • ✅ รู้จักรูปแบบการจัดการ error แบบ asynchronous
  • ✅ รู้ว่าเมื่อใดควรใช้ Streams เทียบกับวิธีแบบดั้งเดิม
  • ✅ ระบุและแก้ไข memory leak ได้
  • ✅ นำแนวปฏิบัติความปลอดภัย OWASP ที่ดีที่สุดไปใช้
  • ✅ implement clustering และ graceful shutdown
  • ✅ ใช้เครื่องมือ profiling (clinic.js, Chrome DevTools)

เริ่มฝึกซ้อมเลย!

ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ

การเตรียมตัวด้านเทคนิคควรเสริมด้วยโปรเจกต์จริง การสร้าง API สำหรับ production, การมีส่วนร่วมในโปรเจกต์ open source Node.js หรือการแก้โจทย์บนแพลตฟอร์มอย่าง LeetCode ช่วยเสริมสร้างความรู้เหล่านี้ให้แข็งแกร่ง

แท็ก

#nodejs
#interview
#backend
#javascript
#technical interview

แชร์

บทความที่เกี่ยวข้อง