Voorbereiding op Node.js-backend-interviews

Node.js-interviewvragen en -antwoorden 2026

Een gerichte set Node.js-interviewvragen voor backend-ontwikkelaars. De antwoorden zijn gestructureerd om hardop te oefenen: leg de architectuur uit, geef een voorbeeld en benoem afwegingen voor productie.

Start Node.js-proefinterviewGeen creditcard nodig. 1 gratis sessie beschikbaar.
Oefen technische interviews in het EngelsEen modus waarin anderstaligen kunnen oefenen met het afleggen van technische interviews.

Architectuur en de eventloop

1Wat is Node.js en waarvoor wordt het gebruikt?

Node.js is een uitvoeringsomgeving voor JavaScript buiten de browser, gebouwd op V8 en uitgebreid met API's voor netwerkcommunicatie, bestanden, processen, gegevensstromen en toegang tot het besturingssysteem. Het wordt veelal gebruikt voor REST (Representational State Transfer) API's, GraphQL-services, realtime WebSocket-servers, microservices, CLI (Command-Line Interface)-tools, proxy's en API-gateways. De belangrijkste kracht ervan is niet-blokkerende I/O (Input/Output): één proces kan efficiënt veel netwerkverbindingen afhandelen. De keerzijde is dat langdurige, CPU (Central Processing Unit)-intensieve berekeningen de eventloop kunnen blokkeren, tenzij deze worden verplaatst naar workers, afzonderlijke processen of een andere service.

Probeer deze vraag te beantwoorden met een AI-begeleider

2Hoe werkt Node.js intern?

JavaScript draait in V8, terwijl de API's van Node.js asynchroon werk delegeren aan het besturingssysteem of aan de threadpool van libuv. De hoofdthread van JavaScript wacht niet tot een I/O-bewerking is voltooid; deze gaat door met het uitvoeren van andere code. Wanneer de bewerking is voltooid, wordt de bijbehorende callbackfunctie of `Promise`-voortzetting klaargezet voor de eventloop. Op deze manier bereikt Node.js een hoge mate van gelijktijdigheid, hoewel de JavaScript-code van de gebruiker doorgaans op één hoofdthread wordt uitgevoerd.

Probeer deze vraag te beantwoorden met een AI-begeleider

3Wat zijn V8, libuv en de eventloop?

V8 compileert en voert JavaScript uit en beheert JavaScript-objecten en het geheugen. libuv biedt de platformonafhankelijke eventloop, asynchrone I/O-integratie en een worker-threadpool voor bewerkingen zoals bepaald bestandssysteemwerk, DNS en cryptografie. De eventloop bepaalt wanneer callbackfuncties van voltooide bewerkingen moeten worden uitgevoerd. Node.js zelf verbindt JavaScript-API's met systeemeigen functionaliteiten; V8 op zichzelf biedt geen HTTP-server, toegang tot het bestandssysteem of proces-API's.

Probeer deze vraag te beantwoorden met een AI-begeleider

4Waarom wordt Node.js vaak single-threaded genoemd?

Een enkel Node.js-proces voert JavaScript-code van de gebruiker doorgaans uit op één hoofdthread, wat betekent dat twee normale afhandelaren van requests niet tegelijkertijd JavaScript uitvoeren in die thread. Het platform bestaat echter niet letterlijk uit één thread: libuv heeft een threadpool, het besturingssysteem voert asynchrone I/O uit, V8 voert garbagecollection-taken uit en Node.js ondersteunt ook `worker_threads` en sub-processen (child processes). De term beschrijft voornamelijk het standaard uitvoeringsmodel voor JavaScript.

Probeer deze vraag te beantwoorden met een AI-begeleider

5Hoe kan Node.js veel gelijktijdige aanvragen afhandelen?

Node.js start I/O-operaties en keert daarna terug naar de eventloop in plaats van een thread per aanvraag te blokkeren. Er kunnen bijvoorbeeld 1.000 database-aanroepen in behandeling zijn, terwijl de hoofdthread doorgaat met het accepteren van verbindingen en het verwerken van voltooide callbacks. Dit werkt goed voor I/O-intensieve taken, zoals HTTP-aanroepen, database-queries, bestanden en sockets. Het werkt slecht als handlers voor aanvragen zwaar synchroon CPU-werk verrichten, omdat dit de eventloop blokkeert en alle andere aanvragen vertraagt.

Probeer deze vraag te beantwoorden met een AI-begeleider

6Wat zijn de belangrijkste fasen van de Node.js-eventloop?

De libuv-eventloop omvat fasen zoals de `timers`-fase, `pending callbacks`-fase, `idle/prepare`-fase, `poll`-fase, `check`-fase en `close callbacks`-fase. De `timers`-fase voert callbacks uit van `setTimeout` en `setInterval`; de `poll`-fase handelt veel I/O-gebeurtenissen af; de `check`-fase voert `setImmediate`-callbacks uit; en de `close callbacks`-fase handelt afsluitingsgebeurtenissen af, zoals het sluiten van sockets. Node.js heeft ook microtask-wachtrijen, waaronder `process.nextTick` en `Promise`-microtasks, die tussen callbacks door worden uitgevoerd. Te veel microtasks kunnen de I/O-verwerking blokkeren, waardoor het lijkt alsof de dienst is vastgelopen.

Probeer deze vraag te beantwoorden met een AI-begeleider

7Waarin verschillen synchrone, asynchrone en niet-blokkerende bewerkingen van elkaar?

Een synchrone bewerking keert terug voordat de volgende regel wordt uitgevoerd en blokkeert de hoofdthread, zoals fs.readFileSync in een request-handler. Een asynchrone bewerking retourneert het resultaat later via een callback of Promise. Niet-blokkerend betekent dat de thread kan doorgaan met ander werk terwijl de bewerking in behandeling is. De termen zijn gerelateerd maar niet identiek: asynchroon beschrijft hoe het resultaat wordt opgeleverd, terwijl niet-blokkerend beschrijft of de thread wacht. Request-handlers in de backend zouden over het algemeen de voorkeur moeten geven aan asynchrone, niet-blokkerende API's.

import fs from 'node:fs';

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

const asyncData = await fs.promises.readFile(
  'file.txt',
  'utf8'
);
Probeer deze vraag te beantwoorden met een AI-begeleider

8Wat is uithongering van de eventloop (event loop starvation)?

Uithongering van de eventloop treedt op wanneer JavaScript-taken of microtaken de eventloop bezig houden, zodat I/O, timers of nieuwe aanvragen niet tijdig kunnen worden verwerkt. Veelvoorkomende oorzaken zijn zware synchrone berekeningen, het verwerken van enorme JSON-bestanden, onbegrensde Promise-ketens en overmatig gebruik van process.nextTick. Oplossingen zijn onder meer het opdelen van werk in kleinere stukken, het beperken van invoergroottes, het profilen van de CPU, het verplaatsen van CPU-intensief werk naar worker_threads of een andere service, en het vermijden van synchrone API's in vaak uitgevoerde codepaden.

app.get('/report', (req, res) => {
  const result = performVeryHeavyCalculation();
  res.json(result);
});
Probeer deze vraag te beantwoorden met een AI-begeleider

Asynchroon programmeren en foutafhandeling

9Wat is een callback en wat is callback hell?

Een callback is een functie die wordt doorgegeven om later te worden uitgevoerd, vaak nadat een asynchrone bewerking is voltooid. Klassieke Node.js-callbacks volgen doorgaans de error-first-stijl: `callback(error, result)`. Callback hell ontstaat wanneer afhankelijke bewerkingen diep genest zijn, wat foutafhandeling en de control flow moeilijk leesbaar maakt. De oplossing is om deze op te splitsen in benoemde functies, gebruik te maken van `Promise`s of `async/await`, en onafhankelijke bewerkingen gelijktijdig uit te voeren met `Promise.all` in plaats van ze te nesten.

import fs from 'node:fs';

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

  console.log(data);
});
Probeer deze vraag te beantwoorden met een AI-begeleider

10Wat is een Promise en hoe werkt async/await?

Een Promise vertegenwoordigt het toekomstige resultaat van een asynchrone bewerking en kan de status pending, fulfilled of rejected hebben. Een async-functie retourneert altijd een Promise. Het sleutelwoord await pauzeert alleen de huidige async-functie totdat de Promise is afgehandeld; het blokkeert niet het volledige Node.js-proces. Een goed antwoord noemt foutafhandeling met try/catch, het vermijden van floating Promises en het gebruik van Promise.all voor onafhankelijke taken, zodat bewerkingen niet onnodig na elkaar worden uitgevoerd.

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()
]);
Probeer deze vraag te beantwoorden met een AI-begeleider

11Hoe verschillen process.nextTick, queueMicrotask, setImmediate en setTimeout van elkaar?

process.nextTick wordt uitgevoerd na de huidige bewerking en voordat de eventloop verdergaat. queueMicrotask plant een standaard JavaScript-microtask, vergelijkbaar met Promise-vervolgacties. setImmediate wordt uitgevoerd in de `check`-fase. setTimeout(fn, 0) wordt uitgevoerd in de `timers`-fase na een minimale vertraging. Meestal wordt nextTick uitgevoerd vóór Promise-microtasks, en de volgorde van setTimeout(0) en setImmediate hangt af van de context; na I/O-callbacks wordt setImmediate vaak als eerste uitgevoerd.

setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
Promise.resolve().then(() => console.log('promise'));
process.nextTick(() => console.log('nextTick'));
Probeer deze vraag te beantwoorden met een AI-begeleider

12Hoe moeten fouten worden afgehandeld in Node.js?

Synchrone code gebruikt `try/catch`. Promise-gebaseerde code moet `await` binnen een `try/catch` gebruiken of `.catch` toevoegen. Error-first callbacks moeten het foutargument controleren voordat het resultaat wordt gebruikt. In serverapplicaties zijn centrale fout-middleware, logging met request-context, veilige berichten naar de client en gecontroleerd afsluiten belangrijk. `uncaughtException` mag niet worden gebruikt om een gecorrumpeerd proces draaiende te houden; het is meestal een signaal om te loggen, op te ruimen en opnieuw op te starten.

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;
});
Probeer deze vraag te beantwoorden met een AI-begeleider

Modules en projectbeheer

13Wat zijn modules in Node.js?

Een module is een geïsoleerde eenheid code met een expliciete geëxporteerde interface. Node.js ondersteunt ingebouwde modules zoals `node:fs` en `node:http`, lokale projectmodules, npm-pakketten, CommonJS-modules en ECMAScript-modules. Modules maken verantwoordelijkheidsgrenzen duidelijker en maken hergebruik mogelijk. De afweging in productieomgevingen is koppeling: als modules elkaar te diep importeren of neveneffecten verbergen tijdens het importeren, worden het testen en het opstartgedrag moeilijker te doorgronden.

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

// app.js
import { add } from './math.js';
Probeer deze vraag te beantwoorden met een AI-begeleider

14Wat is het verschil tussen `require` in CommonJS en `import` in ES Modules?

CommonJS gebruikt `require` en `module.exports`, terwijl ES Modules `import` en `export` gebruiken. ES Modules ondersteunen statische analyse en top-level `await`, en worden geselecteerd via `"type": "module"` in `package.json` of bestanden met een `.mjs`-extensie. CommonJS wordt vaak synchroon geladen en kan worden aangegeven met de `.cjs`-extensie. In moderne Node.js-projecten bestaan beide systemen naast elkaar, dus een goed antwoord vermijdt te sterke simplificatie en legt uit dat de bestandsextensie, de `package.json` en de projectcontext bepalen welke modulemodus actief is.

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

// ES Modules
import fs from 'node:fs';
export { calculate };
Probeer deze vraag te beantwoorden met een AI-begeleider

15Wat zijn npm, `package.json` en `package-lock.json`?

npm is de pakketbeheerder en CLI (Command Line Interface). Het bestand `package.json` beschrijft scripts, metagegevens, `dependencies` en `devDependencies`. `package-lock.json` legt de exacte afhankelijkheidsboom vast, zodat installaties altijd reproduceerbaar zijn. In CI (Continuous Integration) heeft `npm ci` meestal de voorkeur, omdat deze afhankelijkheden strikt vanuit het lock-bestand installeert en faalt als `package.json` en het lock-bestand niet overeenkomen. Dit beschermt builds tegen onbedoelde verschuivingen in afhankelijkheden.

{
  "name": "api",
  "type": "module",
  "scripts": {
    "start": "node src/server.js",
    "test": "node --test"
  },
  "dependencies": {},
  "devDependencies": {}
}
Probeer deze vraag te beantwoorden met een AI-begeleider

16Hoe moeten configuratie en omgevingsvariabelen worden beheerd?

Node.js leest omgevingsvariabelen via `process.env`, maar de waarden zijn strings en moeten bij het opstarten worden gevalideerd en geconverteerd. Geheimen mogen niet in de repository worden opgeslagen, `.env` moet worden genegeerd, en productiegeheimen moeten afkomstig zijn van het platform of een secret manager. Goede services falen direct als vereiste configuratie ontbreekt. `NODE_ENV` is een conventie, geen beveiligingsgrens, dus code mag er niet op vertrouwen als een beveiligd mechanisme.

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

if (!databaseUrl) {
  throw new Error('DATABASE_URL is required');
}
Probeer deze vraag te beantwoorden met een AI-begeleider

Events, buffers en streams

17Wat is EventEmitter?

EventEmitter implementeert een publish-subscribe-patroon binnen een Node.js-proces. on registreert een listener, once registreert een listener voor één event, off verwijdert een listener en emit roept listeners synchroon aan in de volgorde van registratie. Het synchrone karakter is belangrijk: een trage listener blokkeert de emitter. Het error-event is speciaal omdat een onafgehandeld error-event het proces kan laten crashen. Langlopende systemen moeten ook ongebruikte listeners verwijderen om geheugenlekken te voorkomen.

import { EventEmitter } from 'node:events';

const emitter = new EventEmitter();

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

emitter.emit('orderCreated', { id: 42 });
Probeer deze vraag te beantwoorden met een AI-begeleider

18Waarvoor wordt Buffer gebruikt?

Buffer vertegenwoordigt een reeks bytes met een vaste grootte. Het wordt gebruikt voor bestanden, TCP-sockets, HTTP-body's, afbeeldingen, cryptografie en binaire protocollen. Buffer.from creëert een buffer op basis van bestaande gegevens, Buffer.alloc reserveert geheugen dat volledig met nullen is gevuld, en Buffer.allocUnsafe is sneller maar kan oud geheugen bevatten totdat het wordt overschreven. Dat maakt allocUnsafe alleen geschikt wanneer de code de buffer onmiddellijk vult en nooit verouderde gegevens blootstelt.

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

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

const safe = Buffer.alloc(1024);
const fast = Buffer.allocUnsafe(1024);
Probeer deze vraag te beantwoorden met een AI-begeleider

19Wat zijn streams en welke typen bestaan er?

Streams verwerken gegevens in kleine delen (chunks) in plaats van alles in het geheugen te laden. De belangrijkste typen zijn `Readable`, `Writable`, `Duplex` en `Transform`. Bestanden, objecten voor HTTP-aanvragen en -antwoorden, TCP-sockets en gzip-streams zijn veelvoorkomende voorbeelden. Streams zijn waardevol voor grote bestanden en netwerkverkeer omdat ze het geheugengebruik en de vertraging verminderen. Een goed antwoord voor een productieomgeving moet benoemen dat fouten in streams correct door de hele keten heen afgehandeld moeten worden.

import fs from 'node:fs';

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

input.pipe(output);
Probeer deze vraag te beantwoorden met een AI-begeleider

20Wat is backpressure en waarom is `pipeline` nuttig?

Backpressure treedt op wanneer een gegevensbron sneller brokken (chunks) produceert dan de bestemming ze kan verwerken. Zonder backpressure kan het geheugengebruik toenemen totdat het proces trager wordt of crasht. Streams en `pipe` kunnen de datastroom coördineren, maar `pipeline` uit `node:stream/promises` is veiliger voor ketens met meerdere stappen, omdat het fouten doorgeeft, gerelateerde streams sluit en een Promise retourneert. Het is een goede standaardkeuze voor bestandscompressie, uploads, downloads en streamtransformaties.

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')
);
Probeer deze vraag te beantwoorden met een AI-begeleider