Node.js Backend Interview-Fragen: Vollständiger Leitfaden 2026

Die 25 häufigsten Node.js Backend Interview-Fragen. Event Loop, async/await, Streams, Clustering und Performance mit ausführlichen Antworten erklärt.

Node.js Backend Interview-Fragen - Vollständiger Leitfaden

Node.js Backend-Interviews prüfen das Verständnis der Runtime-Interna, die Beherrschung asynchroner Muster und die Fähigkeit, performante Anwendungen zu entwerfen. Dieser Leitfaden behandelt die am häufigsten gestellten Fragen, von den Grundlagen bis hin zu fortgeschrittenen Produktionskonzepten.

Interview-Tipp

Personalverantwortliche schätzen Antworten, die Theorie mit praktischen Beispielen verbinden. Bei jeder Frage zeigt die Veranschaulichung mit Code oder einem konkreten Anwendungsfall praxisnahe Erfahrung.

Node.js Grundlagen

Frage 1: Was ist der Event Loop und wie funktioniert er?

Der Event Loop ist der zentrale Mechanismus, der es Node.js ermöglicht, asynchrone Operationen nicht-blockierend zu verarbeiten, obwohl die Ausführung in einem einzigen Thread erfolgt. Er orchestriert die Ausführung von JavaScript-Code, Callbacks und Events.

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

Der Event Loop folgt einer präzisen Reihenfolge bei der Verarbeitung der Queues: zuerst synchroner Code, dann nextTick, Microtasks (Promises), Timer, I/O-Callbacks, setImmediate und schließlich Close-Callbacks.

Frage 2: Was ist der Unterschied zwischen process.nextTick() und setImmediate()?

Diese Frage prüft das detaillierte Verständnis der Ausführungsprioritäten im 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() wird unmittelbar nach der aktuellen Operation verarbeitet, bevor der Event Loop fortfährt. Übermäßiger Einsatz kann den Event Loop blockieren. setImmediate() ist vorhersehbarer und wird empfohlen, wenn die Ausführung aufgeschoben werden soll.

Vorsicht vor Starvation

Rekursive Aufrufe von process.nextTick() können den Event Loop aushungern und die I/O-Verarbeitung verhindern. Für nicht-kritische Operationen sollte setImmediate() bevorzugt werden.

Frage 3: Wie behandelt Node.js Fehler in asynchronem Code?

Die asynchrone Fehlerbehandlung unterscheidet sich grundlegend von synchronem Code. Ohne korrekte Behandlung kann ein Fehler die gesamte Anwendung zum Absturz bringen.

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);
});

In der Produktion muss jedes Promise ein .catch() haben oder sich innerhalb eines try/catch-Blocks befinden. Globale Handler dienen als Sicherheitsnetz, nicht als primäre Lösung.

Asynchrone Programmierung und Nebenläufigkeit

Frage 4: Erklären Sie den Unterschied zwischen Parallelität und Nebenläufigkeit in Node.js

Node.js ist standardmäßig nebenläufig, aber nicht parallel. Diese Unterscheidung ist fundamental für das Verständnis der Performance.

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);
}

Für I/O-gebundene Operationen (Netzwerk, Dateien) reicht die native Nebenläufigkeit aus. Für CPU-gebundene Aufgaben (aufwendige Berechnungen, Kryptografie) ermöglichen Worker Threads echte Parallelität.

Frage 5: Wie funktioniert das Cluster-Modul?

Das Cluster-Modul ermöglicht die Erstellung mehrerer Node.js-Prozesse, die denselben Port teilen und so alle verfügbaren CPU-Kerne nutzen.

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`);
}

Das Load Balancing wird automatisch vom Betriebssystem übernommen (Round-Robin unter Linux/macOS). In der Produktion vereinfacht PM2 diese Verwaltung mit seinem integrierten Cluster-Modus.

Bereit für deine Node.js / NestJS-Interviews?

Übe mit unseren interaktiven Simulatoren, Flashcards und technischen Tests.

Streams und Buffers

Frage 6: Wann sollten Streams anstelle klassischer Methoden verwendet werden?

Streams ermöglichen die stückweise Verarbeitung von Daten, anstatt alles in den Arbeitsspeicher zu laden. Unverzichtbar bei großen Dateien und Streaming-Szenarien.

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 sollten immer dann eingesetzt werden, wenn die Datengröße einige MB überschreiten kann oder bei Echtzeitverarbeitung (Uploads, Logs, Netzwerkdaten).

Frage 7: Erklären Sie das Konzept des Backpressure

Backpressure tritt auf, wenn der Datenproduzent schneller ist als der Konsument. Ohne Behandlung explodiert der Speicherverbrauch.

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);
}

Die Methode pipe() oder pipeline() übernimmt die Backpressure-Behandlung automatisch. Bei komplexen Fällen kann die Pause/Resume-Logik manuell implementiert werden.

Performance und Optimierung

Frage 8: Wie erkennt und behebt man Memory Leaks?

Memory Leaks sind in Node.js häufig anzutreffen. Das Wissen, wie man sie erkennt und behebt, ist in der Produktion unerlässlich.

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();
}

In der Produktion sollten Tools wie clinic.js, Heap-Snapshots aus Chrome DevTools oder APM-Lösungen (Application Performance Monitoring) wie DataDog oder New Relic eingesetzt werden.

Frage 9: Wie optimiert man die Performance einer Node.js API?

Diese Frage prüft das Wissen über Optimierungstechniken auf verschiedenen Ebenen.

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;
}

Optimierungen sollten durch Profiling gesteuert werden. Vor dem Optimieren messen, um die tatsächlichen Engpässe zu identifizieren.

Die 80/20-Regel

80 % der Performance-Probleme stammen aus 20 % des Codes. Profiling hilft, diese kritischen Bereiche zu identifizieren, bevor blind optimiert wird.

Sicherheit

Frage 10: Wie schützt man eine Node.js API vor gängigen Angriffen?

Sicherheit ist ein wiederkehrendes Interview-Thema. Kenntnisse über OWASP-Schwachstellen werden erwartet.

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}`
  );
}

In der Produktion sollten außerdem restriktive CORS-Einstellungen, verpflichtendes HTTPS, Sicherheits-Logging, Rotation von Geheimnissen und regelmäßige Dependency-Audits (npm audit) eingesetzt werden.

Architektur und Design Patterns

Frage 11: Erklären Sie das Repository Pattern in Node.js

Das Repository Pattern abstrahiert den Datenzugriff und erleichtert Tests sowie die Wartbarkeit.

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;
  }
}

Dieses Pattern ermöglicht es, die Persistenz-Implementierung zu ändern, ohne die Geschäftslogik anpassen zu müssen.

Frage 12: Wie implementiert man ein Job-Queue-System?

Queues ermöglichen es, aufwendige Aufgaben aufzuschieben und deren zuverlässige Ausführung sicherzustellen.

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 mit Redis ist die populärste Lösung. Für einfachere Anforderungen sind agenda oder bee-queue leichtgewichtige Alternativen.

Fortgeschrittene Fragen

Frage 13: Wie funktioniert das native N-API-Modul?

N-API ermöglicht die Erstellung nativer Module in C/C++ mit einer stabilen API über Node.js-Versionen hinweg.

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 Module sind nützlich für rechenintensive Operationen, die Integration bestehender C/C++-Bibliotheken oder den Zugriff auf System-APIs.

Frage 14: Erklären Sie den V8 Garbage Collector

Das Verständnis des GC hilft, Code zu schreiben, der Pausen und Speicherverbrauch minimiert.

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`,
  };
}

Das Flag --max-old-space-size ermöglicht es, das Limit der Old Generation für speicherintensive Anwendungen zu erhöhen.

Frage 15: Wie implementiert man einen Graceful Shutdown?

Ein Graceful Shutdown ermöglicht das Abschließen laufender Anfragen und das ordnungsgemäße Schließen von Verbindungen, bevor der Server gestoppt wird.

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');
});

In der Produktion mit Containern (Docker, Kubernetes) ist ein Graceful Shutdown entscheidend für Deployments ohne Ausfallzeit.

Bereit für deine Node.js / NestJS-Interviews?

Übe mit unseren interaktiven Simulatoren, Flashcards und technischen Tests.

Verhaltensorientierte Fragen

Frage 16: Beschreiben Sie ein Performance-Problem, das Sie gelöst haben

Diese Frage bewertet praktische Erfahrung. Die Antwort sollte mit der STAR-Methode (Situation, Task, Action, Result) strukturiert werden.

Beispiel einer strukturierten Antwort:

text
Situation: Eine Reporting-API hatte Timeouts bei Anfragen
            mit mehr als 100.000 Datensätzen.

Task:       Die Antwortzeit von 45s auf unter 5s reduzieren.

Action:
1. Profiling mit clinic.js → JSON-Serialisierung als Engpass identifiziert
2. Streaming mit Transform Streams implementiert
3. Datenbankseitige Paginierung eingeführt
4. Redis-Caching für häufige Abfragen hinzugefügt

Result:     Antwortzeit auf 2s reduziert, Speicherverbrauch um Faktor 10 gesenkt.

Frage 17: Wie verwalten Sie Abhängigkeiten und deren Aktualisierungen?

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"
  }
}

Wichtig sind der Einsatz von package-lock.json, Dependabot oder Renovate für die Automatisierung und Regressionstests vor jedem größeren Update.

Fazit

Node.js Backend-Interviews bewerten sowohl das theoretische Verständnis interner Mechanismen als auch die Fähigkeit, praktische Produktionsprobleme zu lösen. Die Beherrschung des Event Loops, asynchroner Muster und Optimierungstechniken bildet das erwartete Fundament für Senior-Backend-Entwicklerpositionen.

Vorbereitungs-Checkliste

  • ✅ Event-Loop-Funktionsweise und seine Phasen verstehen
  • ✅ Unterschiede zwischen Callbacks, Promises und async/await beherrschen
  • ✅ Muster der asynchronen Fehlerbehandlung kennen
  • ✅ Wissen, wann Streams anstelle klassischer Methoden eingesetzt werden
  • ✅ Memory Leaks erkennen und beheben können
  • ✅ OWASP-Sicherheits-Best-Practices anwenden
  • ✅ Clustering und Graceful Shutdown implementieren
  • ✅ Profiling-Tools verwenden (clinic.js, Chrome DevTools)

Fang an zu üben!

Teste dein Wissen mit unseren Interview-Simulatoren und technischen Tests.

Die technische Vorbereitung sollte durch praktische Projekte ergänzt werden. Der Aufbau einer produktionsreifen API, Beiträge zu Node.js-Open-Source-Projekten oder das Lösen von Aufgaben auf Plattformen wie LeetCode helfen, dieses Wissen zu festigen.

Tags

#nodejs
#interview
#backend
#javascript
#technical interview

Teilen

Verwandte Artikel