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 płatnicza nie jest wymagana. Dostępna 1 darmowa sesja.
Ćwiczenie rozmów technicznych 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ń (event loop) w Node.js?

Pętla zdarzeń (event loop) biblioteki libuv obejmuje fazy takie jak: timery, oczekujące wywołania zwrotne (pending callbacks), idle/prepare, poll, check oraz close callbacks. Faza timerów obsługuje wywołania zwrotne z funkcji setTimeout i setInterval; faza poll obsługuje wiele zdarzeń wejścia/wyjścia (I/O); faza check uruchamia wywołania zwrotne setImmediate; faza close obsługuje zdarzenia zamknięcia, takie jak zamykanie gniazd (sockets). Node.js posiada również kolejki mikrozadań (microtask queues), w tym dla process.nextTick oraz mikrozadań obiektów Promise, które są uruchamiane pomiędzy wywołaniami zwrotnymi. Zbyt duża liczba mikrozadań może zagłodzić operacje I/O i sprawić, że usługa będzie wyglądać na zablokowaną.

Odpowiedz na to pytanie z trenerem AI

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

Operacja synchroniczna zwraca wynik, zanim zostanie wykonana następna linia kodu i blokuje główny wątek, jak na przykład `fs.readFileSync` w funkcji obsługującej żądanie. Operacja asynchroniczna zwraca swój wynik później poprzez wywołanie zwrotne (callback) lub obiekt `Promise`. Nieblokująca oznacza, że wątek może kontynuować wykonywanie innej pracy, podczas gdy operacja oczekuje na zakończenie. Pojęcia te są ze sobą powiązane, ale nie są identyczne: asynchroniczność opisuje, w jaki sposób dostarczany jest wynik, podczas gdy brak blokowania określa, czy wątek czeka. Funkcje obsługujące żądania na backendzie powinny na ogół preferować asynchroniczne, nieblokujące interfejsy API (Application Programming Interfaces).

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ń (event loop starvation)?

Zagłodzenie pętli zdarzeń (event loop starvation) występuje, gdy operacje w języku JavaScript lub mikrozadania utrzymują pętlę zdarzeń (event loop) w ciągłym użyciu, uniemożliwiając jej szybkie przetwarzanie wejścia/wyjścia (I/O - Input/Output), liczników czasu czy nowych żądań. Do typowych przyczyn należą: ciężkie obliczenia synchroniczne, przetwarzanie ogromnych obiektów JSON (JavaScript Object Notation), nieograniczone łańcuchy Promise oraz nadużywanie process.nextTick. Rozwiązania obejmują: dzielenie pracy na mniejsze porcje, ograniczanie rozmiaru danych wejściowych, profilowanie procesora CPU (Central Processing Unit), przenoszenie zadań obciążających procesor do worker_threads lub innego serwisu oraz unikanie synchronicznych API (Application Programming Interface) w często wykonywanych fragmentach kodu obsługujących żądania.

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 wywołanie zwrotne (callback) i czym jest piekło wywołań zwrotnych (callback hell)?

Wywołanie zwrotne (callback) to funkcja przekazywana do późniejszego wykonania, często po zakończeniu operacji asynchronicznej. Klasyczne wywołania zwrotne w Node.js powszechnie stosują styl, w którym błąd jest pierwszym argumentem (error-first): `callback(error, result)`. Piekło wywołań zwrotnych (callback hell) występuje, gdy operacje zależne od siebie są głęboko zagnieżdżone, co sprawia, że obsługa błędów i przepływ sterowania stają się trudne do odczytania. Rozwiązaniem jest podział na nazwane funkcje, użycie obiektów `Promise` lub `async/await` oraz uruchamianie niezależnych operacji za pomocą `Promise.all` zamiast ich zagnieżdżania.

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 wykonuje się po bieżącej operacji, zanim pętla zdarzeń (event loop) przejdzie do kolejnego etapu. queueMicrotask planuje standardowe mikrozadanie JavaScript, podobnie jak kontynuacje obiektów Promise. setImmediate uruchamia się w fazie sprawdzania (check phase). setTimeout(fn, 0) wykonuje się w fazie liczników czasu (timers phase) po minimalnym opóźnieniu. Zazwyczaj nextTick uruchamia się przed mikrozadaniami obiektów Promise, a kolejność setTimeout(0) i setImmediate zależy od kontekstu; po wywołaniach zwrotnych (callbacks) wejścia/wyjścia (I/O), setImmediate często uruchamia się jako pierwsze.

setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
Promise.resolve().then(() => console.log('promise'));
process.nextTick(() => console.log('nextTick'));
Odpowiedz na to pytanie z trenerem AI

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

Synchroniczny kod używa `try/catch`. Kod oparty na obiektach `Promise` powinien korzystać z `await` wewnątrz bloku `try/catch` lub wywoływać metodę `.catch`. Wywołania zwrotne z argumentem błędu na pierwszym miejscu (error-first callbacks) powinny sprawdzać obecność błędu przed użyciem wyniku. W aplikacjach serwerowych kluczowe znaczenie mają: centralne oprogramowanie pośredniczące (middleware) do obsługi błędów, logowanie powiązane z kontekstem żądania, bezpieczne komunikaty dla klientów oraz łagodne zamykanie aplikacji (graceful shutdown). Zdarzenie `uncaughtException` nie powinno być używane do utrzymywania uszkodzonego procesu w działaniu; zazwyczaj jest to sygnał do zalogowania błędu, oczyszczenia zasobów i restartu.

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ę od siebie `require` w CommonJS oraz `import` w ES Modules?

CommonJS używa `require` i `module.exports`, podczas gdy ES Modules korzysta z `import` i `export`. ES Modules wspierają analizę statyczną oraz użycie `await` na najwyższym poziomie (top-level await), a wybiera się je poprzez `type: module` w pliku `package.json` lub użycie plików z rozszerzeniem `.mjs`. Moduły CommonJS są często ładowane synchronicznie i mogą być oznaczone rozszerzeniem `.cjs`. W nowoczesnych projektach w Node.js współistnieją oba te systemy, dlatego dobra odpowiedź unika nadmiernego upraszczania i wyjaśnia, że to rozszerzenie pliku, `package.json` oraz kontekst projektu decydują o trybie ładowania modułów.

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

// ES Modules
import fs from 'node:fs';
export { calculate };
Odpowiedz na to pytanie z trenerem AI

15Czym są narzędzie npm oraz pliki `package.json` i `package-lock.json`?

Narzędzie npm to menedżer pakietów oraz interfejs wiersza poleceń (CLI - Command Line Interface). Plik `package.json` opisuje skrypty, metadane oraz zbiory zależności `dependencies` i `devDependencies`. Plik `package-lock.json` zapisuje dokładne drzewo zależności, dzięki czemu instalacje są powtarzalne. W środowiskach ciągłej integracji CI (Continuous Integration) zazwyczaj preferuje się polecenie `npm ci`, ponieważ instaluje ono pakiety ściśle z pliku blokady (lock file) i przerywa proces z błędem w sytuacji, gdy plik `package.json` nie zgadza się z plikiem blokady. Takie podejście chroni procesy budowania (builds) aplikacji przed przypadkowymi i niekontrolowanymi zmianami wersji w zależnościach (dependency drift).

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

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

Środowisko Node.js odczytuje zmienne środowiskowe za pomocą process.env, ale ich wartości są ciągami znaków i powinny być zwalidowane oraz przekonwertowane podczas uruchamiania aplikacji. Sekrety nie powinny być przechowywane w repozytorium, plik .env powinien być ignorowany, a sekrety produkcyjne powinny pochodzić z platformy lub menedżera sekretów (secret manager). Dobrze zaprojektowane usługi przerywają działanie na wczesnym etapie (fail fast), gdy brakuje wymaganej konfiguracji. Zmienna NODE_ENV jest jedynie konwencją, a nie granicą bezpieczeństwa (security boundary), dlatego kod nie powinien polegać na niej jako na mechanizmie zabezpieczającym.

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

if (!databaseUrl) {
  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);
console.log(buffer.toString('utf8'));

const safe = Buffer.alloc(1024);
const fast = Buffer.allocUnsafe(1024);
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';

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

input.pipe(output);
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