Przygotowanie do rozmowy rekrutacyjnej Node.js Backend

Pytania i odpowiedzi rekrutacyjne z Node.js (2026)

Skoncentrowany zestaw pytań rekrutacyjnych z Node.js dla backend developerów. Odpowiedzi mają strukturę dostosowaną do ćwiczeń ustnych: wyjaśnij architekturę, podaj przykład i wspomnij o kompromisach produkcyjnych.

Rozpocznij próbną rozmowę (Node.js)Karta kredytowa nie jest wymagana. Dostępna 1 darmowa sesja.
Trening technicznych rozmów rekrutacyjnych po angielskuTryb dla osób, dla których angielski nie jest językiem ojczystym, do ćwiczenia rozmów technicznych.

Architektura i pętla zdarzeń (event loop)

1Czym jest Node.js i do czego służy?

Node.js to środowisko uruchomieniowe (runtime) języka JavaScript działające poza przeglądarką, zbudowane na silniku V8 i rozszerzone o interfejsy API (Application Programming Interface) do obsługi sieci, plików, procesów, strumieni oraz dostępu do systemu operacyjnego. Zwykle używa się go do tworzenia interfejsów REST API, usług GraphQL, serwerów WebSocket działających w czasie rzeczywistym, mikrousług, narzędzi CLI (Command-Line Interface), serwerów proxy oraz bram API (API gateways). Jego główną zaletą jest nieblokujące wejście/wyjście (non-blocking I/O): jeden proces może wydajnie obsługiwać wiele połączeń sieciowych. Kosztem takiego rozwiązania jest to, że długie, obciążające procesor obliczenia mogą zablokować pętlę zdarzeń (event loop), o ile nie zostaną przeniesione do wątków roboczych (workers), osobnych procesów lub innej usługi.

Odpowiedz na to pytanie z trenerem AI

2Jak wewnętrznie działa Node.js?

JavaScript jest uruchamiany w silniku V8, podczas gdy API Node.js delegują operacje asynchroniczne do systemu operacyjnego lub do puli wątków biblioteki libuv. Główny wątek JavaScript nie czeka na zakończenie operacji wejścia/wyjścia; kontynuuje wykonywanie innego kodu. Po zakończeniu operacji jej wywołanie zwrotne (callback) lub kontynuacja w postaci obiektu Promise staje się gotowa dla pętli zdarzeń (event loop). W ten sposób Node.js osiąga wysoką współbieżność, mimo że kod JavaScript użytkownika zazwyczaj działa w jednym głównym wątku.

Odpowiedz na to pytanie z trenerem AI

3Czym są V8, libuv oraz pętla zdarzeń (event loop) w Node.js?

V8 kompiluje i wykonuje kod JavaScript, jak również zarządza obiektami JavaScript oraz pamięcią. Biblioteka libuv dostarcza wieloplatformową pętlę zdarzeń, integrację dla asynchronicznych operacji wejścia/wyjścia (I/O) oraz pulę wątków roboczych dla operacji takich jak praca z systemem plików, DNS czy kryptografia. Pętla zdarzeń decyduje, kiedy powinny zostać wykonane wywołania zwrotne (callbacks) zakończonych operacji. Sam Node.js łączy API języka JavaScript z natywnymi mechanizmami; sam silnik V8 nie zapewnia serwera HTTP, dostępu do systemu plików ani API do zarządzania procesami.

Odpowiedz na to pytanie z trenerem AI

4Dlaczego o Node.js często mówi się, że jest jednowątkowy?

Pojedynczy proces Node.js zazwyczaj wykonuje kod JavaScript użytkownika w jednym wątku głównym, więc dwie zwykłe procedury obsługi żądań (request handlers) nie uruchamiają kodu JavaScript dokładnie w tym samym czasie w tym wątku. Platforma nie działa jednak dosłownie na jednym wątku: `libuv` posiada pulę wątków (thread pool), system operacyjny wykonuje asynchroniczne operacje wejścia/wyjścia, silnik V8 realizuje zadania związane z odśmiecaniem pamięci (garbage collection), a sam Node.js wspiera moduł `worker_threads` oraz procesy potomne (child processes). To sformułowanie odnosi się głównie do domyślnego modelu wykonywania kodu JavaScript.

Odpowiedz na to pytanie z trenerem AI

5W jaki sposób Node.js potrafi obsługiwać wiele żądań współbieżnie?

Node.js rozpoczyna operacje wejścia/wyjścia (I/O), a następnie wraca do pętli zdarzeń (event loop) zamiast blokować wątek dla każdego żądania. Przykładowo, 1000 zapytań do bazy danych może być w trakcie realizacji w tle, podczas gdy główny wątek kontynuuje przyjmowanie połączeń i przetwarzanie gotowych wywołań zwrotnych (callbacks). Takie rozwiązanie bardzo dobrze sprawdza się w obciążeniach zdominowanych przez operacje wejścia/wyjścia (I/O-bound), takich jak wywołania HTTP, zapytania do bazy danych, czy operacje na plikach i gniazdach. Działa natomiast słabo, gdy procedury obsługi żądań wykonują obciążającą, synchroniczną pracę obciążającą procesor, ponieważ powoduje to zablokowanie pętli zdarzeń i opóźnienie wszystkich pozostałych żądań.

Odpowiedz na to pytanie z trenerem AI

6Jakie są główne fazy pętli zdarzeń Node.js?

Node.js korzysta z faz planowania libuv, między innymi pending callbacks, idle/prepare, poll, check i close callbacks, wraz z obsługą timerów. poll obsługuje wiele funkcji zwrotnych wejścia/wyjścia, check wykonuje funkcje zwrotne setImmediate, a timery uruchamiają funkcje zwrotne setTimeout/setInterval, gdy upłyną ich minimalne opóźnienia i pozwoli na to harmonogram. W libuv 1.45 i Node.js 20 lub nowszych timery są obsługiwane po poll w każdej iteracji, z początkowym przebiegiem przed wejściem do pętli dla zgodności. process.nextTick korzysta z osobnej kolejki Node, natomiast reakcje Promise i queueMicrotask używają kolejki mikrozadań V8; żadna z tych kolejek nie jest fazą libuv. Kolejki te są wykonywane w punktach kontrolnych między funkcjami zwrotnymi. Ich rekurencyjne uzupełnianie może zagłodzić pozostałą pracę.

Odpowiedz na to pytanie z trenerem AI

7Czym różnią się operacje synchroniczne, asynchroniczne i nieblokujące?

Synchroniczność opisuje przepływ sterowania: wywołanie daje wynik lub zgłasza wyjątek, zanim kod wywołujący będzie kontynuowany. Asynchroniczność opisuje wynik dostarczony później przez funkcję zwrotną, Promise lub inne powiadomienie. Blokowanie oznacza oczekiwanie na pracę lub zasób, podczas którego bieżący wątek nie może robić nic innego; interfejsy nieblokujące unikają tego oczekiwania, na przykład zwracając wynik „operacja by blokowała” albo organizując powiadomienie. Są to różne cechy: synchroniczność sama w sobie nie oznacza oczekiwania na gotowość wejścia/wyjścia, a zwrócenie Promise nie czyni ciężkich obliczeń synchronicznych nieblokującymi. W Node.js fs.readFileSync jest synchronicznym interfejsem blokującym; fs.promises.readFile dostarcza wynik asynchronicznie, podczas gdy główny wątek może obsługiwać inną pracę. W ścieżkach obsługi żądań wybieraj odpowiednie asynchroniczne interfejsy wejścia/wyjścia.

import fs from 'node:fs';

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

const asyncData = await fs.promises.readFile(
  'file.txt',
  'utf8'
);
Odpowiedz na to pytanie z trenerem AI

8Czym jest zagłodzenie pętli zdarzeń?

Zagłodzenie pętli zdarzeń występuje, gdy synchroniczny JavaScript lub stale uzupełniane kolejki punktów kontrolnych uniemożliwiają terminową obsługę funkcji zwrotnych wejścia/wyjścia, timerów i żądań. Przyczynami są między innymi ciężkie obliczenia, duże operacje na JSON, rekurencyjnie planowane mikrozadania i nadmierne użycie process.nextTick. Ograniczaj rozmiary danych wejściowych, profiluj kosztowną pracę i przenoś zadania wymagające wielu obliczeń do worker_threads albo innej usługi. Dzieląc pracę na części, oddawaj sterowanie do późniejszego obrotu pętli zdarzeń, na przykład przez setImmediate; wielokrotne oczekiwanie na już rozstrzygnięte Promise lub planowanie queueMicrotask nadal może zagłodzić wejście/wyjście. Unikaj blokujących interfejsów synchronicznych w często wykonywanych ścieżkach obsługi żądań.

app.get('/report', (req, res) => {
  const result = performVeryHeavyCalculation();
  res.json(result);
});
Odpowiedz na to pytanie z trenerem AI

Programowanie asynchroniczne i obsługa błędów

9Czym jest funkcja zwrotna i na czym polega piekło funkcji zwrotnych?

Funkcja zwrotna jest przekazywana innej operacji, aby ta ją wywołała. Może działać synchronicznie, jak w Array.map, lub asynchronicznie, jak w fs.readFile. Klasyczne asynchroniczne funkcje zwrotne Node.js często stosują konwencję błędu w pierwszym argumencie: callback(error, result). Piekło funkcji zwrotnych to głęboko zagnieżdżone zależne wywołania, które zaciemniają przepływ sterowania i obsługę błędów. Nazwane funkcje, mniejsze operacje, Promise i async/await mogą poprawić czytelność. Gdy to właściwe, uruchamiaj niezależne operacje razem, na przykład przez Promise.all, przestrzegając limitów współbieżności i obsługując błędy.

import fs from 'node:fs';

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

  console.log(data);
});
Odpowiedz na to pytanie z trenerem AI

10Czym jest Promise i jak działa async/await?

Promise reprezentuje przyszły wynik operacji asynchronicznej i może znajdować się w stanie oczekiwania (pending), spełnienia (fulfilled) lub odrzucenia (rejected). Funkcja oznaczona jako async zawsze zwraca Promise. Operator await wstrzymuje wykonanie tylko bieżącej funkcji async do momentu rozstrzygnięcia Promise; nie blokuje on całego procesu Node.js. Dobra odpowiedź powinna wspominać o obsłudze błędów za pomocą bloku try/catch, unikaniu pozostawionych bez nadzoru Promise (floating Promises) oraz używaniu `Promise.all` do niezależnych zadań, aby operacje nie były niepotrzebnie wykonywane sekwencyjnie.

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()
]);
Odpowiedz na to pytanie z trenerem AI

11Czym różnią się process.nextTick, queueMicrotask, setImmediate i setTimeout?

process.nextTick planuje pracę w kolejce next-tick Node; queueMicrotask używa kolejki mikrozadań V8, z której korzystają też reakcje Promise. W zwykłym punkcie kontrolnym na najwyższym poziomie CommonJS funkcje zwrotne nextTick wykonują się przed oczekującymi funkcjami Promise/queueMicrotask. Na najwyższym poziomie modułu ES obliczanie już odbywa się w kontekście mikrozadania, więc mikrozadania w kolejce mogą poprzedzić nextTick. Zależy to od kontekstu i nie stanowi uniwersalnej reguły priorytetu przerywającej wykonywany JavaScript. setImmediate działa w check. setTimeout(fn, 0) czeka na minimalny próg czasowy i nie wykonuje się natychmiast. Ich wzajemna kolejność zależy od miejsca zaplanowania; gdy oba zostaną zaplanowane wewnątrz funkcji zwrotnej wejścia/wyjścia, setImmediate wykona się pierwszy. Preferuj queueMicrotask do przenośnego odraczania pracy jako mikrozadania, chyba że potrzebne jest zachowanie specyficzne dla 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.
Odpowiedz na to pytanie z trenerem AI

12Jak należy obsługiwać błędy w Node.js?

Używaj try/catch do synchronicznie zgłaszanych wyjątków. W operacjach opartych na Promise stosuj await wewnątrz try/catch lub dołącz obsługę odrzucenia; zewnętrzny synchroniczny try/catch nie przechwytuje wyjątku zgłoszonego przez późniejszą funkcję zwrotną. Funkcje zwrotne z błędem jako pierwszym argumentem muszą ten argument sprawdzać, a zdarzenia error w EventEmitter wymagają odpowiednich procedur obsługi. Serwery powinny centralizować obsługę błędów żądań, rejestrować przydatny kontekst i nie ujawniać klientom wewnętrznych szczegółów. Po uncaughtException nie zakładaj, że proces może bezpiecznie działać dalej: wykonaj tylko konieczne synchroniczne sprzątanie awaryjne, zakończ proces i pozwól nadzorcy go uruchomić ponownie. Zaplanowane łagodne zamknięcie jest użyteczne przy normalnym kończeniu pracy, lecz dowolna asynchroniczna próba odtworzenia działania po nieprzechwyconym wyjątku nie jest niezawodną strategią.

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;
});
Odpowiedz na to pytanie z trenerem AI

Moduły i zarządzanie projektem

13Czym są moduły w Node.js?

Moduł to wyizolowana jednostka kodu z wyraźnie wyeksportowanym interfejsem. Node.js obsługuje moduły wbudowane, takie jak node:fs i node:http, lokalne moduły projektu, pakiety npm (Node Package Manager), moduły CommonJS oraz moduły ECMAScript. Moduły sprawiają, że granice odpowiedzialności są wyraźniejsze i umożliwiają ponowne użycie kodu. Kompromisem (trade-off) w środowiskach produkcyjnych jest powiązanie (coupling): jeśli moduły importują się nawzajem zbyt głęboko lub ukrywają skutki uboczne podczas importu, testowanie i zachowanie aplikacji przy uruchamianiu stają się trudniejsze do przeanalizowania.

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

// app.js
import { add } from './math.js';
Odpowiedz na to pytanie z trenerem AI

14Czym różnią się require w CommonJS i import w modułach ES?

CommonJS używa require i module.exports, a moduły ES używają import oraz export. require zwykle ładuje synchronicznie i może być wywoływane warunkowo. Statyczne deklaracje import udostępniają graf zależności do analizy; dynamiczne import() zwraca Promise. Współczesny Node.js może również za pomocą require załadować graf modułów ES bez await na najwyższym poziomie, zgodnie z obsługiwanymi regułami współdziałania. Moduły ES nie oznaczają więc zawsze asynchronicznego wykonania. Rozszerzenia .cjs/.mjs, pole type w package.json i obowiązujące reguły wykrywania składni określają tryb modułu. Moduły ES obsługują await na najwyższym poziomie. CommonJS udostępnia __dirname/__filename; bieżące wersje Node.js udostępniają import.meta.dirname/filename dla modułów ES opartych na plikach, a import.meta.url przydaje się do rozwiązywania adresów URL i podejść zgodnych ze starszymi wersjami.

// 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.
Odpowiedz na to pytanie z trenerem AI

15Czym są npm, package.json i package-lock.json?

npm to interfejs wiersza poleceń do zarządzania pakietami. package.json opisuje metadane, skrypty i wymagania wobec bezpośrednich zależności, często jako zakresy wersji. package-lock.json zapisuje rozwiązane drzewo zależności z wersjami pakietów i informacjami o integralności. Plik blokady zależności aplikacji należy umieścić w kontroli wersji. npm ci jest przydatne w ciągłej integracji, ponieważ instaluje na podstawie pliku blokady, usuwa istniejący katalog node_modules i kończy się błędem, gdy package.json oraz plik blokady są niespójne. Plik blokady ogranicza niezamierzone zmiany zależności; nie ujednolica systemów operacyjnych, kompilacji natywnych, skryptów cyklu życia, wersji Node ani opcji instalacji. Powtarzalne kompilacje wymagają również kontrolowanego zestawu narzędzi i środowiska.

{
  "name": "api",
  "type": "module",
  "scripts": {
    "start": "node src/server.js",
    "test": "node --test"
  },
  "dependencies": {},
  "devDependencies": {}
}
Odpowiedz na to pytanie z trenerem AI

16Jak zarządzać konfiguracją i zmiennymi środowiskowymi?

Odczytuj zmienne środowiskowe przez process.env: obecne wartości są napisami, a brakująca właściwość daje undefined. Waliduj wymagane wartości i świadomie konwertuj je przy uruchamianiu; samo przekształcenie PORT przez Number nie odrzuca pustego napisu, NaN ani portu poza dozwolonym zakresem. Kończ uruchamianie z jasnym błędem, gdy wymagana konfiguracja jest nieprawidłowa. Nie przechowuj rzeczywistych sekretów w kontroli wersji, a sekrety produkcyjne dostarczaj przez platformę wdrożeniową lub menedżera sekretów; przykładową konfigurację bez sekretów można zapisać w repozytorium. NODE_ENV jest konwencją aplikacji, a nie zaufaną granicą bezpieczeństwa.

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');
Odpowiedz na to pytanie z trenerem AI

Zdarzenia, bufory i strumienie

17Czym jest EventEmitter?

Klasa EventEmitter implementuje wzorzec publikuj-subskrybuj (publish-subscribe) wewnątrz procesu środowiska Node.js. Metoda on rejestruje funkcję nasłuchującą (listener), metoda once rejestruje funkcję nasłuchującą na pojedyncze wystąpienie zdarzenia, metoda off usuwa funkcję nasłuchującą, a metoda emit w sposób synchroniczny wywołuje funkcje nasłuchujące w kolejności ich rejestracji. Ta synchroniczna natura jest ważna: powolna funkcja nasłuchująca blokuje nadawcę (emitter). Zdarzenie error jest szczególne, ponieważ nieobsłużone zdarzenie błędu może doprowadzić do awarii i zamknięcia procesu. Długo działające systemy powinny również usuwać nieużywane funkcje nasłuchujące, aby uniknąć wycieków pamięci.

import { EventEmitter } from 'node:events';

const emitter = new EventEmitter();

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

emitter.emit('orderCreated', { id: 42 });
Odpowiedz na to pytanie z trenerem AI

18Do czego używany jest `Buffer`?

`Buffer` reprezentuje sekwencję bajtów o stałym rozmiarze. Używa się go do obsługi plików, gniazd TCP, ciał żądań HTTP, obrazów, kryptografii i protokołów binarnych. `Buffer.from` tworzy bufor z istniejących danych, `Buffer.alloc` tworzy pamięć wypełnioną zerami, a `Buffer.allocUnsafe` jest szybsze, ale może zawierać stare dane z pamięci, dopóki nie zostaną one nadpisane. Sprawia to, że `allocUnsafe` jest odpowiednie tylko wtedy, gdy kod natychmiast wypełnia bufor i nigdy nie eksponuje starych danych.

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.
Odpowiedz na to pytanie z trenerem AI

19Czym są strumienie i jakie są ich typy?

Strumienie przetwarzają dane we fragmentach (chunks), zamiast ładować ich całość do pamięci. Główne typy to `Readable`, `Writable`, `Duplex` i `Transform`. Popularnymi przykładami są pliki, obiekty żądań i odpowiedzi HTTP (Hypertext Transfer Protocol), gniazda TCP (Transmission Control Protocol) oraz strumienie gzip. Strumienie są bardzo przydatne w przypadku dużych plików i ruchu sieciowego, ponieważ zmniejszają zużycie pamięci i opóźnienia. W odpowiedzi na poziomie produkcyjnym należy wspomnieć, że błędy strumieni muszą być prawidłowo obsługiwane w całym łańcuchu przetwarzania.

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;
}
Odpowiedz na to pytanie z trenerem AI

20Czym jest mechanizm przeciwciśnienia (backpressure) i dlaczego funkcja `pipeline` jest przydatna?

Przeciwciśnienie (backpressure) występuje, gdy źródło danych produkuje fragmenty szybciej, niż miejsce docelowe jest w stanie je przetworzyć. Bez mechanizmu przeciwciśnienia zużycie pamięci może rosnąć, dopóki proces nie zwolni lub nie ulegnie awarii. Strumienie i metoda `pipe` mogą koordynować przepływ danych, ale `pipeline` z modułu `node:stream/promises` jest bezpieczniejszym rozwiązaniem dla wieloetapowych łańcuchów, ponieważ propaguje błędy, zamyka powiązane strumienie i zwraca obiekt `Promise`. Jest to dobry domyślny wybór w przypadku kompresji plików, przesyłania i pobierania danych oraz transformacji strumieni.

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')
);
Odpowiedz na to pytanie z trenerem AI