Pytania na rozmowie kwalifikacyjnej z Node.js Backend: Kompletny przewodnik 2026
25 najczęściej zadawanych pytań na rozmowie kwalifikacyjnej z Node.js backend. Event loop, async/await, strumienie, klasteryzacja i wydajność z szczegółowymi odpowiedziami.

Rozmowy kwalifikacyjne z Node.js backend weryfikują zrozumienie wewnętrznych mechanizmów środowiska uruchomieniowego, biegłość w asynchronicznych wzorcach programowania oraz zdolność do projektowania wydajnych aplikacji. Niniejszy przewodnik obejmuje najczęściej zadawane pytania, od podstaw po zaawansowane koncepcje produkcyjne.
Rekruterzy cenią odpowiedzi łączące teorię z praktycznymi przykładami. Przy każdym pytaniu warto zilustrować odpowiedź kodem lub konkretnym przypadkiem użycia, co świadczy o rzeczywistym doświadczeniu.
Podstawy Node.js
Pytanie 1: Czym jest Event Loop i jak działa?
Event Loop to podstawowy mechanizm pozwalający Node.js obsługiwać operacje asynchroniczne w sposób nieblokujący, mimo działania na jednym wątku. Koordynuje wykonywanie kodu JavaScript, callbacków i zdarzeń.
// Demonstration of Event Loop execution order
console.log('1. Script start (synchronous)');
// setTimeout goes to the Timer Queue
setTimeout(() => {
console.log('5. setTimeout callback (Timer Queue)');
}, 0);
// setImmediate goes to the Check Queue
setImmediate(() => {
console.log('6. setImmediate callback (Check Queue)');
});
// Promise goes to the Microtask Queue (priority)
Promise.resolve().then(() => {
console.log('3. Promise.then (Microtask Queue)');
});
// process.nextTick has the highest priority
process.nextTick(() => {
console.log('2. process.nextTick (nextTick Queue)');
});
console.log('4. Script end (synchronous)');
// Output order: 1, 4, 2, 3, 5, 6Event Loop przetwarza kolejki w ściśle określonej kolejności: najpierw kod synchroniczny, następnie nextTick, mikrozadania (Promise), timery, callbacki I/O, setImmediate, a na końcu callbacki zamknięcia. Szczegółowe omówienie wpływu na wydajność znajduje się w artykule Node.js Performance: Event Loop, Clustering and Optimization.
Pytanie 2: Jaka jest różnica między process.nextTick() a setImmediate()?
To pytanie sprawdza szczegółowe zrozumienie priorytetów wykonywania w Event Loop. Od wersji Node.js v22.7.0 funkcja process.nextTick() otrzymała status Legacy, a zalecaną alternatywą dla nowego kodu jest queueMicrotask().
// Behavior comparison with modern recommendations
// process.nextTick executes BEFORE the next Event Loop phase
// LEGACY: use queueMicrotask() for new code
process.nextTick(() => {
console.log('nextTick 1');
process.nextTick(() => {
console.log('nextTick 2 (nested)');
});
});
// queueMicrotask is the modern replacement (Web-standard API)
queueMicrotask(() => {
console.log('microtask 1');
queueMicrotask(() => {
console.log('microtask 2 (nested)');
});
});
// setImmediate executes in the Check phase of the Event Loop
setImmediate(() => {
console.log('setImmediate 1');
setImmediate(() => {
console.log('setImmediate 2 (nested)');
});
});
// CJS output: nextTick 1, nextTick 2, microtask 1, microtask 2, setImmediate 1, setImmediate 2
// ESM output: microtask 1, microtask 2, nextTick 1, nextTick 2, setImmediate 1, setImmediate 2process.nextTick() jest przetwarzany natychmiast po bieżącej operacji, zanim Event Loop przejdzie dalej. Nadmierne użycie może zablokować Event Loop. setImmediate() jest bardziej przewidywalny i zalecany do odraczania wykonywania. Dla nowego kodu zaleca się queueMicrotask(), ponieważ jest zgodny ze standardem platformy Web i działa spójnie w różnych środowiskach uruchomieniowych.
Rekurencyjne wywołania process.nextTick() mogą zagłodzić Event Loop i uniemożliwić przetwarzanie operacji I/O. Dlatego Node.js oznaczył tę funkcję jako Legacy. Dla niekrytycznych operacji lepiej stosować queueMicrotask() lub setImmediate().
Pytanie 3: Jak Node.js obsługuje błędy w kodzie asynchronicznym?
Obsługa błędów asynchronicznych różni się zasadniczo od kodu synchronicznego. Bez odpowiedniej obsługi błąd może spowodować awarię całej aplikacji.
// Asynchronous error handling patterns
// Pattern 1: Callbacks with error-first convention
function readFileCallback(path, callback) {
const fs = require('fs');
fs.readFile(path, 'utf8', (err, data) => {
if (err) {
// Error is ALWAYS the first argument
return callback(err, null);
}
callback(null, data);
});
}
// Pattern 2: Promises with catch
async function readFilePromise(path) {
const fs = require('fs').promises;
try {
const data = await fs.readFile(path, 'utf8');
return data;
} catch (err) {
// Centralized error handling
console.error(`File read error: ${err.message}`);
throw err; // Re-throw for propagation
}
}
// Pattern 3: Global handling of unhandled rejections
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled rejection:', reason);
// In production: log and graceful shutdown
});
// Pattern 4: Handling uncaught exceptions
process.on('uncaughtException', (err) => {
console.error('Uncaught exception:', err);
// CRITICAL: always terminate the process after
process.exit(1);
});W środowisku produkcyjnym każdy Promise musi posiadać .catch() lub znajdować się wewnątrz bloku try/catch. Globalne handlery stanowią siatkę bezpieczeństwa, a nie główne rozwiązanie.
Programowanie asynchroniczne i współbieżność
Pytanie 4: Wyjaśnij różnicę między równoległością a współbieżnością w Node.js
Node.js jest współbieżny, ale domyślnie nie równoległy. To rozróżnienie jest fundamentalne dla zrozumienia wydajności.
// CONCURRENCY: multiple tasks progress by alternating (single-thread)
async function concurrentTasks() {
console.time('concurrent');
// These calls are concurrent, not parallel
const results = await Promise.all([
fetch('https://api.example.com/users'), // Non-blocking I/O
fetch('https://api.example.com/products'), // Non-blocking I/O
fetch('https://api.example.com/orders'), // Non-blocking I/O
]);
console.timeEnd('concurrent'); // ~time of the longest request
return results;
}
// PARALLELISM: with Worker Threads for CPU-bound tasks
const { Worker, isMainThread, parentPort } = require('worker_threads');
if (isMainThread) {
// Main thread delegates CPU-intensive work
async function parallelComputation() {
console.time('parallel');
const workers = [
createWorker({ start: 0, end: 1000000 }),
createWorker({ start: 1000000, end: 2000000 }),
createWorker({ start: 2000000, end: 3000000 }),
];
const results = await Promise.all(workers);
console.timeEnd('parallel');
return results.reduce((a, b) => a + b, 0);
}
function createWorker(data) {
return new Promise((resolve, reject) => {
const worker = new Worker(__filename, { workerData: data });
worker.on('message', resolve);
worker.on('error', reject);
});
}
} else {
// Code executed in the Worker Thread
const { workerData } = require('worker_threads');
let sum = 0;
for (let i = workerData.start; i < workerData.end; i++) {
sum += Math.sqrt(i); // CPU-intensive calculation
}
parentPort.postMessage(sum);
}Dla operacji I/O (sieć, pliki) natywna współbieżność jest wystarczająca. Dla zadań obciążających procesor (intensywne obliczenia, kryptografia) Worker Threads umożliwiają prawdziwą równoległość.
Pytanie 5: Jak działa moduł Cluster?
Moduł Cluster pozwala tworzyć wiele procesów Node.js współdzielących ten sam port, wykorzystując w ten sposób wszystkie dostępne rdzenie procesora.
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;
if (cluster.isPrimary) {
console.log(`Primary ${process.pid} is running`);
console.log(`Forking ${numCPUs} workers...`);
// Fork one worker per CPU core
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
// Handle crashing workers
cluster.on('exit', (worker, code, signal) => {
console.log(`Worker ${worker.process.pid} died (${signal || code})`);
console.log('Starting a new worker...');
cluster.fork(); // Automatic restart
});
// Inter-process communication
cluster.on('message', (worker, message) => {
console.log(`Message from worker ${worker.id}:`, message);
});
} else {
// Workers share the TCP port
http.createServer((req, res) => {
res.writeHead(200);
res.end(`Handled by worker ${process.pid}\n`);
// Send stats to primary
process.send({ type: 'request', pid: process.pid });
}).listen(8000);
console.log(`Worker ${process.pid} started`);
}Równowaga obciążenia jest obsługiwana automatycznie przez system operacyjny (round-robin na Linux/macOS). W produkcji PM2 upraszcza zarządzanie dzięki wbudowanemu trybowi klastra.
Gotowy na rozmowy o Node.js / NestJS?
Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.
Strumienie i bufory
Pytanie 6: Kiedy należy używać strumieni zamiast klasycznych metod?
Strumienie umożliwiają przetwarzanie danych we fragmentach zamiast ładowania wszystkiego do pamięci. Są niezbędne przy dużych plikach i scenariuszach strumieniowania.
const fs = require('fs');
// BAD: loads entire file into memory
async function readEntireFile(path) {
const data = await fs.promises.readFile(path); // Blocks if file > RAM
return processData(data);
}
// GOOD: chunk-based processing with Stream
function readWithStream(path) {
return new Promise((resolve, reject) => {
const chunks = [];
const readStream = fs.createReadStream(path, {
highWaterMark: 64 * 1024, // 64KB per chunk
});
readStream.on('data', (chunk) => {
// Progressive processing, constant memory
chunks.push(processChunk(chunk));
});
readStream.on('end', () => resolve(chunks));
readStream.on('error', reject);
});
}
// BEST: pipeline for chaining transformations
const { pipeline } = require('stream/promises');
const zlib = require('zlib');
async function compressFile(input, output) {
await pipeline(
fs.createReadStream(input), // Source
zlib.createGzip(), // Transform
fs.createWriteStream(output) // Destination
);
// Automatic error handling and backpressure management
}Strumienie należy stosować zawsze, gdy rozmiar danych może przekroczyć kilka MB lub gdy potrzebne jest przetwarzanie w czasie rzeczywistym (uploady, logi, dane sieciowe).
Pytanie 7: Wyjaśnij pojęcie backpressure
Backpressure występuje, gdy producent danych jest szybszy niż konsument. Bez odpowiedniego zarządzania pamięć szybko się wyczerpuje.
const fs = require('fs');
// Problem: no backpressure handling
function badCopy(src, dest) {
const readable = fs.createReadStream(src);
const writable = fs.createWriteStream(dest);
readable.on('data', (chunk) => {
// If write() returns false, the internal buffer is full
// But here reading continues anyway → memory leak
writable.write(chunk);
});
}
// Solution: respect the writable signal
function goodCopy(src, dest) {
const readable = fs.createReadStream(src);
const writable = fs.createWriteStream(dest);
readable.on('data', (chunk) => {
const canContinue = writable.write(chunk);
if (!canContinue) {
// Pause reading until buffer drains
readable.pause();
}
});
writable.on('drain', () => {
// Buffer drained, resume reading
readable.resume();
});
readable.on('end', () => writable.end());
}
// BEST: pipe() handles everything automatically
function bestCopy(src, dest) {
const readable = fs.createReadStream(src);
const writable = fs.createWriteStream(dest);
// pipe() handles backpressure natively
readable.pipe(writable);
}Metoda pipe() lub pipeline() obsługuje backpressure automatycznie. W przypadku złożonych scenariuszy należy zaimplementować logikę pause/resume ręcznie.
Wydajność i optymalizacja
Pytanie 8: Jak zidentyfikować i naprawić wycieki pamięci?
Wycieki pamięci są częstym problemem w Node.js. Umiejętność ich wykrywania i naprawiania jest niezbędna w środowisku produkcyjnym.
// Leak 1: closures that retain references
function createLeakyHandler() {
const hugeData = Buffer.alloc(100 * 1024 * 1024); // 100MB
return function handler(req, res) {
// hugeData remains in memory as long as handler exists
res.end('Hello');
};
}
// Fix: limit the scope
function createSafeHandler() {
return function handler(req, res) {
// Data created and released on each request
const data = fetchData();
res.end(data);
};
}
// Leak 2: event listeners not cleaned up
class LeakyClass {
constructor() {
// Added on each instantiation, never removed
process.on('message', this.handleMessage);
}
handleMessage(msg) { /* ... */ }
}
// Fix: explicit cleanup
class SafeClass {
constructor() {
this.boundHandler = this.handleMessage.bind(this);
process.on('message', this.boundHandler);
}
handleMessage(msg) { /* ... */ }
destroy() {
// Mandatory cleanup
process.removeListener('message', this.boundHandler);
}
}
// Diagnostics with native tools
function diagnoseMemory() {
const used = process.memoryUsage();
console.log({
heapUsed: `${Math.round(used.heapUsed / 1024 / 1024)}MB`,
heapTotal: `${Math.round(used.heapTotal / 1024 / 1024)}MB`,
external: `${Math.round(used.external / 1024 / 1024)}MB`,
rss: `${Math.round(used.rss / 1024 / 1024)}MB`,
});
}
// Enable manual garbage collector for testing
// node --expose-gc app.js
if (global.gc) {
global.gc();
diagnoseMemory();
}W produkcji warto korzystać ze zrzutów sterty z Chrome DevTools (node --inspect), wbudowanego profilera Node.js lub rozwiązań APM, takich jak DataDog czy New Relic. W przypadku aplikacji NestJS przydatne wzorce zapobiegające problemom z pamięcią opisano w artykule NestJS Guards, Interceptors and Modular Architecture.
Pytanie 9: Jak zoptymalizować wydajność API Node.js?
To pytanie weryfikuje znajomość technik optymalizacji na wielu poziomach.
// 1. CACHING: reduce expensive calls
const NodeCache = require('node-cache');
const cache = new NodeCache({ stdTTL: 300 }); // 5-minute TTL
async function getCachedUser(id) {
const cacheKey = `user:${id}`;
let user = cache.get(cacheKey);
if (!user) {
user = await db.users.findById(id);
cache.set(cacheKey, user);
}
return user;
}
// 2. CONNECTION POOLING: reuse DB connections
const { Pool } = require('pg');
const pool = new Pool({
max: 20, // Max simultaneous connections
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
});
// 3. COMPRESSION: reduce response size
const compression = require('compression');
app.use(compression({
filter: (req, res) => {
// Only compress if > 1KB
return compression.filter(req, res);
},
threshold: 1024,
}));
// 4. BATCHING: group operations
async function batchInsert(items) {
const BATCH_SIZE = 1000;
for (let i = 0; i < items.length; i += BATCH_SIZE) {
const batch = items.slice(i, i + BATCH_SIZE);
await db.items.insertMany(batch);
}
}
// 5. LAZY LOADING: load on demand
async function getUserWithPosts(userId, includePosts = false) {
const user = await db.users.findById(userId);
if (includePosts) {
user.posts = await db.posts.findByUserId(userId);
}
return user;
}Optymalizacje powinny być oparte na profilowaniu. Przed optymalizacją warto zmierzyć rzeczywiste wąskie gardła.
80% problemów wydajnościowych pochodzi z 20% kodu. Profilowanie pozwala zidentyfikować te krytyczne obszary zanim zacznie się ślepo optymalizować.
Bezpieczeństwo
Pytanie 10: Jak zabezpieczyć API Node.js przed typowymi atakami?
Bezpieczeństwo to stały temat na rozmowach kwalifikacyjnych. Oczekuje się wykazania znajomości podatności OWASP.
const express = require('express');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const mongoSanitize = require('express-mongo-sanitize');
const xss = require('xss-clean');
const app = express();
// 1. SECURITY HEADERS with Helmet
app.use(helmet());
// 2. RATE LIMITING against brute-force attacks
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // 100 requests per IP
message: 'Too many requests, please try again later',
standardHeaders: true,
legacyHeaders: false,
});
app.use('/api/', limiter);
// 3. SANITIZATION against NoSQL injections
app.use(mongoSanitize());
// 4. XSS PROTECTION
app.use(xss());
// 5. STRICT INPUT VALIDATION
const { body, validationResult } = require('express-validator');
app.post('/api/users',
[
body('email').isEmail().normalizeEmail(),
body('password').isLength({ min: 8 }).escape(),
body('name').trim().escape(),
],
(req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
// Continue processing
}
);
// 6. SQL INJECTION PROTECTION (with parameters)
async function safeQuery(userId) {
// Parameterized query
const result = await pool.query(
'SELECT * FROM users WHERE id = $1',
[userId]
);
return result.rows;
}
// NEVER string concatenation
async function unsafeQuery(userId) {
// Vulnerable to SQL injection
const result = await pool.query(
`SELECT * FROM users WHERE id = ${userId}`
);
}W produkcji należy również dodać: restrykcyjne CORS, obowiązkowe HTTPS, logowanie bezpieczeństwa, rotację sekretów i regularne audyty zależności (npm audit). Node.js 24 wprowadził stabilny Permission Model (flaga --permission) do ograniczania dostępu do systemu plików, sieci i workerów w czasie wykonywania.
Architektura i wzorce projektowe
Pytanie 11: Wyjaśnij wzorzec Repository w Node.js
Wzorzec Repository abstrahuje dostęp do danych i ułatwia testowanie oraz utrzymanie kodu.
// Abstract interface (for TypeScript, or documentation)
class UserRepository {
async findById(id) { throw new Error('Not implemented'); }
async findByEmail(email) { throw new Error('Not implemented'); }
async create(userData) { throw new Error('Not implemented'); }
async update(id, userData) { throw new Error('Not implemented'); }
async delete(id) { throw new Error('Not implemented'); }
}
// Concrete implementation with Prisma
class PrismaUserRepository extends UserRepository {
constructor(prisma) {
super();
this.prisma = prisma;
}
async findById(id) {
return this.prisma.user.findUnique({ where: { id } });
}
async findByEmail(email) {
return this.prisma.user.findUnique({ where: { email } });
}
async create(userData) {
return this.prisma.user.create({ data: userData });
}
async update(id, userData) {
return this.prisma.user.update({
where: { id },
data: userData,
});
}
async delete(id) {
return this.prisma.user.delete({ where: { id } });
}
}
// Implementation for testing
class InMemoryUserRepository extends UserRepository {
constructor() {
super();
this.users = new Map();
this.idCounter = 1;
}
async findById(id) {
return this.users.get(id) || null;
}
async create(userData) {
const user = { id: this.idCounter++, ...userData };
this.users.set(user.id, user);
return user;
}
// ... other methods
}
// Service using the repository (dependency injection)
class UserService {
constructor(userRepository) {
this.userRepository = userRepository;
}
async getUser(id) {
const user = await this.userRepository.findById(id);
if (!user) throw new Error('User not found');
return user;
}
}Ten wzorzec pozwala na zmianę implementacji warstwy trwałości bez modyfikacji logiki biznesowej. Integracja TypeORM z tym wzorcem została opisana w artykule NestJS and TypeORM: Migrations, Relations and Best Practices.
Pytanie 12: Jak zaimplementować system kolejek zadań?
Kolejki pozwalają na odraczanie ciężkich zadań i zapewniają ich niezawodne wykonanie. BullMQ jest zalecaną biblioteką dla nowych projektów (Bull jest w trybie utrzymania od 2026 roku).
const { Queue, Worker } = require('bullmq');
// Create queue with Redis as backend
const emailQueue = new Queue('email', {
connection: {
host: 'localhost',
port: 6379,
},
defaultJobOptions: {
attempts: 3, // Number of attempts
backoff: {
type: 'exponential',
delay: 2000, // Initial delay between attempts
},
removeOnComplete: 100, // Keep last 100 completed jobs
},
});
// Producer: add jobs to the queue
async function sendWelcomeEmail(userId, email) {
await emailQueue.add('welcome', {
userId,
email,
template: 'welcome',
}, {
priority: 1, // High priority
delay: 5000, // 5-second delay
});
}
// Consumer: process jobs with a Worker (BullMQ separates Queue and Worker)
const emailWorker = new Worker('email', async (job) => {
const { userId, email, template } = job.data;
// Update progress
await job.updateProgress(10);
const html = await renderTemplate(template, { userId });
await job.updateProgress(50);
await sendEmail(email, 'Welcome!', html);
await job.updateProgress(100);
return { sent: true, email };
}, {
connection: {
host: 'localhost',
port: 6379,
},
});
// Event handling
emailWorker.on('completed', (job, result) => {
console.log(`Job ${job.id} completed:`, result);
});
emailWorker.on('failed', (job, err) => {
console.error(`Job ${job.id} failed:`, err.message);
});
// Recurring jobs (cron)
emailQueue.add('newsletter', { type: 'weekly' }, {
repeat: {
pattern: '0 9 * * MON', // Every Monday at 9am
},
});BullMQ z Redis to zalecane rozwiązanie dla nowych projektów w 2026 roku. Oferuje projektowanie zorientowane na TypeScript, lepszą wydajność (o 20 do 30% szybsze niż Bull) oraz pełne wsparcie dla Redis Cluster. Dla prostszych potrzeb lub kolejek opartych na PostgreSQL, pg-boss jest lekką alternatywą. Wzorce cache'owania z Redis opisano w artykule NestJS and Redis: Caching, Sessions and Interview Questions.
Pytania zaawansowane
Pytanie 13: Jak działa natywny moduł N-API?
N-API pozwala tworzyć natywne moduły w C/C++ ze stabilnym API niezależnym od wersji Node.js.
// Native module for CPU-intensive calculations
#include <napi.h>
// Synchronous function exposed to JavaScript
Napi::Number Fibonacci(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
// Argument validation
if (info.Length() < 1 || !info[0].IsNumber()) {
Napi::TypeError::New(env, "Number expected")
.ThrowAsJavaScriptException();
return Napi::Number::New(env, 0);
}
int n = info[0].As<Napi::Number>().Int32Value();
// Iterative Fibonacci calculation
long long a = 0, b = 1;
for (int i = 0; i < n; i++) {
long long temp = a + b;
a = b;
b = temp;
}
return Napi::Number::New(env, static_cast<double>(a));
}
// Module initialization
Napi::Object Init(Napi::Env env, Napi::Object exports) {
exports.Set(
Napi::String::New(env, "fibonacci"),
Napi::Function::New(env, Fibonacci)
);
return exports;
}
NODE_API_MODULE(native_module, Init)// Usage from JavaScript
const native = require('./build/Release/native_module');
// 10x faster than JavaScript equivalent
const result = native.fibonacci(50);Moduły natywne przydają się przy intensywnych obliczeniach, integracji istniejących bibliotek C/C++ lub dostępie do API systemowych.
Pytanie 14: Wyjaśnij działanie Garbage Collectora V8
Zrozumienie GC pomaga pisać kod minimalizujący pauzy i zużycie pamięci.
// V8 GC uses two spaces: Young and Old Generation
// 1. Young Generation: short-lived objects
function shortLivedObjects() {
for (let i = 0; i < 1000; i++) {
const temp = { data: i }; // Allocated then collected quickly
}
// Minor GC (Scavenge) very fast
}
// 2. Old Generation: objects that survive multiple GCs
const cache = new Map(); // Survives, promoted to Old Generation
// Problematic pattern: many promoted objects
function createManyLongLived() {
const objects = [];
for (let i = 0; i < 100000; i++) {
objects.push({ id: i, data: new Array(100).fill(0) });
}
return objects; // All promoted to Old Gen = slow major GC
}
// Optimized pattern: object reuse
class ObjectPool {
constructor(factory, size = 100) {
this.pool = Array.from({ length: size }, factory);
this.available = [...this.pool];
}
acquire() {
return this.available.pop() || this.pool[0];
}
release(obj) {
// Reset and return to pool
Object.keys(obj).forEach(k => obj[k] = null);
this.available.push(obj);
}
}
// GC monitoring
const v8 = require('v8');
function getHeapStats() {
const stats = v8.getHeapStatistics();
return {
totalHeap: `${Math.round(stats.total_heap_size / 1024 / 1024)}MB`,
usedHeap: `${Math.round(stats.used_heap_size / 1024 / 1024)}MB`,
heapLimit: `${Math.round(stats.heap_size_limit / 1024 / 1024)}MB`,
};
}Flaga --max-old-space-size pozwala zwiększyć limit Old Generation dla aplikacji wymagających dużej ilości pamięci.
Pytanie 15: Jak zaimplementować graceful shutdown?
Graceful shutdown pozwala na zakończenie przetwarzania bieżących żądań i prawidłowe zamknięcie połączeń przed zatrzymaniem serwera.
const http = require('http');
const server = http.createServer((req, res) => {
// Simulate a long request
setTimeout(() => {
res.writeHead(200);
res.end('Done');
}, 2000);
});
// Tracking active connections
let connections = new Set();
server.on('connection', (conn) => {
connections.add(conn);
conn.on('close', () => connections.delete(conn));
});
// Graceful shutdown function
async function shutdown(signal) {
console.log(`${signal} received, starting graceful shutdown...`);
// 1. Stop accepting new connections
server.close(() => {
console.log('HTTP server closed');
});
// 2. Close idle connections
for (const conn of connections) {
conn.end();
}
// 3. Close DB connections, queues, etc.
await Promise.all([
database.disconnect(),
redisClient.quit(),
messageQueue.close(),
]);
// 4. Safety timeout
setTimeout(() => {
console.error('Forced shutdown after timeout');
process.exit(1);
}, 30000);
console.log('Graceful shutdown completed');
process.exit(0);
}
// Listen for termination signals
process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('SIGINT', () => shutdown('SIGINT'));
// Start server
server.listen(3000, () => {
console.log('Server running on port 3000');
});W produkcji z kontenerami (Docker, Kubernetes) graceful shutdown ma kluczowe znaczenie dla wdrożeń bez przestojów.
Gotowy na rozmowy o Node.js / NestJS?
Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.
Pytania behawioralne
Pytanie 16: Opisz problem wydajnościowy, który udało się rozwiązać
To pytanie weryfikuje praktyczne doświadczenie. Odpowiedź powinna być ustrukturyzowana zgodnie z formatem STAR (Sytuacja, Zadanie, Działanie, Rezultat).
Przykład ustrukturyzowanej odpowiedzi:
Situation: A reporting API was timing out on requests
exceeding 100,000 records.
Task: Reduce response time from 45s to under 5s.
Action:
1. Profiling with Chrome DevTools (node --inspect) identified JSON serialization as bottleneck
2. Implemented streaming with Transform streams
3. Database-side pagination
4. Added Redis caching for frequent queries
Result: Response time reduced to 2s, memory usage decreased by 10x.Pytanie 17: Jak zarządzać zależnościami i ich aktualizacjami?
{
"dependencies": {
// Exact versions for production
"express": "4.18.2",
// Caret for compatible minor updates
"lodash": "^4.17.21"
// Avoid latest or *
// "some-lib": "*"
},
"devDependencies": {
// Quality tools
"npm-check-updates": "^16.0.0"
},
"scripts": {
// Vulnerability check
"audit": "npm audit --audit-level=moderate",
// Interactive update
"update:check": "ncu",
"update:apply": "ncu -u && npm install"
},
"engines": {
// Specify required Node.js version
"node": ">=20.0.0"
}
}Warto wspomnieć o stosowaniu package-lock.json, automatyzacji za pomocą Dependabot lub Renovate oraz o testach regresyjnych przed każdą dużą aktualizacją.
Źródła
- Node.js 24 Release Notes - V8 v13.6, URLPattern, stabilizacja Permission Model
- process.nextTick() API Documentation - status Legacy i rekomendacja queueMicrotask()
- BullMQ Documentation - nowoczesna kolejka zadań dla Node.js
- Node.js Permission Model - ograniczenia bezpieczeństwa w czasie wykonywania
Lista kontrolna przygotowania do rozmów z Node.js Backend
Rozmowy kwalifikacyjne z Node.js backend weryfikują zarówno teoretyczne zrozumienie wewnętrznych mechanizmów, jak i zdolność do rozwiązywania praktycznych problemów produkcyjnych. Opanowanie Event Loop, wzorców asynchronicznych i technik optymalizacji stanowi fundament oczekiwany od kandydatów na stanowiska seniorskich programistów backend.
Lista kontrolna przygotowania
- Zrozumienie działania Event Loop i jego faz
- Opanowanie różnic między callbackami, Promise i async/await
- Znajomość wzorców obsługi błędów asynchronicznych
- Wiedza, kiedy stosować strumienie zamiast klasycznych metod
- Identyfikacja i naprawa wycieków pamięci
- Stosowanie najlepszych praktyk bezpieczeństwa OWASP
- Implementacja klasteryzacji i graceful shutdown
- Korzystanie z narzędzi profilujących (Chrome DevTools, node --inspect)
Zacznij ćwiczyć!
Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.
Przygotowanie techniczne warto uzupełnić projektami praktycznymi. Budowanie produkcyjnego API, udział w projektach open source związanych z Node.js lub rozwiązywanie zadań na platformach takich jak LeetCode pomaga ugruntować tę wiedzę.
Znajdziesz błąd w Node.js / NestJS?
Prawdziwy fragment kodu, ukryty błąd, jedna próba dziennie. Bez konta, żeby spróbować.

Autor:
Anthony Fillion-MailletZałożyciel SharpSkill
Programista fullstack od ponad 10 lat. Prowadzi SharpSkill i odpowiada za wszystko, co się tu ukazuje.
Zaktualizowano 7 października 2026
Tagi
Udostępnij
Powiązane artykuły

NestJS i MongoDB w 2026: Mongoose, Agregacje i Pytania Rekrutacyjne
Opanuj NestJS z MongoDB i Mongoose 9. Poznaj wzorce projektowania schematów, potoki agregacji i przygotuj się do rozmów kwalifikacyjnych z praktycznymi przykładami.

NestJS + Prisma: nowoczesny stack backendowy dla Node.js
Kompletny przewodnik po budowaniu nowoczesnego API backendowego z NestJS i Prismą. Konfiguracja, modele, serwisy, transakcje i dobre praktyki.

NestJS: Budowanie kompletnego REST API
Kompletny przewodnik po budowaniu profesjonalnego REST API z NestJS. Kontrolery, serwisy, moduły, walidacja z class-validator i obsługa błędów z praktycznymi przykładami.