Domande Node.js per colloqui backend senior

Milad Bonakdar
Autore
Preparati ai colloqui backend senior Node.js con 30 domande pratiche su event loop, stream, sicurezza API, system design, scalabilità, performance, testing e responsabilità in produzione.
Domande Node.js per colloqui backend senior
Nei colloqui senior su Node.js raramente basta conoscere la sintassi. Aspettati domande su event loop, backpressure, confini delle API, sistemi distribuiti, osservabilità, sicurezza e compromessi di produzione. Le risposte migliori collegano gli internals di Node.js a decisioni prese su servizi reali.
Usa queste 30 domande per allenare spiegazioni concise e di livello senior. Per ogni argomento prepara un esempio tuo: una scelta di scalabilità, un problema di performance, un incidente di affidabilità o un tradeoff architetturale che sai spiegare senza esagerare.
Concetti Avanzati di Node.js (10 Domande)
1. Spiega in dettaglio l'Event Loop di Node.js. Quali sono le diverse fasi?
Risposta: L’event loop permette a Node.js di eseguire I/O non bloccante mentre JavaScript gira, per impostazione predefinita, su un solo thread principale. Il sistema operativo o libuv gestiscono il lavoro in attesa, poi i callback vengono messi in coda per essere eseguiti da JavaScript.
- Timers: Esegue callback di
setTimeout()esetInterval(). Nelle versioni moderne di Node.js, il lavoro nella fase poll può influenzare il timing. - Pending callbacks: Esegue alcuni callback I/O di basso livello rimandati all’iterazione successiva.
- Idle, prepare: Fasi interne di libuv.
- Poll: Recupera nuovi eventi I/O ed esegue i callback collegati. Se la coda è vuota, Node.js può attendere I/O o avanzare quando ci sono callback
setImmediate(). - Check: Esegue callback
setImmediate(). - Close callbacks: Esegue handler di chiusura come
socket.on('close', ...).
Una risposta senior cita anche le microtask queue: process.nextTick() viene eseguito prima delle promise, e entrambe prima che l’event loop continui. L’uso eccessivo può affamare l’I/O.
Rarità: Molto Comune Difficoltà: Difficile
2. Qual è la differenza tra process.nextTick() e setImmediate()?
Risposta:
process.nextTick(): Non fa parte dell'Event Loop. Si attiva immediatamente dopo il completamento dell'operazione corrente, ma prima che l'Event Loop continui. Ha una priorità più alta disetImmediate(). Un uso eccessivo può bloccare l'Event Loop (starvation).setImmediate(): Fa parte della fase Check dell'Event Loop. Viene eseguito dopo la fase Poll.
Rarità: Comune Difficoltà: Media
3. Come gestisce Node.js la concorrenza se è single-threaded?
Risposta: Node.js utilizza un modello I/O non bloccante e guidato dagli eventi.
- Thread Principale: Esegue codice JavaScript (motore V8).
- Libuv: Una libreria C che fornisce l'Event Loop e un pool di thread (predefinito 4 thread).
- Meccanismo: Quando viene avviata un'operazione asincrona (come I/O di file o richiesta di rete), Node.js la delega a Libuv. Libuv utilizza il suo pool di thread (per I/O di file, DNS) o meccanismi asincroni del kernel del sistema (per la rete). Quando l'operazione è completa, il callback viene inserito nella coda dell'Event Loop per essere eseguito dal thread principale.
Rarità: Comune Difficoltà: Media
4. Spiega gli Stream in Node.js e i loro tipi.
Risposta: Gli Stream sono oggetti che consentono di leggere dati da una sorgente o scrivere dati in una destinazione in blocchi continui. Sono efficienti in termini di memoria perché non è necessario caricare l'intero set di dati in memoria.
- Tipi:
- Readable: Per la lettura di dati (ad esempio,
fs.createReadStream). - Writable: Per la scrittura di dati (ad esempio,
fs.createWriteStream). - Duplex: Sia leggibili che scrivibili (ad esempio, socket TCP).
- Transform: Stream Duplex in cui l'output viene calcolato in base all'input (ad esempio,
zlib.createGzip).
- Readable: Per la lettura di dati (ad esempio,
Rarità: Comune Difficoltà: Media
5. Cos'è la Backpressure negli Stream e come la gestisci?
Risposta: La backpressure si verifica quando uno stream readable produce dati più velocemente di quanto la parte writable riesca a consumarli. Se la ignori, i buffer crescono, la garbage collection lavora di più e il processo può esaurire memoria.
- Usa
stream.pipeline()opipelinedanode:stream/promises: Collega gli stream, propaga gli errori e ripulisce le risorse correttamente. - Rispetta
.write(): Se restituiscefalse, aspettadrainprima di scrivere altro. - Tuning con misura:
highWaterMarkpuò aiutare in workload specifici, ma aumentarlo senza misure sposta solo pressione in memoria. - Per upload: Fai streaming diretto verso object storage o una pipeline di elaborazione invece di bufferizzare file interi in memoria.
Rarità: Media Difficoltà: Difficile
6. Come funziona il modulo cluster?
Risposta:
Poiché Node.js è single-threaded, viene eseguito su un singolo core della CPU. Il modulo cluster consente di creare processi figlio (worker) che condividono la stessa porta del server.
- Processo Master: Gestisce i worker.
- Processi Worker: Ognuno esegue un'istanza della tua applicazione.
- Vantaggio: Consente di utilizzare tutti i core della CPU disponibili, aumentando la velocità di trasmissione.
Rarità: Comune Difficoltà: Media
7. Worker Threads vs modulo Cluster: quando usare quale?
Risposta:
- Cluster: Crea processi separati. Ognuno ha il proprio spazio di memoria e istanza V8. Ideale per scalare server HTTP (I/O bound).
- Worker Threads: Crea thread all'interno di un singolo processo. Condividono la memoria (tramite
SharedArrayBuffer). Ideale per attività ad alta intensità di CPU (ad esempio, elaborazione di immagini, crittografia) per evitare di bloccare l'Event Loop principale.
Rarità: Media Difficoltà: Difficile
8. Come gestisci le eccezioni non gestite e i rejection di promise non gestiti?
Risposta:
- Eccezione Non Gestita: Ascolta
process.on('uncaughtException', cb). Di solito è meglio registrare l'errore e riavviare il processo (utilizzando un gestore di processi come PM2) perché lo stato dell'applicazione potrebbe essere corrotto. - Rejection Non Gestito: Ascolta
process.on('unhandledRejection', cb). - Best Practice: Usa sempre blocchi
try/catche.catch()sulle promise.
Rarità: Comune Difficoltà: Facile
9. Qual è il ruolo di package-lock.json?
Risposta: Descrive l'albero esatto che è stato generato, in modo che le installazioni successive siano in grado di generare alberi identici, indipendentemente dagli aggiornamenti delle dipendenze intermedie. Assicura che il tuo progetto funzioni esattamente allo stesso modo su ogni macchina (CI/CD, altri sviluppatori).
Rarità: Comune Difficoltà: Facile
10. Spiega il concetto di Middleware in Express.js.
Risposta:
Le funzioni middleware sono funzioni che hanno accesso all'oggetto richiesta (req), all'oggetto risposta (res) e alla successiva funzione middleware nel ciclo richiesta-risposta dell'applicazione (next).
- Compiti: Eseguire codice, modificare gli oggetti req/res, terminare il ciclo richiesta-risposta, chiamare il middleware successivo.
- Ordine: Vengono eseguite sequenzialmente nell'ordine in cui sono definite.
Rarità: Comune Difficoltà: Facile
Progettazione del Sistema e Architettura (10 Domande)
11. Come progetteresti un'applicazione di chat in tempo reale?
Risposta:
- Protocollo: WebSockets (utilizzando
socket.ioows) per la comunicazione full-duplex. - Backend: Node.js è ideale grazie alla sua natura guidata dagli eventi che gestisce molte connessioni simultanee.
- Scaling:
- Redis Pub/Sub: Se hai più istanze del server, un utente connesso al Server A deve inviare un messaggio a un utente sul Server B. Redis Pub/Sub funge da message broker per trasmettere messaggi tra i server.
- Database:
- Messaggi: NoSQL (MongoDB/Cassandra) per un'elevata velocità di scrittura.
- Utenti: Relazionale (PostgreSQL) o NoSQL.
Rarità: Molto Comune Difficoltà: Difficile
12. Microservizi in Node.js: schemi di comunicazione.
Risposta:
- Sincrono: HTTP/REST o gRPC. Buono per semplici richieste/risposte.
- Asincrono: Code di messaggi (RabbitMQ, Kafka, SQS). Buono per disaccoppiare i servizi e gestire i picchi di carico.
- Event-Driven: I servizi emettono eventi, altri ascoltano.
Rarità: Comune Difficoltà: Media
13. Come gestisci le transazioni distribuite (Saga Pattern)?
Risposta: Nei microservizi, una transazione potrebbe estendersi a più servizi. È difficile garantire ACID.
- Saga Pattern: Una sequenza di transazioni locali. Ogni transazione locale aggiorna il database e pubblica un evento o un messaggio per attivare la transazione locale successiva nella saga.
- Compensazione: Se una transazione locale fallisce, la saga esegue una serie di transazioni di compensazione che annullano le modifiche apportate dalle transazioni locali precedenti.
Rarità: Media Difficoltà: Difficile
14. Spiega il pattern Circuit Breaker.
Risposta: Impedisce a un'applicazione di tentare ripetutamente di eseguire un'operazione che probabilmente fallirà (ad esempio, chiamare un microservizio inattivo).
- Stati:
- Closed: Le richieste passano attraverso.
- Open: Le richieste falliscono immediatamente (fast fail) senza chiamare il servizio.
- Half-Open: Consente a un numero limitato di richieste di verificare se il servizio si è ripreso.
Rarità: Media Difficoltà: Media
15. Come proteggi un'API Node.js?
Risposta: Una risposta forte parte dal threat modeling, non da un elenco di pacchetti. Per un’API Node.js copri:
- Autenticazione e autorizzazione: Valida l’identità, applica permessi a livello oggetto e non fidarti degli ID utente o tenant inviati dal client.
- Validazione input: Valida tipo, formato, range, content type e dimensione della richiesta al confine con Zod, Joi o simili.
- Trasporto e header: Usa HTTPS, cookie sicuri quando servono, allowlist CORS e header tramite Helmet o controlli di piattaforma.
- Controlli anti-abuso: Rate limit, timeout, limiti di body size e reverse proxy contro client lenti o ad alto volume.
- Dipendenze e segreti: Blocca le dipendenze, monitora pacchetti vulnerabili, tieni i segreti fuori dal codice e ruota credenziali compromesse.
- Osservabilità: Logga errori rilevanti per la sicurezza senza esporre dati sensibili.
Rarità: Comune Difficoltà: Media
16. Cos'è Serverless e come si adatta a Node.js?
Risposta: Serverless (ad esempio, AWS Lambda) ti consente di eseguire codice senza provisioning o gestione di server.
- Adattamento Node.js: Node.js è eccellente per serverless grazie al suo rapido tempo di avvio (cold start) e alla sua natura leggera.
- Casi d'uso: Endpoint API, elaborazione di eventi (caricamenti S3), attività pianificate.
Rarità: Media Difficoltà: Media
17. Spiega GraphQL vs REST. Quando usare GraphQL?
Risposta:
- REST: Endpoint multipli, over-fetching o under-fetching dei dati.
- GraphQL: Endpoint singolo, il client chiede esattamente ciò di cui ha bisogno.
- Usa GraphQL: Quando hai requisiti di dati complessi, più client (web, mobile) che necessitano di forme di dati diverse o per ridurre i round trip di rete.
Rarità: Comune Difficoltà: Media
18. Come implementi la Caching in Node.js?
Risposta:
- In-Memory:
node-cache(buono per una singola istanza, ma i dati vengono persi al riavvio e non vengono condivisi). - Distribuito: Redis (standard industriale).
- Strategie: Cache-Aside, Write-Through.
- HTTP Caching: Usa gli header ETag, Cache-Control.
Rarità: Comune Difficoltà: Media
19. Database Connection Pooling.
Risposta: Aprire una nuova connessione al database per ogni richiesta è costoso.
- Pooling: Mantiene una cache di connessioni al database che possono essere riutilizzate.
- Node.js: Librerie come
pg(PostgreSQL) omongoosegestiscono automaticamente il pooling. È necessario configurare la dimensione del pool in base al carico di lavoro e ai limiti del DB.
Rarità: Media Difficoltà: Media
20. Come gestisci il caricamento di file in Node.js?
Risposta:
- Multipart/form-data: La codifica standard per il caricamento di file.
- Librerie:
multer(middleware per Express),formidable,busboy. - Storage: Non archiviare i file nel filesystem del server (statelessness). Carica su storage cloud come AWS S3. Trasmetti il file direttamente a S3 per evitare di caricarlo in memoria.
Rarità: Comune Difficoltà: Media
Prestazioni & Testing (10 Domande)
21. Come esegui il debug di una perdita di memoria in Node.js?
Risposta:
- Sintomi: Aumento dell'utilizzo della memoria nel tempo (RSS), eventuale crash.
- Strumenti:
- Node.js Inspector: Flag
--inspect, connettiti con Chrome DevTools. - Heap Snapshots: Scatta snapshot e confrontali per trovare oggetti che non vengono garbage collected.
process.memoryUsage(): Monitora programmaticamente.
- Node.js Inspector: Flag
Rarità: Comune Difficoltà: Difficile
22. Profilazione delle applicazioni Node.js.
Risposta: La profilazione aiuta a identificare i colli di bottiglia della CPU.
- Profiler Integrato:
node --prof app.js. Genera un file di log. Elaboralo connode --prof-process isolate-0x...log. - Clinic.js: Una suite di strumenti (
clinic doctor,clinic flame,clinic bubbleprof) per diagnosticare problemi di prestazioni.
Rarità: Media Difficoltà: Difficile
23. Spiega la regola "Non bloccare l'Event Loop".
Risposta: Poiché c'è solo un thread, se esegui un'operazione sincrona a lunga esecuzione (ad esempio, calcolo pesante, lettura sincrona di file, regex complessa), l'Event Loop si ferma. Nessun'altra richiesta può essere elaborata.
- Soluzione: Partiziona il calcolo (setImmediate), usa Worker Threads o delega a un microservizio.
Rarità: Molto Comune Difficoltà: Facile
24. Unit Testing vs Integration Testing in Node.js.
Risposta:
- Unit Testing: Test di singole funzioni/moduli in isolamento. Mock delle dipendenze. (Strumenti: Jest, Mocha, Chai).
- Integration Testing: Test di come i moduli lavorano insieme (ad esempio, endpoint API + Database). (Strumenti: Supertest).
Rarità: Comune Difficoltà: Facile
25. Cos'è TDD (Test Driven Development)?
Risposta: Un processo di sviluppo in cui scrivi il test prima del codice.
- Scrivi un test che fallisce (Red).
- Scrivi il codice minimo per superare il test (Green).
- Riorganizza il codice (Refactor).
Rarità: Media Difficoltà: Media
26. Come gestisci il logging in un'app Node.js di produzione?
Risposta: Il logging in produzione deve essere strutturato, ricercabile e sicuro per gli strumenti di osservabilità.
- Usa un logger: Pino, Winston o il logger della piattaforma invece di
console.logsparsi. - Struttura: Emetti JSON con request ID, identificatori utente o tenant quando sicuro, route, stato, latenza e metadati dell’errore.
- Livelli: Usa error, warn, info e debug in modo coerente.
- Redazione: Non loggare token, password, dati di pagamento completi o contenuti privati degli utenti.
- Correlazione: Collega log, metriche e trace per diagnosticare incidenti tra servizi.
Rarità: Comune Difficoltà: Facile
27. Spiega il Semantic Versioning (SemVer).
Risposta:
Formato: MAJOR.MINOR.PATCH (ad esempio, 1.2.3).
- MAJOR: Modifiche API incompatibili.
- MINOR: Funzionalità retrocompatibile.
- PATCH: Correzioni di bug retrocompatibili.
^vs~:^1.2.3si aggiorna a<2.0.0.~1.2.3si aggiorna a<1.3.0.
Rarità: Comune Difficoltà: Facile
28. Cosa sono le Variabili d'Ambiente e come le gestisci?
Risposta:
- Scopo: Configurazione che varia tra gli ambienti (Dev, Staging, Prod) come URL DB, chiavi API.
- Utilizzo:
process.env.VARIABLE_NAME. - Gestione: File
.env(utilizzando il pacchettodotenv) per lo sviluppo locale. In produzione, impostale nel sistema operativo o nelle impostazioni del container/piattaforma.
Rarità: Comune Difficoltà: Facile
29. Come distribuisci un'applicazione Node.js?
Risposta:
- Process Manager: PM2 (mantiene l'app attiva, gestisce i riavvii, i log).
- Reverse Proxy: Nginx (gestisce SSL, file statici, bilanciamento del carico) -> App Node.js.
- Containerizzazione: Docker (standard).
- Orchestrazione: Kubernetes.
- CI/CD: GitHub Actions, Jenkins.
Rarità: Comune Difficoltà: Media
30. Cos'è Event Emitter?
Risposta:
Il modulo events è il cuore dell'architettura event-driven di Node.js.
- Utilizzo:
- Molti moduli core lo estendono:
fs.ReadStream,net.Server,http.Server.
Rarità: Comune Difficoltà: Facile


