Vorbereitung auf Node.js Backend Interviews

Node.js Interviewfragen und Antworten 2026

Eine fokussierte Sammlung von Node.js-Fragen für Backend-Interviews, strukturiert für mündliche Praxis.

Node.js Mock Interview auf Deutsch starten Technische Interviews auf Englisch übenEin Modus, in dem Nicht-Muttersprachler technische Interviews üben können.

Architektur und Event Loop

1Was ist Node.js und wofür wird es verwendet?

Node.js ist eine JavaScript-Laufzeitumgebung außerhalb des Browsers, die auf V8 basiert und mit APIs für Networking, Dateien, Prozesse, Streams und den Zugriff auf das Betriebssystem erweitert wurde. Es wird häufig für REST APIs, GraphQL Services, Echtzeit WebSocket Server, Microservices, CLI Tools, Proxys und API Gateways verwendet. Seine größte Stärke ist die nicht-blockierende I/O: Ein Prozess kann viele Netzwerkverbindungen effizient verwalten. Der Kompromiss besteht darin, dass langwierige CPU-intensive Berechnungen den Event Loop blockieren können, es sei denn, sie werden in Worker, Prozesse oder einen anderen Dienst ausgelagert.

2Wie funktioniert Node.js intern?

JavaScript läuft in V8, während die APIs von Node.js asynchrone Aufgaben an das Betriebssystem oder an den Thread-Pool von libuv delegieren. Der Haupt-JavaScript-Thread wartet nicht darauf, dass eine I/O-Operation abgeschlossen wird; er setzt mit der Ausführung anderer Code fort. Wenn die Operation abgeschlossen ist, wird sein Callback oder Promise Continuation für den Event Loop bereit. So erreicht Node.js eine hohe Konkurrenzfähigkeit, obwohl das Benutzer-JavaScript normalerweise auf einem Hauptthread läuft.

3Was sind V8, libuv und der Event Loop?

V8 kompiliert und führt JavaScript aus und verwaltet JavaScript-Objekte und den Speicher. libuv bietet den plattformübergreifenden Event Loop, die asynchrone I/O-Integration und einen Worker Thread Pool für Operationen wie bestimmte Dateisystemarbeiten, DNS und Kryptografie. Der Event Loop entscheidet, wann Callbacks für abgeschlossene Operationen ausgeführt werden sollen. Node.js selbst verbindet JavaScript APIs mit nativen Fähigkeiten; V8 allein bietet keinen HTTP-Server, keinen Dateizugriff oder Prozess-APIs.

4Warum wird Node.js oft als Single-Thread bezeichnet?

Ein einzelner Node.js-Prozess führt normalerweise das Benutzer-JavaScript auf einem Hauptthread aus, sodass zwei normale Request-Handler nicht gleichzeitig JavaScript in diesem Thread ausführen. Die Plattform ist jedoch nicht buchstäblich ein einziger Thread: libuv verfügt über einen Thread Pool, das Betriebssystem führt asynchrone I/O durch, V8 hat Garbage-Collection-Arbeit und Node.js unterstützt auch worker_threads und Child Processes. Der Ausdruck beschreibt hauptsächlich das Standard-JavaScript-Ausführungsmodell.

5Wie kann Node.js viele Anfragen gleichzeitig verarbeiten?

Node.js startet I/O-Operationen und kehrt dann zum Event Loop zurück, anstatt für jede Anfrage einen Thread zu blockieren. Zum Beispiel können 1.000 Datenbankaufrufe im Flug sein, während der Hauptthread weiterhin Verbindungen annimmt und bereitstehende Callbacks verarbeitet. Dies eignet sich gut für I/O-gebundene Workloads wie HTTP-Aufrufe, Datenbankabfragen, Dateien und Sockets. Es funktioniert schlecht, wenn Request Handler schwere synchrone CPU-Arbeit leisten, da dies den Event Loop blockiert und jede andere Anfrage verzögert.

6Was sind die Hauptphasen des Node.js Event Loops?

Der libuv Event Loop umfasst Phasen wie timers, pending callbacks, idle/prepare, poll, check und close callbacks. Timers führen setTimeout- und setInterval-Callbacks aus; poll verarbeitet viele I/O-Ereignisse; check führt setImmediate-Callbacks aus; close behandelt Close-Ereignisse wie das Schließen von Sockets. Node.js verfügt außerdem über Microtask Queues, einschließlich process.nextTick und Promise microtasks, die zwischen Callbacks ausgeführt werden. Zu viele Microtasks können I/O verhungern lassen und dazu führen, dass der Dienst feststeckt.

7Wie unterscheiden sich synchrone, asynchrone und nicht blockierende Operationen?

Eine synchrone Operation gibt vor, dass die nächste Zeile ausgeführt wird, und blockiert dabei den Haupt-Thread, wie beispielsweise fs.readFileSync in einem Request Handler. Eine asynchrone Operation liefert ihr Ergebnis später über ein Callback oder Promise. Nicht blockierend bedeutet, dass der Thread während des Wartezeitraums andere Aufgaben erledigen kann. Die Begriffe sind miteinander verwandt, aber nicht identisch: async beschreibt, wie das Ergebnis geliefert wird, während non-blocking beschreibt, ob der Thread wartet. Backend Request Handler sollten im Allgemeinen asynchrone, nicht blockierende APIs bevorzugen.

import fs from 'node:fs';

const syncData = fs.readFileSync('file.txt', 'utf8');

const asyncData = await fs.promises.readFile(
  'file.txt',
  'utf8'
);

8Was ist Event Loop Starvation?

Event loop starvation tritt auf, wenn JavaScript-Arbeit oder Microtasks den Event Loop so stark beanspruchen, dass er I/O, Timer oder neue Anfragen nicht rechtzeitig verarbeiten kann. Häufige Ursachen sind schwere synchrone Berechnungen, die Verarbeitung riesiger JSONs, unbegrenzte Promise-Ketten und übermäßiger Gebrauch von process.nextTick. Lösungen umfassen das Aufteilen der Arbeit in Blöcke (Chunks), die Begrenzung der Eingabegrößen, CPU-Profiling, das Verschieben von CPU-gebundener Arbeit auf worker_threads oder einen anderen Dienst sowie die Vermeidung synchrone APIs in kritischen Anforderungspfaden.

app.get('/report', (req, res) => {
  const result = performVeryHeavyCalculation();
  res.json(result);
});

Asynchrone Programmierung und Fehler

9Was ist ein Callback und was ist Callback Hell?

Ein Callback ist eine Funktion, die ausgeführt wird, wenn später, oft nachdem eine asynchrone Operation abgeschlossen wurde. Klassische Node.js Callbacks folgen häufig dem Error-first-Stil: callback(error, result). Callback Hell tritt auf, wenn abhängige Operationen tief verschachtelt sind, was das Fehlerbehandeln und den Kontrollfluss schwer lesbar macht. Die Lösung besteht darin, die Logik in benannte Funktionen zu zerlegen, Promises oder async/await zu verwenden und unabhängige Operationen mit Promise.all anstelle von Verschachtelung auszuführen.

import fs from 'node:fs';

fs.readFile('file.txt', 'utf8', (err, data) => {
  if (err) {
    console.error(err);
    return;
  }

  console.log(data);
});

10Was ist ein Promise und wie funktioniert async/await?

Ein Promise repräsentiert das zukünftige Ergebnis einer asynchronen Operation und kann im Zustand pending, fulfilled oder rejected sein. Eine async-Funktion gibt immer ein Promise zurück. await pausiert nur die aktuelle async-Funktion, bis das Promise aufgelöst wird; es blockiert nicht den gesamten Node.js-Prozess. Eine gute Antwort sollte Fehlerbehandlung mit try/catch erwähnen, das Vermeiden von fliegenden Promises und die Verwendung von Promise.all für unabhängige Aufgaben, damit Operationen nicht unnötigerweise sequenziell ablaufen.

async function loadUser(id) {
  try {
    const user = await repository.findById(id);
    return user;
  } catch (error) {
    console.error(error);
    throw error;
  }
}

const [user, settings] = await Promise.all([
  getUser(),
  getSettings()
]);

11Wie unterscheiden sich process.nextTick, queueMicrotask, setImmediate und setTimeout?

process.nextTick läuft nach der aktuellen Operation und bevor die Event-Schleife fortfährt. queueMicrotask plant eine standardmäßige JavaScript-Mikroaufgabe, ähnlich wie Promise-Fortsetzungen. setImmediate läuft in der Check-Phase. setTimeout(fn, 0) läuft in der Timers-Phase nach einer minimalen Verzögerung. Normalerweise läuft nextTick vor Promise-Mikroaufgaben, und die Reihenfolge von setTimeout(0) und setImmediate hängt vom Kontext ab; nach I/O-Callbacks läuft setImmediate oft zuerst.

setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
Promise.resolve().then(() => console.log('promise'));
process.nextTick(() => console.log('nextTick'));

12Wie sollten Fehler in Node.js behandelt werden?

Synchroner Code verwendet try/catch. Promise-basierter Code sollte await innerhalb von try/catch verwenden oder .catch anhängen. Error-first Callbacks sollten das Fehlerargument überprüfen, bevor sie das Ergebnis verwenden. In Serveranwendungen sind zentrale Fehlermittelwares, Request-Kontext-Logging, sichere Client-Nachrichten und ein sauberer Shutdown wichtig. uncaughtException sollte nicht verwendet werden, um einen beschädigten Prozess am Laufen zu halten; es ist normalerweise ein Signal zum Protokollieren, Aufräumen und Neustarten.

try {
  JSON.parse(input);
} catch (error) {
  handleError(error);
}

try {
  await operation();
} catch (error) {
  handleError(error);
}

operation((error, result) => {
  if (error) return handleError(error);
  return result;
});

Module und Projektmanagement

13Was sind Module in Node.js?

Ein Modul ist eine isolierte Code-Einheit mit einer explizit exportierten Schnittstelle. Node.js unterstützt eingebaute Module wie node:fs und node:http, lokale Projektmodule, npm-Pakete, CommonJS-Module und ECMAScript-Module. Module machen Verantwortungsgrenzen klarer und ermöglichen Wiederverwendung. Der Produktionskompromiss ist die Kopplung (Coupling): Wenn Module sich zu tief importieren oder Nebenwirkungen zum Importzeitpunkt verbergen, wird das Testen und das Startverhalten schwerer nachvollziehbar.

// math.js
export function add(a, b) {
  return a + b;
}

// app.js
import { add } from './math.js';

14Wie unterscheiden sich CommonJS `require` und ES Modules `import`?

CommonJS verwendet `require` und `module.exports`, während ES Modules `import` und `export` verwenden. ES Modules unterstützen statische Analyse und top-level await, und sie werden über `package.json` mit dem Typ `module` oder in `.mjs`-Dateien ausgewählt. CommonJS wird oft synchron geladen und kann mit `.cjs` markiert werden. In modernen Node.js-Projekten existieren beide Systeme, daher vermeidet eine fundierte Antwort die Übervereinfachung und erklärt, dass Dateiendungen, `package.json` und der Projektkontext bestimmen, welcher Modus verwendet wird.

// CommonJS
const fs = require('node:fs');
module.exports = { calculate };

// ES Modules
import fs from 'node:fs';
export { calculate };

15Was sind npm, package.json und package-lock.json?

npm ist der Paketmanager und die CLI. package.json beschreibt Skripte, Metadaten, Abhängigkeiten (dependencies) und Entwicklungsabhängigkeiten (devDependencies). package-lock.json protokolliert den exakten Abhängigkeitsbaum, sodass Installationen reproduzierbar sind. In CI wird normalerweise npm ci bevorzugt, da es strikt aus der Lock-Datei installiert und fehlschlägt, wenn package.json und die Lock-Datei voneinander abweichen. Dies schützt Builds vor versehentlicher Abhängigkeitsdrift.

{
  "name": "api",
  "type": "module",
  "scripts": {
    "start": "node src/server.js",
    "test": "node --test"
  },
  "dependencies": {},
  "devDependencies": {}
}

16Wie sollten Konfigurations- und Umgebungsvariablen verwaltet werden?

Node.js liest Umgebungsvariablen über process.env, aber die Werte sind Strings und müssen beim Start validiert und konvertiert werden. Geheimnisse dürfen nicht im Repository gespeichert werden, .env sollte ignoriert werden, und Produktionsgeheimnisse sollten von der Plattform oder einem Secret Manager stammen. Gute Dienste schlagen schnell fehl, wenn erforderliche Konfiguration fehlt. NODE_ENV ist eine Konvention, keine Sicherheitsgrenze, daher sollte der Code sich nicht darauf als geschützter Mechanismus verlassen.

const port = Number(process.env.PORT ?? 3000);
const databaseUrl = process.env.DATABASE_URL;

if (!databaseUrl) {
  throw new Error('DATABASE_URL is required');
}

Ereignisse, Puffer und Streams

17Was ist EventEmitter?

EventEmitter implementiert ein Publish-Subscribe-Muster innerhalb eines Node.js-Prozesses. on registriert einen Listener, once registriert einen Listener für ein Ereignis, off entfernt einen Listener und emit ruft die Listener synchron in der Reihenfolge ihrer Registrierung auf. Die synchrone Natur ist wichtig: Ein langsamer Listener blockiert den Emitter. Das Error-Ereignis ist besonders, da ein unbehandelter Error-Event den Prozess abstürzen lassen kann. Langfristig laufende Systeme sollten auch nicht verwendete Listener entfernen, um Speicherlecks zu vermeiden.

import { EventEmitter } from 'node:events';

const emitter = new EventEmitter();

emitter.on('orderCreated', order => {
  console.log(`Order ${order.id} created`);
});

emitter.emit('orderCreated', { id: 42 });

18Wofür wird Buffer verwendet?

Buffer repräsentiert eine feste Folge von Bytes. Es wird für Dateien, TCP-Sockets, HTTP-Bodies, Bilder, Kryptographie und binäre Protokolle verwendet. Buffer.from erstellt einen Puffer aus vorhandenen Daten, Buffer.alloc erstellt speicher mit Nullen, und Buffer.allocUnsafe ist schneller, kann aber alte Speicher enthalten, bis er überschrieben wird. Das macht allocUnsafe nur dann geeignet, wenn der Code den Puffer sofort füllt und niemals veraltete Daten preisgibt.

const buffer = Buffer.from('Hello', 'utf8');

console.log(buffer);
console.log(buffer.toString('utf8'));

const safe = Buffer.alloc(1024);
const fast = Buffer.allocUnsafe(1024);

19Was sind Streams und welche Typen gibt es?

Streams verarbeiten Daten in Blöcken (Chunks), anstatt alles in den Speicher zu laden. Die Haupttypen sind Readable, Writable, Duplex und Transform. Häufige Beispiele sind Dateien, HTTP-Anfrage- und Antwortobjekte, TCP-Sockets und gzip-Streams. Streams sind für große Dateien und Netzwerkverkehr wertvoll, da sie den Speicherverbrauch und die Latenz reduzieren. Eine professionelle Antwort sollte erwähnen, dass Stream-Fehler über die gesamte Kette korrekt behandelt werden müssen.

import fs from 'node:fs';

const input = fs.createReadStream('large.log');
const output = fs.createWriteStream('copy.log');

input.pipe(output);

20Was ist Backpressure und warum ist eine Pipeline nützlich?

Backpressure tritt auf, wenn eine Datenquelle Chunks schneller erzeugt, als das Ziel sie verarbeiten kann. Ohne Backpressure kann der Speicher wachsen, bis der Prozess langsamer wird oder abstürzt. Streams und Pipes können den Fluss koordinieren, aber `pipeline` aus `node:stream/promises` ist sicherer für mehrstufige Ketten, weil es Fehler weitergibt, verwandte Streams schließt und ein Promise zurückgibt. Es ist ein guter Standardwert für Dateikomprimierung, Uploads, Downloads und Stream-Transformationen.

import fs from 'node:fs';
import zlib from 'node:zlib';
import { pipeline } from 'node:stream/promises';

await pipeline(
  fs.createReadStream('input.txt'),
  zlib.createGzip(),
  fs.createWriteStream('input.txt.gz')
);