Otázky a odpovědi k pohovoru z Node.js pro rok 2026
Cílená sada otázek k pohovoru z Node.js pro backendové vývojáře. Odpovědi jsou strukturovány pro nácvik mluveného projevu: vysvětlete architekturu, uveďte příklad a zmiňte kompromisy v produkčním nasazení.
Node.js je běhové prostředí pro JavaScript mimo prohlížeč, postavené na enginu V8 a rozšířené o API (Application Programming Interface) pro sítě, soubory, procesy, datové toky (streams) a přístup k operačnímu systému. Běžně se používá pro REST API, služby GraphQL, servery WebSocket v reálném čase, mikroslužby, nástroje CLI (Command Line Interface), proxy servery a API brány (API gateways). Jeho hlavní předností jsou neblokující V/V operace (non-blocking I/O): jeden proces dokáže efektivně obsloužit mnoho síťových připojení. Nevýhodou je, že výpočty náročné na procesor mohou zablokovat smyčku událostí (event loop), pokud nejsou přesunuty do pracovních vláken (workers), samostatných procesů nebo jiné služby.
JavaScript běží v enginu V8, zatímco API (Application Programming Interface) v Node.js delegují asynchronní práci na operační systém nebo na fond vláken knihovny libuv. Hlavní vlákno JavaScriptu nečeká na dokončení I/O (Input/Output) operace; pokračuje ve vykonávání dalšího kódu. Jakmile se operace dokončí, její zpětné volání (callback) nebo pokračování pomocí `Promise` je připraveno pro smyčku událostí (event loop). Tímto způsobem Node.js dosahuje vysoké souběžnosti, přestože uživatelský JavaScript obvykle běží v jednom hlavním vlákně.
3Co jsou to V8, knihovna libuv a smyčka událostí (event loop)?
V8 kompiluje a vykonává JavaScript a spravuje javascriptové objekty a paměť. Knihovna libuv poskytuje multiplatformní smyčku událostí, integraci asynchronního I/O (Input/Output) a fond vláken (worker thread pool) pro operace, jako je práce se souborovým systémem, překlad DNS (Domain Name System) a kryptografie. Smyčka událostí rozhoduje o tom, kdy se mají spustit zpětná volání (callbacks) dokončených operací. Samotný Node.js propojuje API JavaScriptu s nativními možnostmi systému; samotný engine V8 neposkytuje HTTP server, přístup k souborovému systému ani API pro práci s procesy.
4Proč je Node.js často označován jako jednovláknový (single-threaded)?
Jediný proces Node.js obvykle vykonává uživatelský JavaScript v jednom hlavním vlákně, takže dva běžné obslužné programy požadavků (request handlers) nespouštějí JavaScript v tomto vlákně přesně ve stejný okamžik. Platforma však doslovně není jen jedním vláknem: knihovna `libuv` má fond vláken, operační systém provádí asynchronní I/O (Input/Output) operace, jádro V8 vykonává práci pro GC (Garbage Collector) a Node.js také podporuje `worker_threads` a dceřiné procesy. Tento pojem popisuje především výchozí model vykonávání jazyka JavaScript.
5Jak dokáže Node.js souběžně zpracovávat velké množství požadavků?
Node.js zahájí I/O (Input/Output) operace a poté se vrátí do smyčky událostí (event loop), místo aby blokoval jedno vlákno pro každý požadavek. Například může probíhat 1 000 volání databáze, zatímco hlavní vlákno nadále přijímá nová spojení a zpracovává připravená zpětná volání (callbacks). Tento přístup funguje skvěle pro zátěž vázanou na I/O (I/O-bound workloads), jako jsou volání HTTP (Hypertext Transfer Protocol), databázové dotazy, operace se soubory a sokety. Naopak funguje špatně, když obsluhy požadavků vykonávají náročnou synchronní práci na CPU (Central Processing Unit), protože to zablokuje smyčku událostí a zpozdí všechny ostatní požadavky.
Node.js používá fáze libuv včetně pending callbacks, idle/prepare, poll, check a close callbacks spolu se zpracováním časovačů. poll obsluhuje mnoho zpětných volání vstupu/výstupu, check spouští setImmediate a časovače spouštějí setTimeout/setInterval, jakmile uplyne minimální prodleva a dovolí to plánování. V libuv 1.45 a Node.js 20+ se časovače zpracovávají po poll v každé iteraci, s úvodním průchodem před vstupem do smyčky kvůli kompatibilitě. process.nextTick má samostatnou frontu Node, zatímco reakce Promise a queueMicrotask využívají frontu mikrouloh V8; ani jedna není fází libuv. Tyto fronty běží v kontrolních bodech mezi zpětnými voláními. Rekurzivní doplňování může vyhladovět ostatní práci.
7Jak se liší synchronní, asynchronní a neblokující operace?
Synchronní popisuje tok řízení: volání vrátí výsledek nebo vyvolá výjimku před pokračováním volajícího. Asynchronní popisuje výsledek dodaný později zpětným voláním, Promise či jiným oznámením. Blokování znamená čekání na práci nebo prostředek, během něhož aktuální vlákno nemůže dělat jinou práci. Neblokující rozhraní se čekání vyhýbají, například návratem „operace by blokovala“ nebo zajištěním oznámení. Jde o různé vlastnosti: synchronní samo o sobě neznamená čekání na připravenost vstupu/výstupu a návrat Promise neudělá náročné synchronní výpočty neblokujícími. fs.readFileSync je v Node.js blokující synchronní rozhraní; fs.promises.readFile dodává výsledek asynchronně, zatímco hlavní vlákno může zpracovávat jinou práci. Při obsluze požadavků preferujte vhodná asynchronní rozhraní vstupu/výstupu.
Nastává, když synchronní JavaScript nebo neustále doplňované fronty kontrolních bodů brání včasnému zpracování zpětných volání vstupu/výstupu, časovačů a požadavků. Příčinou mohou být náročné výpočty, velké operace JSON, rekurzivně plánované mikroulohy a nadměrné používání process.nextTick. Omezujte velikost vstupů, profilujte nákladnou práci a výpočetně náročné úlohy přesouvejte do worker_threads nebo jiné služby. Při dělení práce na části předávejte řízení pozdějšímu průchodu smyčky, například pomocí setImmediate; opakované čekání na již vyřešené Promise či plánování queueMicrotask může vstup/výstup stále vyhladovět. V často prováděných cestách požadavků se vyhýbejte blokujícím synchronním rozhraním.
9Co je zpětné volání a co znamená peklo zpětných volání?
Zpětné volání je funkce předaná jiné operaci, aby ji tato operace zavolala. Může běžet synchronně, jako u Array.map, nebo asynchronně, jako u fs.readFile. Klasická asynchronní zpětná volání Node.js obvykle používají chybu jako první argument: callback(error, result). Peklo zpětných volání označuje hluboce vnořená závislá volání, která znepřehledňují tok řízení a obsluhu chyb. Pojmenované funkce, menší operace, Promise a async/await mohou čitelnost zlepšit. Nezávislé operace případně spouštějte společně, například přes Promise.all, při respektování limitů souběžnosti a ošetření chyb.
import fs from 'node:fs';
fs.readFile('file.txt', 'utf8', (err, data) => {
if (err) {
console.error(err);
return;
}
console.log(data);
});
Objekt `Promise` představuje budoucí výsledek asynchronní operace a může se nacházet ve stavech čekající (pending), splněno (fulfilled) nebo zamítnuto (rejected). Funkce označená jako `async` vždy vrací `Promise`. Klíčové slovo `await` pozastaví pouze aktuální `async` funkci, dokud se `Promise` nevyřeší; neblokuje celý proces Node.js. Dobrá odpověď by měla zmínit zpracování chyb pomocí `try/catch`, vyvarování se takzvaných plovoucích (floating) `Promise` a využití `Promise.all` pro nezávislé úlohy, aby operace neprobíhaly zbytečně sekvenčně.
11Jak se liší process.nextTick, queueMicrotask, setImmediate a setTimeout?
process.nextTick plánuje práci do fronty next-tick Node; queueMicrotask používá frontu mikrouloh V8, kterou využívají také reakce Promise. V běžném kontrolním bodě na nejvyšší úrovni CommonJS běží nextTick před čekajícími voláními Promise/queueMicrotask. Na nejvyšší úrovni ES modulu již vyhodnocování probíhá v kontextu mikroulohy, takže čekající mikroulohy mohou předběhnout nextTick. Záleží na kontextu; nejde o univerzální pravidlo priority přerušující běžící JavaScript. setImmediate běží ve fázi check. setTimeout(fn, 0) čeká na minimální časový práh a není okamžitý. Jejich vzájemné pořadí závisí na místě naplánování; uvnitř stejného zpětného volání vstupu/výstupu se setImmediate provede první. Pro přenositelné odložení do mikroulohy preferujte queueMicrotask, nepotřebujete-li chování specifické pro nextTick.
// 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.
Pro synchronní výjimky používejte try/catch. U operací založených na Promise použijte await uvnitř try/catch nebo připojte obsluhu odmítnutí; vnější synchronní try/catch nezachytí výjimku pozdějšího zpětného volání. Volání s chybou jako prvním argumentem ji musí kontrolovat a události error u EventEmitter potřebují odpovídající obsluhu. Servery mají centralizovat obsluhu chyb požadavků, zaznamenávat užitečný kontext a neodhalovat klientům interní podrobnosti. Po uncaughtException nepředpokládejte bezpečné pokračování procesu: proveďte jen nezbytný synchronní nouzový úklid, ukončete proces a nechte jej restartovat dohledovým procesem. Plánované korektní ukončení je užitečné při běžném zastavení; libovolná asynchronní obnova po nezachycené výjimce není spolehlivá strategie.
Modul je izolovaná jednotka kódu s explicitně exportovaným rozhraním. Node.js podporuje vestavěné moduly, jako jsou `node:fs` a `node:http`, lokální projektové moduly, balíčky npm (Node Package Manager), moduly CommonJS a moduly ECMAScript. Moduly zpřehledňují hranice zodpovědností a umožňují znovupoužitelnost. Kompromisem v produkčním prostředí je úroveň provázanosti (coupling): pokud se moduly navzájem importují příliš hluboce nebo skrývají vedlejší efekty (side effects) během importu, je složitější porozumět jejich testování a chování při startu.
// math.js
export function add(a, b) {
return a + b;
}
// app.js
import { add } from './math.js';
14Jak se liší require v CommonJS a import v ES modulech?
CommonJS používá require a module.exports; ES moduly používají import a export. require zpravidla načítá synchronně a lze jej volat podmíněně. Statické deklarace import zpřístupňují graf závislostí analýze; dynamický import() vrací Promise. Moderní Node.js může přes require načíst i graf ES modulů bez await na nejvyšší úrovni podle podporovaných pravidel interoperability, takže ES moduly neznamenají vždy asynchronní vyhodnocování. Režim určují přípony .cjs/.mjs, type v package.json a příslušná pravidla detekce syntaxe. ES moduly podporují await na nejvyšší úrovni. CommonJS poskytuje __dirname/__filename; současné Node.js poskytuje pro souborové ES moduly import.meta.dirname/filename. import.meta.url se hodí pro řešení cest pomocí URL a postupy kompatibilní se staršími verzemi.
// 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.
npm je rozhraní příkazového řádku pro správu balíčků. package.json popisuje metadata, skripty a požadavky na přímé závislosti, často jako rozsahy verzí. package-lock.json zaznamenává vyřešený strom závislostí s verzemi balíčků a údaji o integritě. Zamykací soubor aplikace ukládejte do správy verzí. npm ci se hodí pro průběžnou integraci: instaluje podle zamykacího souboru, odstraní existující node_modules a selže při nesouladu s package.json. Zamykací soubor omezuje nechtěné změny závislostí; nesjednocuje operační systémy, nativní sestavení, skripty životního cyklu, verze Node ani instalační volby. Reprodukovatelná sestavení vyžadují také řízené nástroje a prostředí.
Proměnné prostředí čtěte přes process.env: přítomné hodnoty jsou řetězce, chybějící vlastnost je undefined. Požadované hodnoty při startu ověřujte a záměrně převádějte; samotný převod PORT přes Number neodmítne prázdný řetězec, NaN ani port mimo povolený rozsah. Při neplatné povinné konfiguraci skončete s jasnou chybou. Skutečná tajemství neukládejte do správy verzí a produkční tajemství dodávejte přes platformu nasazení nebo správce tajemství; ukázkovou konfiguraci bez tajemství lze verzovat. NODE_ENV je konvence aplikace, nikoli důvěryhodná bezpečnostní hranice.
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');
`EventEmitter` implementuje návrhový vzor publish-subscribe uvnitř procesu Node.js. Metoda `on` registruje posluchače (listener), `once` registruje posluchače pro jednu událost, `off` odebírá posluchače a `emit` synchronně volá posluchače v pořadí jejich registrace. Synchronní povaha je důležitá: pomalý posluchač zablokuje emitter. Událost `error` je speciální, protože neobsloužená událost `error` může způsobit pád procesu. Dlouhodobě běžící systémy by také měly odebírat nepoužívané posluchače, aby se zabránilo únikům paměti (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 });
`Buffer` představuje posloupnost bajtů o pevné velikosti. Používá se pro soubory, TCP sokety, těla HTTP požadavků, obrázky, kryptografii a binární protokoly. `Buffer.from` vytvoří buffer z existujících dat, `Buffer.alloc` vytvoří paměť vyplněnou nulami a `Buffer.allocUnsafe` je rychlejší, ale může obsahovat starou paměť, dokud není přepsána. Z toho důvodu je `allocUnsafe` vhodné použít pouze tehdy, když kód buffer okamžitě naplní a nikdy nevystaví neaktuální 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.
19Co jsou to proudy (streams) a jaké typy existují?
Proudy zpracovávají data po částech (chunks) místo toho, aby načítaly všechno do paměti. Hlavní typy jsou Readable, Writable, Duplex a Transform. Běžnými příklady jsou soubory, objekty HTTP (Hypertext Transfer Protocol) požadavků a odpovědí, TCP (Transmission Control Protocol) sockety a gzip proudy. Proudy jsou cenné pro velké soubory a síťový provoz, protože snižují využití paměti a latenci. Odpověď pro produkční úroveň by měla zmínit, že chyby v proudech musí být správně ošetřeny napříč celým řetězcem.
20Co je to zpětný tlak (backpressure) a proč je užitečná funkce `pipeline`?
Ke zpětnému tlaku (backpressure) dochází, když zdroj dat produkuje části dat (chunks) rychleji, než je cíl dokáže spotřebovat. Bez zpětného tlaku může spotřeba paměti růst, dokud se proces nezpomalí nebo nespadne. Proudy (streams) a funkce `pipe` dokážou koordinovat tok, ale funkce `pipeline` z modulu `node:stream/promises` je bezpečnější pro vícekrokové řetězce, protože propaguje chyby, uzavírá související proudy a vrací `Promise`. Je to dobrá výchozí volba pro kompresi souborů, nahrávání, stahování a transformace proudů.
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')
);