# Node.js 백엔드 면접 질문: 완벽 가이드 2026 > Node.js 백엔드 면접에서 가장 자주 나오는 25가지 질문. Event loop, async/await, streams, 클러스터링, 성능을 상세한 답변과 함께 설명합니다. - Published: 2026-01-22 - Updated: 2026-04-10 - Author: SharpSkill - Tags: nodejs, interview, backend, javascript, technical interview - Reading time: 18 min --- Node.js 백엔드 면접에서는 런타임 내부 메커니즘의 이해, 비동기 패턴의 숙달, 고성능 애플리케이션 설계 능력이 평가됩니다. 이 가이드는 기초부터 프로덕션 환경의 고급 개념까지 가장 자주 출제되는 질문을 다룹니다. > **면접 팁** > > 채용 담당자는 이론과 실전 예제를 결합한 답변을 높이 평가합니다. 각 질문에 대해 코드나 구체적인 사용 사례로 설명하면 실무 경험이 전달됩니다. ## Node.js 기초 ### 질문 1: Event Loop란 무엇이며 어떻게 동작하는가? Event Loop는 Node.js가 싱글 스레드로 동작하면서도 비동기 작업을 논블로킹 방식으로 처리할 수 있게 하는 핵심 메커니즘입니다. JavaScript 코드, 콜백, 이벤트의 실행을 조율합니다. ```javascript // event-loop-demo.js // 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는 큐를 처리할 때 정확한 순서를 따릅니다. 먼저 동기 코드, 그 다음 nextTick, 마이크로태스크(Promise), 타이머, I/O 콜백, setImmediate, 마지막으로 닫기 콜백 순입니다. ### 질문 2: process.nextTick()과 setImmediate()의 차이점은? 이 질문은 Event Loop에서의 실행 우선순위에 대한 세밀한 이해를 평가합니다. ```javascript // nextTick-vs-immediate.js // 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()`는 더 예측 가능하며 실행 지연에 권장됩니다. > **기아 상태 주의** > > process.nextTick()의 재귀 호출은 Event Loop를 고갈시키고 I/O 처리를 방해할 수 있습니다. 중요하지 않은 작업에는 setImmediate() 사용이 권장됩니다. ### 질문 3: Node.js는 비동기 코드에서 오류를 어떻게 처리하는가? 비동기 오류 처리는 동기 코드와 근본적으로 다릅니다. 적절한 처리 없이는 오류가 애플리케이션을 크래시시킬 수 있습니다. ```javascript // error-handling.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는 기본적으로 동시적이지만 병렬적이지는 않습니다. 이 구분은 성능 이해에 필수적입니다. ```javascript // concurrency-vs-parallelism.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 코어를 활용합니다. ```javascript // cluster-example.js 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가 내장 클러스터 모드로 이 관리를 간소화합니다. ## Streams와 Buffers ### 질문 6: 클래식 메서드 대신 Streams를 사용해야 하는 경우는? Streams는 모든 것을 메모리에 로드하는 대신 청크 단위로 데이터를 처리합니다. 대용량 파일과 스트리밍 시나리오에 필수적입니다. ```javascript // streams-comparison.js 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: 백프레셔 개념을 설명하라 백프레셔는 데이터 생산자가 소비자보다 빠를 때 발생합니다. 관리하지 않으면 메모리가 폭발합니다. ```javascript // backpressure-demo.js 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에서 흔히 발생합니다. 프로덕션에서 감지하고 수정하는 방법을 아는 것이 필수적입니다. ```javascript // memory-leak-patterns.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 성능을 최적화하는 방법은? 이 질문은 여러 수준의 최적화 기법에 대한 지식을 평가합니다. ```javascript // performance-optimization.js // 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 법칙** > > 성능 문제의 80%는 코드의 20%에서 발생합니다. 프로파일링을 통해 맹목적으로 최적화하기 전에 이러한 중요 영역을 식별할 수 있습니다. ## 보안 ### 질문 10: Node.js API를 일반적인 공격으로부터 보호하는 방법은? 보안은 면접에서 반복적으로 나오는 주제입니다. OWASP 취약점에 대한 지식을 보여주는 것이 기대됩니다. ```javascript // security-best-practices.js 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 패턴은 데이터 접근을 추상화하고 테스트와 유지보수를 용이하게 합니다. ```javascript // repository-pattern.js // 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: 작업 큐 시스템을 구현하는 방법은? 큐는 무거운 작업을 지연시키고 안정적인 실행을 보장합니다. ```javascript // job-queue.js 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++ 네이티브 모듈을 생성할 수 있습니다. ```cpp // native-module.cpp // Native module for CPU-intensive calculations #include // 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().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(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); ``` 네이티브 모듈은 집중적인 계산, 기존 C/C++ 라이브러리 통합, 또는 시스템 API 접근에 유용합니다. ### 질문 14: V8 Garbage Collector를 설명하라 GC의 이해는 일시 정지와 메모리 소비를 최소화하는 코드 작성에 도움이 됩니다. ```javascript // gc-optimization.js // 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: 그레이스풀 셧다운을 구현하는 방법은? 그레이스풀 셧다운은 진행 중인 요청을 완료하고 서버 중지 전에 연결을 적절히 닫는 것을 가능하게 합니다. ```javascript // graceful-shutdown.js 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)를 사용하는 프로덕션 환경에서 그레이스풀 셧다운은 무중단 배포에 필수적입니다. ## 행동 질문 ### 질문 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: 의존성과 그 업데이트를 어떻게 관리하는가? ```javascript // package.json - Versioning best practices { "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 같은 플랫폼에서의 문제 풀이가 이러한 지식을 견고히 하는 데 도움이 됩니다. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/ko/blog/node-nestjs/nodejs-backend-interview-questions