Node.js 백엔드 면접 질문: 완벽 가이드 2026
Node.js 백엔드 면접에서 가장 자주 나오는 25가지 질문. Event loop, async/await, streams, 클러스터링, 성능을 상세한 답변과 함께 설명합니다.

Node.js 백엔드 면접에서는 런타임 내부 메커니즘의 이해, 비동기 패턴의 숙달, 고성능 애플리케이션 설계 능력이 평가됩니다. 이 가이드는 기초부터 프로덕션 환경의 고급 개념까지 가장 자주 출제되는 질문을 다룹니다.
채용 담당자는 이론과 실전 예제를 결합한 답변을 높이 평가합니다. 각 질문에 대해 코드나 구체적인 사용 사례로 설명하면 실무 경험이 전달됩니다.
Node.js 기초
질문 1: Event Loop란 무엇이며 어떻게 동작하는가?
Event Loop는 Node.js가 싱글 스레드로 동작하면서도 비동기 작업을 논블로킹 방식으로 처리할 수 있게 하는 핵심 메커니즘입니다. JavaScript 코드, 콜백, 이벤트의 실행을 조율합니다.
// 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, 6Event Loop는 큐를 처리할 때 정확한 순서를 따릅니다. 먼저 동기 코드, 그 다음 nextTick, 마이크로태스크(Promise), 타이머, I/O 콜백, setImmediate, 마지막으로 닫기 콜백 순입니다.
질문 2: process.nextTick()과 setImmediate()의 차이점은?
이 질문은 Event Loop에서의 실행 우선순위에 대한 세밀한 이해를 평가합니다.
// 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 2process.nextTick()은 현재 작업 직후, Event Loop가 계속되기 전에 처리됩니다. 과도한 사용은 Event Loop를 차단할 수 있습니다. setImmediate()는 더 예측 가능하며 실행 지연에 권장됩니다.
process.nextTick()의 재귀 호출은 Event Loop를 고갈시키고 I/O 처리를 방해할 수 있습니다. 중요하지 않은 작업에는 setImmediate() 사용이 권장됩니다.
질문 3: Node.js는 비동기 코드에서 오류를 어떻게 처리하는가?
비동기 오류 처리는 동기 코드와 근본적으로 다릅니다. 적절한 처리 없이는 오류가 애플리케이션을 크래시시킬 수 있습니다.
// 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);
});프로덕션 환경에서 모든 Promise는 .catch()를 갖거나 try/catch 블록 안에 있어야 합니다. 글로벌 핸들러는 안전망 역할을 하며 주요 솔루션이 아닙니다.
비동기 프로그래밍과 동시성
질문 4: Node.js에서 병렬 처리와 동시 처리의 차이를 설명하라
Node.js는 기본적으로 동시적이지만 병렬적이지는 않습니다. 이 구분은 성능 이해에 필수적입니다.
// 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 바운드 작업(네트워크, 파일)에는 네이티브 동시성으로 충분합니다. CPU 바운드 작업(무거운 계산, 암호화)에는 Worker Threads가 진정한 병렬 처리를 가능하게 합니다.
질문 5: Cluster 모듈은 어떻게 동작하는가?
Cluster 모듈은 같은 포트를 공유하는 여러 Node.js 프로세스를 생성하여 사용 가능한 모든 CPU 코어를 활용합니다.
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`);
}로드 밸런싱은 운영체제가 자동으로 처리합니다(Linux/macOS에서 라운드 로빈). 프로덕션에서는 PM2가 내장 클러스터 모드로 이 관리를 간소화합니다.
Node.js / NestJS 면접 준비가 되셨나요?
인터랙티브 시뮬레이터, flashcards, 기술 테스트로 연습하세요.
Streams와 Buffers
질문 6: 클래식 메서드 대신 Streams를 사용해야 하는 경우는?
Streams는 모든 것을 메모리에 로드하는 대신 청크 단위로 데이터를 처리합니다. 대용량 파일과 스트리밍 시나리오에 필수적입니다.
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
}데이터 크기가 수 MB를 초과할 수 있는 경우, 또는 실시간 처리(업로드, 로그, 네트워크 데이터)에 Streams 사용이 권장됩니다.
질문 7: 백프레셔 개념을 설명하라
백프레셔는 데이터 생산자가 소비자보다 빠를 때 발생합니다. 관리하지 않으면 메모리가 폭발합니다.
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() 메서드가 백프레셔를 자동으로 처리합니다. 복잡한 경우에는 pause/resume 로직을 수동으로 구현합니다.
성능과 최적화
질문 8: 메모리 누수를 식별하고 수정하는 방법은?
메모리 누수는 Node.js에서 흔히 발생합니다. 프로덕션에서 감지하고 수정하는 방법을 아는 것이 필수적입니다.
// ❌ 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();
}프로덕션에서는 clinic.js, Chrome DevTools의 힙 스냅샷, 또는 DataDog이나 New Relic 같은 APM 솔루션이 사용됩니다.
질문 9: Node.js API 성능을 최적화하는 방법은?
이 질문은 여러 수준의 최적화 기법에 대한 지식을 평가합니다.
// 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;
}최적화는 프로파일링에 기반해야 합니다. 최적화 전에 측정하여 실제 병목 지점을 식별하는 것이 중요합니다.
성능 문제의 80%는 코드의 20%에서 발생합니다. 프로파일링을 통해 맹목적으로 최적화하기 전에 이러한 중요 영역을 식별할 수 있습니다.
보안
질문 10: Node.js API를 일반적인 공격으로부터 보호하는 방법은?
보안은 면접에서 반복적으로 나오는 주제입니다. OWASP 취약점에 대한 지식을 보여주는 것이 기대됩니다.
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}`
);
}프로덕션에서는 제한적인 CORS, 필수 HTTPS, 보안 로깅, 시크릿 로테이션, 정기적인 의존성 감사(npm audit)도 추가해야 합니다.
아키텍처와 디자인 패턴
질문 11: Node.js에서 Repository 패턴을 설명하라
Repository 패턴은 데이터 접근을 추상화하고 테스트와 유지보수를 용이하게 합니다.
// 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;
}
}이 패턴을 통해 비즈니스 로직을 수정하지 않고 영속성 구현을 변경할 수 있습니다.
질문 12: 작업 큐 시스템을 구현하는 방법은?
큐는 무거운 작업을 지연시키고 안정적인 실행을 보장합니다.
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: 네이티브 N-API 모듈은 어떻게 동작하는가?
N-API를 통해 Node.js 버전 간 안정적인 API를 가진 C/C++ 네이티브 모듈을 생성할 수 있습니다.
// 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)// Usage from JavaScript
const native = require('./build/Release/native_module');
// 10x faster than JavaScript equivalent
const result = native.fibonacci(50);네이티브 모듈은 집중적인 계산, 기존 C/C++ 라이브러리 통합, 또는 시스템 API 접근에 유용합니다.
질문 14: V8 Garbage Collector를 설명하라
GC의 이해는 일시 정지와 메모리 소비를 최소화하는 코드 작성에 도움이 됩니다.
// 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`,
};
}--max-old-space-size 플래그로 메모리 집약적 애플리케이션의 Old Generation 제한을 늘릴 수 있습니다.
질문 15: 그레이스풀 셧다운을 구현하는 방법은?
그레이스풀 셧다운은 진행 중인 요청을 완료하고 서버 중지 전에 연결을 적절히 닫는 것을 가능하게 합니다.
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');
});컨테이너(Docker, Kubernetes)를 사용하는 프로덕션 환경에서 그레이스풀 셧다운은 무중단 배포에 필수적입니다.
Node.js / NestJS 면접 준비가 되셨나요?
인터랙티브 시뮬레이터, flashcards, 기술 테스트로 연습하세요.
행동 질문
질문 16: 해결한 성능 문제를 설명하라
이 질문은 실무 경험을 평가합니다. STAR 형식(상황, 과제, 행동, 결과)으로 답변을 구성하는 것이 권장됩니다.
구조화된 답변 예시:
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: 의존성과 그 업데이트를 어떻게 관리하는가?
{
"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를 통한 자동화, 각 메이저 업데이트 전 회귀 테스트를 언급하는 것이 권장됩니다.
결론
Node.js 백엔드 면접에서는 내부 메커니즘의 이론적 이해와 프로덕션 환경의 실제 문제를 해결하는 능력이 모두 평가됩니다. Event Loop, 비동기 패턴, 최적화 기법의 숙달은 시니어 백엔드 개발자 포지션에서 기대되는 기반을 형성합니다.
준비 체크리스트
- ✅ Event Loop의 동작과 그 단계를 이해한다
- ✅ 콜백, Promise, async/await의 차이를 숙달한다
- ✅ 비동기 오류 처리 패턴을 안다
- ✅ Streams와 클래식 메서드의 사용 시점을 안다
- ✅ 메모리 누수를 식별하고 수정한다
- ✅ OWASP 보안 모범 사례를 적용한다
- ✅ 클러스터링과 그레이스풀 셧다운을 구현한다
- ✅ 프로파일링 도구(clinic.js, Chrome DevTools)를 사용한다
연습을 시작하세요!
면접 시뮬레이터와 기술 테스트로 지식을 테스트하세요.
기술적 준비는 실전 프로젝트로 보완해야 합니다. 프로덕션 API 구축, Node.js 오픈소스 프로젝트 기여, LeetCode 같은 플랫폼에서의 문제 풀이가 이러한 지식을 견고히 하는 데 도움이 됩니다.
태그
공유
관련 기사

NestJS: 완전한 REST API 구축 가이드
NestJS로 전문적인 REST API를 구축하는 완벽 가이드입니다. 컨트롤러, 서비스, 모듈 구성, class-validator를 활용한 유효성 검사, 에러 핸들링을 실전 코드로 설명합니다.

Laravel과 PHP 면접 질문: 2026년 핵심 25선
Laravel과 PHP 면접에서 가장 자주 출제되는 25가지 질문을 상세히 다룹니다. Eloquent ORM, 미들웨어, 큐, 테스트, 아키텍처 패턴에 대한 상세한 답변과 코드 예제를 제공합니다.

Django와 Python 면접 질문: 2026년 핵심 25선
Django와 Python 면접에서 가장 자주 출제되는 25가지 질문을 상세히 다룹니다. ORM, 뷰, 미들웨어, Django REST Framework, 시그널, 성능 최적화에 대한 상세한 답변과 코드 예제를 제공합니다.