Domande colloquio Node.js Backend: Guida completa 2026
Le 25 domande più frequenti nei colloqui Node.js backend. Event loop, async/await, stream, clustering e performance spiegate con risposte dettagliate.

I colloqui tecnici Node.js backend valutano la comprensione degli internals del runtime, la padronanza dei pattern asincroni e la capacità di progettare applicazioni performanti. Questa guida copre le domande più frequenti, dai fondamentali ai concetti avanzati di produzione.
I selezionatori apprezzano risposte che combinano teoria ed esempi pratici. Per ogni domanda, illustrare con codice o un caso d'uso concreto dimostra esperienza sul campo.
Fondamentali di Node.js
Domanda 1: Cos'è l'Event Loop e come funziona?
L'Event Loop è il meccanismo centrale che consente a Node.js di gestire operazioni asincrone in modo non bloccante, nonostante l'esecuzione avvenga su un singolo thread. Esso orchestra l'esecuzione di codice JavaScript, callback ed eventi.
// 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, 6L'Event Loop segue un ordine preciso nell'elaborazione delle code: prima il codice sincrono, poi nextTick, microtask (Promise), timer, callback I/O, setImmediate e infine i callback di chiusura. Per un approfondimento sulle implicazioni per la performance, vedere Node.js Performance: Event Loop, Clustering e Ottimizzazione.
Domanda 2: Qual è la differenza tra process.nextTick() e setImmediate()?
Questa domanda valuta la comprensione dettagliata delle priorità di esecuzione nell'Event Loop. A partire da Node.js v22.7.0, process.nextTick() ha ottenuto lo status Legacy, con queueMicrotask() come alternativa raccomandata per il nuovo codice.
// 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() viene elaborato immediatamente dopo l'operazione corrente, prima che l'Event Loop prosegua. Un uso eccessivo può bloccare l'Event Loop. setImmediate() è più prevedibile e consigliato per posticipare l'esecuzione. Per il nuovo codice, preferire queueMicrotask() poiché segue lo standard della piattaforma Web e si comporta in modo coerente tra i diversi runtime.
Chiamate ricorsive a process.nextTick() possono affamare l'Event Loop e impedire l'elaborazione dell'I/O. Questo è il motivo per cui Node.js l'ha marcato come Legacy. Per operazioni non critiche utilizzare queueMicrotask() o setImmediate().
Domanda 3: Come gestisce Node.js gli errori nel codice asincrono?
La gestione degli errori asincroni differisce fondamentalmente dal codice sincrono. Senza una gestione corretta, un errore può mandare in crash l'intera applicazione.
// 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);
});In produzione, ogni Promise deve avere un .catch() o trovarsi all'interno di un blocco try/catch. I gestori globali fungono da rete di sicurezza, non da soluzione primaria.
Programmazione asincrona e concorrenza
Domanda 4: Spiegare la differenza tra parallelismo e concorrenza in Node.js
Node.js è concorrente ma non parallelo per impostazione predefinita. Questa distinzione è fondamentale per comprendere le prestazioni.
// 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);
}Per operazioni I/O-bound (rete, file), la concorrenza nativa è sufficiente. Per task CPU-bound (calcoli intensivi, crittografia), i Worker Threads consentono il vero parallelismo.
Domanda 5: Come funziona il modulo Cluster?
Il modulo Cluster consente di creare più processi Node.js che condividono la stessa porta, sfruttando così tutti i core CPU disponibili.
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}
`);
// Send stats to primary
process.send({ type: 'request', pid: process.pid });
}).listen(8000);
console.log(`Worker ${process.pid} started`);
}Il bilanciamento del carico viene gestito automaticamente dal sistema operativo (round-robin su Linux/macOS). In produzione, PM2 semplifica questa gestione con la sua modalità cluster integrata.
Pronto a superare i tuoi colloqui su Node.js / NestJS?
Pratica con i nostri simulatori interattivi, flashcards e test tecnici.
Stream e Buffer
Domanda 6: Quando utilizzare gli Stream invece dei metodi classici?
Gli Stream consentono di elaborare i dati a blocchi invece di caricare tutto in memoria. Sono essenziali per file di grandi dimensioni e scenari di streaming.
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
}Gli Stream vanno utilizzati ogni volta che la dimensione dei dati può superare alcuni MB, oppure per l'elaborazione in tempo reale (upload, log, dati di rete).
Domanda 7: Spiegare il concetto di backpressure
La backpressure si verifica quando il produttore di dati è più veloce del consumatore. Senza gestione, il consumo di memoria esplode.
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);
}Il metodo pipe() o pipeline() gestisce la backpressure automaticamente. Per casi complessi, la logica pause/resume può essere implementata manualmente.
Performance e ottimizzazione
Domanda 8: Come identificare e risolvere i memory leak?
I memory leak sono comuni in Node.js. Sapere come individuarli e risolverli è essenziale in produzione.
// 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();
}In produzione, vanno utilizzati heap snapshot da Chrome DevTools (node --inspect), il profiler integrato di Node.js o soluzioni APM come DataDog o New Relic. Per le applicazioni NestJS, vedere anche NestJS Guards, Interceptors e Architettura Modulare per pattern che aiutano a prevenire problemi di memoria.
Domanda 9: Come ottimizzare le prestazioni di un'API Node.js?
Questa domanda valuta la conoscenza delle tecniche di ottimizzazione a più livelli.
// 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;
}Le ottimizzazioni devono essere guidate dal profiling. Misurare prima di ottimizzare per identificare i veri colli di bottiglia.
L'80% dei problemi di performance deriva dal 20% del codice. Il profiling aiuta a identificare queste aree critiche prima di ottimizzare alla cieca.
Sicurezza
Domanda 10: Come proteggere un'API Node.js dagli attacchi più comuni?
La sicurezza è un argomento ricorrente nei colloqui. Dimostrare la conoscenza delle vulnerabilità OWASP è atteso.
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}`
);
}In produzione, vanno inoltre aggiunti: CORS restrittivo, HTTPS obbligatorio, logging di sicurezza, rotazione dei segreti e audit regolari delle dipendenze (npm audit). Node.js 24 ha introdotto il Permission Model stabile (flag --permission) per limitare l'accesso a filesystem, rete e worker a runtime.
Architettura e Design Pattern
Domanda 11: Spiegare il pattern Repository in Node.js
Il pattern Repository astrae l'accesso ai dati e facilita il testing e la manutenibilità.
// 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;
}
}Questo pattern consente di cambiare l'implementazione della persistenza senza modificare la logica di business. Per l'integrazione di TypeORM con questo pattern, vedere NestJS e TypeORM: Migrations, Relations e Best Practices.
Domanda 12: Come implementare un sistema di code di lavoro (job queue)?
Le code consentono di posticipare task pesanti e garantirne l'esecuzione affidabile. BullMQ è la libreria raccomandata per i nuovi progetti (Bull è in modalità manutenzione dal 2026).
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 con Redis è la scelta raccomandata per i nuovi progetti nel 2026. Offre design TypeScript-first, migliori performance (dal 20 al 30% più veloce di Bull) e supporto completo per Redis Cluster. Per esigenze più semplici o code basate su PostgreSQL, pg-boss è un'alternativa leggera. Per pattern di caching con Redis, vedere NestJS e Redis: Caching, Sessions e Domande da Colloquio.
Domande avanzate
Domanda 13: Come funziona il modulo nativo N-API?
N-API consente di creare moduli nativi in C/C++ con un'API stabile tra le versioni di 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);I moduli nativi sono utili per calcoli intensivi, integrazione di librerie C/C++ esistenti o accesso ad API di sistema.
Domanda 14: Spiegare il Garbage Collector di V8
Comprendere il GC aiuta a scrivere codice che minimizza le pause e il consumo di memoria.
// 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`,
};
}Il flag --max-old-space-size consente di aumentare il limite della Old Generation per applicazioni ad alto consumo di memoria.
Domanda 15: Come implementare un graceful shutdown?
Il graceful shutdown consente di completare le richieste in corso e chiudere correttamente le connessioni prima di arrestare il server.
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');
});In produzione con container (Docker, Kubernetes), il graceful shutdown è fondamentale per deployment a zero downtime.
Pronto a superare i tuoi colloqui su Node.js / NestJS?
Pratica con i nostri simulatori interattivi, flashcards e test tecnici.
Domande comportamentali
Domanda 16: Descrivere un problema di performance risolto
Questa domanda valuta l'esperienza pratica. La risposta va strutturata con il metodo STAR (Situation, Task, Action, Result).
Esempio di risposta strutturata:
Situation: Un'API di reportistica andava in timeout
per richieste con oltre 100.000 record.
Task: Ridurre il tempo di risposta da 45s a meno di 5s.
Action:
1. Profiling con Chrome DevTools (node --inspect) ha identificato la serializzazione JSON come collo di bottiglia
2. Streaming implementato con Transform stream
3. Paginazione lato database
4. Caching Redis per le query frequenti
Result: Tempo di risposta ridotto a 2s, consumo di memoria diminuito di 10 volte.Domanda 17: Come si gestiscono le dipendenze e i loro aggiornamenti?
{
"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"
}
}Fondamentale è l'utilizzo di package-lock.json, Dependabot o Renovate per l'automazione e test di regressione prima di ogni aggiornamento maggiore.
Fonti
- Node.js 24 Release Notes - V8 v13.6, URLPattern, stabilizzazione del Permission Model
- Documentazione API process.nextTick() - Status Legacy e raccomandazione queueMicrotask()
- Documentazione BullMQ - Job queue moderna per Node.js
- Node.js Permission Model - Restrizioni di sicurezza a runtime
Checklist per i colloqui Node.js Backend
I colloqui Node.js backend valutano sia la comprensione teorica dei meccanismi interni sia la capacità di risolvere problemi pratici di produzione. La padronanza dell'Event Loop, dei pattern asincroni e delle tecniche di ottimizzazione costituisce la base attesa per posizioni di sviluppatore backend senior.
Checklist di preparazione
- Comprendere il funzionamento dell'Event Loop e le sue fasi
- Padroneggiare le differenze tra callback, Promise e async/await
- Conoscere i pattern di gestione degli errori asincroni
- Sapere quando usare gli Stream rispetto ai metodi classici
- Identificare e risolvere i memory leak
- Applicare le best practice di sicurezza OWASP
- Implementare clustering e graceful shutdown
- Utilizzare strumenti di profiling (Chrome DevTools, node --inspect)
Inizia a praticare!
Metti alla prova le tue conoscenze con i nostri simulatori di colloquio e test tecnici.
La preparazione tecnica va integrata con progetti pratici. Costruire un'API di produzione, contribuire a progetti open source Node.js o risolvere sfide su piattaforme come LeetCode aiuta a consolidare queste conoscenze.
Sapresti trovare il bug in Node.js / NestJS?
Uno snippet reale, un bug nascosto, un tentativo al giorno. Senza account per provare.

Scritto da
Anthony Fillion-MailletFondatore di SharpSkill
Sviluppatore fullstack da oltre 10 anni. Guida SharpSkill e risponde di tutto ciò che vi viene pubblicato.
Aggiornato il 7 ottobre 2026
Tag
Condividi
Articoli correlati

NestJS + Prisma: lo stack backend moderno per Node.js
Guida completa per costruire un'API backend moderna con NestJS e Prisma. Configurazione, modelli, servizi, transazioni e best practice spiegate.

NestJS: Creare una REST API completa da zero
Guida passo dopo passo per costruire una REST API pronta per la produzione con NestJS, TypeScript, Prisma e class-validator. CRUD, validazione, gestione errori e interceptor.

Node.js 24 nel 2026: URLPattern, Permission Model e Domande per Colloqui Tecnici
Node.js 24 LTS introduce un Permission Model stabile, URLPattern globale, gestione esplicita delle risorse con using/await using e V8 13.6. Un approfondimento sulle funzionalità rilevanti per la produzione e i colloqui tecnici.