Forberedelse til Node.js backend-interview

Node.js-interviewspørgsmål og svar 2026

Et målrettet sæt af Node.js-interviewspørgsmål til backendudviklere. Svarene er struktureret til mundtlig øvelse: forklar arkitekturen, giv et eksempel og nævn kompromiser i produktionsmiljøer.

Start Node.js-prøvesamtaleIntet kreditkort kræves. 1 gratis session er tilgængelig.
Øv tekniske jobsamtaler på engelskEn tilstand, hvor ikke-modersmålstalende kan øve sig i at bestå tekniske interviews.

Arkitektur og Event Loop

1Hvad er Node.js, og hvad bruges det til?

Node.js er et JavaScript-kørselsmiljø uden for browseren, bygget på V8 og udvidet med API'er (Application Programming Interfaces) til netværk, filer, processer, streams og adgang til operativsystemet. Det bruges ofte til REST API'er, GraphQL-tjenester, realtids WebSocket-servere, mikrotjenester, CLI-værktøjer, proxyer og API-gateways. Dets primære styrke er ikke-blokerende I/O: én proces kan håndtere mange netværksforbindelser effektivt. Kompromiset er, at lange, CPU-tunge beregninger kan blokere event-loopet, medmindre de flyttes til workers, andre processer eller en anden tjeneste.

Prøv at besvare dette spørgsmål med en AI-coach

2Hvordan fungerer Node.js internt?

JavaScript kører i V8, mens Node.js-API'er (Application Programming Interface) uddelegerer asynkront arbejde til operativsystemet eller til libuvs trådpulje. Den primære JavaScript-tråd venter ikke på, at en I/O-operation bliver færdig; den fortsætter med at udføre anden kode. Når operationen er fuldført, bliver dens callback eller `Promise`-fortsættelse klar til event-loopet. Det er sådan, Node.js opnår høj samtidighed, selvom brugerens JavaScript normalt kører på én primær tråd.

Prøv at besvare dette spørgsmål med en AI-coach

3Hvad er V8, libuv og event-loopet?

V8 kompilerer og udfører JavaScript og håndterer JavaScript-objekter og hukommelse. libuv leverer det platformsuafhængige event-loop, asynkron I/O-integration og en trådpulje til operationer som f.eks. visse filsystemopgaver, DNS og kryptografi. Event-loopet beslutter, hvornår callbacks for afsluttede operationer skal køres. Node.js selv forbinder JavaScript-API'er med native-funktioner; V8 alene tilbyder ikke en HTTP-server, filsystemadgang eller proces-API'er.

Prøv at besvare dette spørgsmål med en AI-coach

4Hvorfor kaldes Node.js ofte for enkelttrådet (single-threaded)?

En enkelt Node.js-proces udfører normalt brugerens JavaScript på én hovedtråd, så to almindelige forespørgselshåndterere kører ikke JavaScript på nøjagtig samme tid i den tråd. Platformen består dog ikke bogstaveligt talt af kun én tråd: `libuv` har en trådpulje, styresystemet udfører asynkron I/O, V8 har opgaver med garbage collection, og Node.js understøtter desuden `worker_threads` og underprocesser. Udtrykket beskriver primært standardmodellen for udførelse af JavaScript.

Prøv at besvare dette spørgsmål med en AI-coach

5Hvordan kan Node.js håndtere mange forespørgsler sideløbende (concurrently)?

Node.js starter I/O-operationer og vender derefter tilbage til begivenhedsløkken i stedet for at blokere en tråd per forespørgsel. For eksempel kan 1.000 databasekald være undervejs, mens hovedtråden fortsætter med at acceptere forbindelser og behandle klare callbacks. Dette fungerer godt til I/O-tunge arbejdsbelastninger såsom HTTP-kald, databaseforespørgsler, filer og sockets. Det fungerer dårligt, når forespørgselshåndterere udfører tungt synkront CPU-arbejde, fordi dette blokerer begivenhedsløkken og forsinker alle andre forespørgsler.

Prøv at besvare dette spørgsmål med en AI-coach

6Hvad er hovedfaserne i Node.js' hændelsesløkke?

Node.js bruger libuvs planlægningsfaser, herunder pending callbacks, idle/prepare, poll, check og close callbacks, sammen med timerbehandling. poll håndterer mange I/O-tilbagekald (ind-/uddata), check udfører setImmediate, og timere udfører setTimeout/setInterval, når minimumsforsinkelsen og planlægningen tillader det. I libuv 1.45 og Node.js 20+ behandles timere efter poll i hver iteration, med en indledende kompatibilitetsgennemgang før løkken. process.nextTick bruger en separat Node-kø, mens Promise-reaktioner og queueMicrotask bruger V8's mikrotask-kø; ingen af dem er en libuv-fase. Køerne behandles ved kontrolpunkter mellem tilbagekald. Rekursiv genopfyldning kan udsulte andet arbejde.

Prøv at besvare dette spørgsmål med en AI-coach

7Hvordan adskiller synkrone, asynkrone og ikke-blokerende operationer sig?

Synkron beskriver kontrolflow: Et kald leverer resultatet eller kaster en undtagelse, før kalderen fortsætter. Asynkron beskriver et resultat leveret senere via tilbagekald, Promise eller anden besked. Blokerende betyder ventetid på arbejde eller en ressource, mens den aktuelle tråd ikke kan udføre andet. Ikke-blokerende grænseflader undgår ventetiden, eksempelvis ved at returnere „ville blokere“ eller arrangere en besked. Det er forskellige dimensioner: Synkron betyder ikke i sig selv venten på I/O-parathed (ind-/uddata), og returnering af en Promise gør ikke tunge synkrone beregninger ikke-blokerende. fs.readFileSync er en blokerende synkron grænseflade; fs.promises.readFile leverer resultatet asynkront, mens hovedtråden kan behandle andet arbejde. Foretræk passende asynkrone I/O-grænseflader ved behandling af forespørgsler.

import fs from 'node:fs';

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

const asyncData = await fs.promises.readFile(
  'file.txt',
  'utf8'
);
Prøv at besvare dette spørgsmål med en AI-coach

8Hvad er udsultning af hændelsesløkken?

Det sker, når synkront JavaScript eller konstant genopfyldte kontrolpunktskøer forhindrer rettidig behandling af I/O-tilbagekald (ind-/uddata), timere og forespørgsler. Årsager omfatter tunge beregninger, store JSON-operationer, rekursivt planlagte mikrotasks og overdreven process.nextTick-brug. Begræns inputstørrelser, profilér dyrt arbejde, og flyt beregningstunge opgaver til worker_threads eller en anden tjeneste. Når arbejdet opdeles, skal kontrollen afgives til en senere omgang i løkken, eksempelvis med setImmediate; gentagne await på allerede afgjorte Promise-objekter eller queueMicrotask kan stadig udsulte I/O. Undgå blokerende synkrone grænseflader i hyppigt udførte forespørgselsforløb.

app.get('/report', (req, res) => {
  const result = performVeryHeavyCalculation();
  res.json(result);
});
Prøv at besvare dette spørgsmål med en AI-coach

Asynkron programmering og fejl

9Hvad er et tilbagekald, og hvad er callback hell?

Et tilbagekald er en funktion, der gives til en anden operation, så denne kan kalde den. Det kan køre synkront som ved Array.map eller asynkront som ved fs.readFile. Klassiske asynkrone Node.js-tilbagekald bruger ofte fejlen som første argument: callback(error, result). Callback hell betegner dybt indlejrede afhængige tilbagekald, der gør kontrolflow og fejlhåndtering uoverskuelige. Navngivne funktioner, mindre operationer, Promise og async/await kan forbedre læsbarheden. Start uafhængige operationer sammen, hvor det passer, eksempelvis med Promise.all, og respekter samtidighedsgrænser samt håndter fejl.

import fs from 'node:fs';

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

  console.log(data);
});
Prøv at besvare dette spørgsmål med en AI-coach

10Hvad er et Promise, og hvordan fungerer `async/await`?

Et Promise repræsenterer det fremtidige resultat af en asynkron operation og kan være afventende (pending), opfyldt (fulfilled) eller afvist (rejected). En `async`-funktion returnerer altid et Promise. `await` sætter kun den aktuelle `async`-funktion på pause, indtil det pågældende Promise er afgjort; det blokerer ikke hele Node.js-processen. Et godt svar bør nævne fejlhåndtering med `try/catch`, undgåelse af ikke-håndterede (floating) Promises og brugen af `Promise.all` til uafhængigt arbejde, så operationer ikke unødigt afvikles sekventielt.

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()
]);
Prøv at besvare dette spørgsmål med en AI-coach

11Hvordan adskiller process.nextTick, queueMicrotask, setImmediate og setTimeout sig?

process.nextTick planlægger arbejde i Nodes next-tick-kø; queueMicrotask bruger V8's mikrotask-kø, som også bruges til Promise-reaktioner. Ved et normalt kontrolpunkt på øverste niveau i CommonJS kører nextTick før kølagte Promise/queueMicrotask-tilbagekald. På øverste niveau i et ES-modul foregår evalueringen allerede i en mikrotask-kontekst, så kølagte mikrotasks kan køre før nextTick. Det afhænger af konteksten og er ikke en universel prioritetsregel, der afbryder igangværende JavaScript. setImmediate kører i check. setTimeout(fn, 0) venter på en minimumstærskel og er ikke øjeblikkelig. Deres rækkefølge afhænger af, hvor de planlægges; planlægges begge i et I/O-tilbagekald (ind-/uddata), kører setImmediate først. Foretræk queueMicrotask til portabel udskydelse som mikrotask, medmindre nextTicks særlige adfærd er nødvendig.

// CommonJS file (.cjs): nextTick precedes promise at this top level.
// ES module file (.mjs): promise precedes nextTick at this top level.
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
Promise.resolve().then(() => console.log('promise'));
process.nextTick(() => console.log('nextTick'));
// timeout vs immediate is context-dependent here.
Prøv at besvare dette spørgsmål med en AI-coach

12Hvordan bør fejl håndteres i Node.js?

Brug try/catch til synkrone undtagelser. Ved Promise-baserede operationer bruges await i try/catch eller en afvisningshandler; et ydre synkront try/catch fanger ikke en undtagelse fra et senere tilbagekald. Tilbagekald med fejl som første argument skal kontrollere dette, og EventEmitters error-hændelser kræver passende handlere. Servere bør centralisere forespørgselsfejl, logge nyttig kontekst og undgå at afsløre interne detaljer for klienter. Efter uncaughtException må man ikke antage, at processen sikkert kan fortsætte: Udfør kun nødvendig synkron nødoprydning, afslut, og lad en supervisor genstarte processen. Planlagt kontrolleret nedlukning er nyttig ved normal afslutning, men vilkårlig asynkron genopretning efter en ufanget undtagelse er ikke pålidelig.

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;
});
Prøv at besvare dette spørgsmål med en AI-coach

Moduler og projektstyring

13Hvad er moduler i Node.js?

Et modul er en isoleret kodeenhed med en eksplicit eksporteret grænseflade. Node.js understøtter indbyggede moduler som `node:fs` og `node:http`, lokale projektmoduler, npm-pakker, CommonJS-moduler og ECMAScript-moduler. Moduler gør ansvarsgrænser tydeligere og muliggør genbrug. Kompromisset i produktionsmiljøer er kobling: Hvis moduler importerer hinanden for dybt eller skjuler bivirkninger på importtidspunktet, bliver test og opstartsadfærd sværere at gennemskue.

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

// app.js
import { add } from './math.js';
Prøv at besvare dette spørgsmål med en AI-coach

14Hvordan adskiller CommonJS require og ES-modulers import sig?

CommonJS bruger require og module.exports; ES-moduler bruger import og export. require indlæser normalt synkront og kan kaldes betinget. Statiske import-erklæringer gør afhængighedsgrafen tilgængelig for analyse; dynamisk import() returnerer en Promise. Moderne Node.js kan også bruge require på en ES-modulgraf uden await på øverste niveau efter de understøttede interoperabilitetsregler, så ES-moduler betyder ikke altid asynkron evaluering. .cjs/.mjs, type i package.json og relevante syntaksdetektionsregler bestemmer modultilstanden. ES-moduler understøtter await på øverste niveau. CommonJS tilbyder __dirname/__filename; aktuelle Node.js-versioner tilbyder import.meta.dirname/filename til filbaserede ES-moduler. import.meta.url er nyttig til URL-baseret opløsning og fremgangsmåder, der understøtter ældre versioner.

// File: math.cjs (CommonJS)
function calculate(a, b) { return a + b; }
module.exports = { calculate };

// Separate file: math.mjs (ES module)
export function calculate(a, b) { return a + b; }
// These are two separate files, not one combined script.
Prøv at besvare dette spørgsmål med en AI-coach

15Hvad er npm, package.json og package-lock.json?

npm er kommandolinjegrænsefladen til pakkehåndtering. package.json beskriver metadata, scripts og krav til direkte afhængigheder, ofte som versionsintervaller. package-lock.json registrerer et opløst afhængighedstræ med pakkeversioner og integritetsoplysninger. Gem applikationens låsefil i versionsstyring. npm ci er nyttig i kontinuerlig integration: Den installerer fra låsefilen, fjerner en eksisterende node_modules-mappe og fejler ved uoverensstemmelse med package.json. En låsefil reducerer ændringer i afhængigheder; den gør ikke operativsystemer, native builds, livscyklusscripts, Node-versioner eller installationsvalg ens. Reproducerbare builds kræver også kontrolleret værktøjskæde og miljø.

{
  "name": "api",
  "type": "module",
  "scripts": {
    "start": "node src/server.js",
    "test": "node --test"
  },
  "dependencies": {},
  "devDependencies": {}
}
Prøv at besvare dette spørgsmål med en AI-coach

16Hvordan bør konfiguration og miljøvariabler håndteres?

Læs miljøvariabler gennem process.env: Eksisterende værdier er strenge, mens en manglende egenskab er undefined. Validér nødvendige værdier, og konvertér dem bevidst ved opstart; konvertering af PORT alene med Number afviser ikke en tom streng, NaN eller en port uden for det tilladte interval. Stop med en tydelig fejl ved ugyldig obligatorisk konfiguration. Hold rigtige hemmeligheder ude af versionsstyring, og lever produktionshemmeligheder gennem platformen eller en hemmelighedshåndtering; en eksempelkonfiguration uden hemmeligheder kan versionsstyres. NODE_ENV er en applikationskonvention, ikke en betroet sikkerhedsgrænse.

const rawPort = process.env.PORT ?? '3000';
if (!/^\d+$/.test(rawPort)) throw new Error('PORT must be an integer');
const port = Number(rawPort);
if (!Number.isSafeInteger(port) || port < 1 || port > 65535) {
  throw new Error('PORT must be between 1 and 65535');
}
const databaseUrl = process.env.DATABASE_URL;
if (!databaseUrl?.trim()) throw new Error('DATABASE_URL is required');
Prøv at besvare dette spørgsmål med en AI-coach

Events, Buffers og Streams

17Hvad er `EventEmitter`?

`EventEmitter` implementerer et publish-subscribe-mønster inde i en Node.js-proces. `on` registrerer en lytter (listener), `once` registrerer en lytter til en enkelt hændelse, `off` fjerner en lytter, og `emit` kalder lyttere synkront i den rækkefølge, de blev registreret. Den synkrone natur er vigtig: en langsom lytter blokerer emitteren. Hændelsen `error` er speciel, fordi en uhåndteret fejlhændelse kan få processen til at gå ned. Længerevarende systemer bør også fjerne ubrugte lyttere for at undgå hukommelseslæk (memory leaks).

import { EventEmitter } from 'node:events';

const emitter = new EventEmitter();

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

emitter.emit('orderCreated', { id: 42 });
Prøv at besvare dette spørgsmål med en AI-coach

18Hvad bruges Buffer til?

Buffer repræsenterer en sekvens af bytes med en fast størrelse. Den bruges til filer, TCP-sockets, HTTP-bodies, billeder, kryptografi og binære protokoller. Buffer.from opretter en buffer fra eksisterende data, Buffer.alloc opretter nul-udfyldt hukommelse, og Buffer.allocUnsafe er hurtigere, men kan indeholde gammel hukommelse, indtil den overskrives. Det gør allocUnsafe velegnet, men kun når koden umiddelbart udfylder bufferen og aldrig eksponerer forældede data.

const buffer = Buffer.from('Hello', 'utf8');
console.log(buffer.toString('utf8'));
const safe = Buffer.alloc(1024);
// Prefer alloc for general use. Never expose allocUnsafe bytes before filling them.
const fast = Buffer.allocUnsafe(1024);
fast.fill(0); // All bytes initialized before use.
Prøv at besvare dette spørgsmål med en AI-coach

19Hvad er streams, og hvilke typer findes der?

Streams behandler data i bidder (chunks) i stedet for at indlæse det hele i hukommelsen. De primære typer er `Readable`, `Writable`, `Duplex` og `Transform`. Filer, HTTP-forespørgsels- og svarobjekter (Hypertext Transfer Protocol), TCP-sockets (Transmission Control Protocol) og gzip-streams er almindelige eksempler. Streams er værdifulde til store filer og netværkstrafik, fordi de reducerer hukommelsesforbruget og ventetiden (latency). Et produktionsklart svar bør nævne, at stream-fejl skal håndteres korrekt på tværs af hele kæden.

import fs from 'node:fs';
import { pipeline } from 'node:stream/promises';
try {
  await pipeline(
    fs.createReadStream('large.log'),
    fs.createWriteStream('copy.log')
  );
} catch (error) {
  console.error('Copy failed:', error);
  process.exitCode = 1;
}
Prøv at besvare dette spørgsmål med en AI-coach

20Hvad er modtryk (backpressure), og hvorfor er `pipeline` nyttig?

Modtryk opstår, når en datakilde producerer datablokke hurtigere, end destinationen kan nå at forbruge dem. Uden modtryk kan hukommelsesforbruget vokse, indtil processen bliver langsommere eller går ned. Streams og `pipe` kan koordinere datastrømmen, men `pipeline` fra `node:stream/promises` er mere sikker til flertrins-kæder, fordi den videresender fejl, lukker relaterede streams og returnerer et `Promise`. Det er et godt standardvalg til filkomprimering, uploads, downloads og stream-transformationer.

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')
);
Prøv at besvare dette spørgsmål med en AI-coach