Preparati ai colloqui per Software Engineer con schemi di programmazione, strutture dati, algoritmi, progettazione di sistemi, debugging, test e domande comportamentali.
I colloqui per Software Engineer valutano la capacità di pensare con chiarezza, scrivere codice corretto, spiegare i compromessi, analizzare i casi limite e collaborare in modo affidabile.
3–6
round di colloquio tipici
45–60 min
durata di ogni round di coding
6+
schemi di programmazione essenziali
4–8 sett.
preparazione consigliata
Cosa valutano gli intervistatori
—
Scomposizione: trasformi una richiesta ambigua in un algoritmo concreto?
—
Strutture dati: scegli una rappresentazione adatta ai vincoli?
—
Correttezza: consideri casi limite, invarianti ed errori?
—
Qualità: scrivi codice leggibile e manutenibile sotto pressione?
—
Complessità: spieghi tempo e spazio senza ottimizzare prematuramente?
—
Produzione: consideri affidabilità, scala, test e rischio operativo?
—
Comunicazione: ragioni ad alta voce e reagisci bene ai suggerimenti?
Rendi visibile il tuo ragionamento
Non programmare in silenzio per venti minuti. Spiega prima l’approccio, le ipotesi, un esempio e i casi limite. Vengono valutati sia il percorso sia il risultato.
Il processo di selezione per Software Engineer
I processi tipici includono selezione, una prima prova tecnica, diversi round di coding, progettazione di sistemi per i profili senior e colloqui comportamentali e di collaborazione.
Fasi tipiche
1
Colloquio con il recruiting: conferma livello, sede, fascia retributiva, autorizzazione al lavoro e compatibilità.
2
Prima prova tecnica: in genere un esercizio di coding, talvolta con debugging o concetti fondamentali.
3
Round di programmazione: da due a quattro problemi algoritmici o pratici.
4
Progettazione di sistemi: per profili senior, con architettura, scala, dati, API e resilienza.
5
Comportamento o valori: esamina responsabilità, collaborazione, conflitto e maturità tecnica.
6
Valutazione finale: integra i segnali relativi a problem solving, codice, progettazione e lavoro di squadra.
Colloquio di programmazione
Colloquio di progettazione di sistemi
Domanda centrale
Sai risolvere correttamente in codice un problema circoscritto?
Sai progettare un sistema affidabile con vincoli reali?
Segnale positivo
Algoritmo chiaro, implementazione pulita, casi verificati e complessità corretta
API e modelli validi, piano di scalabilità, compromessi e guasti
Errore comune
Programmare prima di chiarire input e casi limite
Disegnare riquadri senza spiegare flusso, responsabilità o vincoli
Preparazione migliore
Riconoscere gli schemi ed eseguire con precisione
Comprendere le architetture e ragionare ad alta voce sui compromessi
Non conta soltanto quanti problemi hai risolto
Centinaia di esercizi servono a poco se non spieghi l’approccio, non testi il codice o non scegli bene sotto pressione. La qualità della pratica conta più della quantità.
Fondamenti dei colloqui di programmazione
Un processo ripetibile evita di precipitarsi, aiuta a trovare un approccio solido e permette all’intervistatore di seguire il ragionamento.
Un processo affidabile
1
Chiarisci input, output, vincoli, duplicati, ordine, valori nulli e comportamento ai limiti.
2
Risolvi manualmente un piccolo esempio e individua la trasformazione.
3
Parti dalla forza bruta se è utile e spiega cosa si potrebbe memorizzare, ordinare o attraversare diversamente.
4
Scegli schema e struttura dati e dichiara l’invariante che garantisce la correttezza.
5
Scrivi codice chiaro, con nomi comprensibili e senza espedienti inutili.
6
Verifica un caso normale, i limiti e un caso che metta in discussione le tue ipotesi.
7
Spiega tempo e spazio, inclusi ordinamento, stack ricorsivo e strutture ausiliarie.
Concetti essenziali
Invariante
Condizione che rimane vera durante l’algoritmo e permette di giustificarne la correttezza.
Complessità ammortizzata
Costo medio su una sequenza di operazioni, come nelle tabelle hash o negli array dinamici.
Spazio di ricerca
Insieme delle possibili risposte o degli stati esplorati mediante ricerca binaria, backtracking, BFS o DP.
Stato
Informazioni necessarie in un punto dell’algoritmo per prendere la decisione successiva.
✓ Da fare
—
Chiarire i vincoli prima di scegliere un approccio
—
Spiegare le invarianti, non soltanto i dettagli implementativi
—
Convalidare l’algoritmo con esempi prima di programmare
—
Verificare input vuoti, un solo valore, duplicati, negativi e limiti
—
Rifattorizzare solo quando migliora chiarezza o correttezza
✗ Da evitare
—
Iniziare immediatamente a programmare
—
Nascondere l’incertezza restando in silenzio
—
Applicare uno schema memorizzato che non si adatta
—
Ignorare overflow, mutazione, ordine o duplicati
—
Dichiarare la complessità prima di considerare tutte le operazioni
Array, stringhe e tabelle hash
Questi fondamenti valutano indici, frequenze, due puntatori, finestre scorrevoli e precisione dell’implementazione.
Approccio — Memorizzare il complemento in una tabella hash
Scorrerei l’array una sola volta. Per ogni x verificherei se target − x è già presente in una tabella hash. In tal caso restituirei il suo indice e quello corrente; altrimenti salverei x con il relativo indice. Ogni elemento viene elaborato una volta: O(n) tempo e O(n) spazio. Chiarirei prima se esiste esattamente una soluzione e se un indice può essere riutilizzato.
Possibili domande di approfondimento
Come gestiresti i duplicati?
Come risolveresti senza spazio aggiuntivo?
Cosa cambia se l’array è ordinato?
Approccio — Finestra scorrevole con ultimo indice osservato
Manterrei una finestra [left, right] senza duplicati e l’ultimo indice di ogni carattere. Se un carattere ricompare nella finestra, sposterei left a lastSeen + 1. Aggiornerei poi l’indice e il massimo. Left non deve mai arretrare. Il tempo è O(n) e lo spazio O(k), dove k è l’alfabeto.
Possibili domande di approfondimento
Perché left può soltanto avanzare?
Come gestiresti Unicode?
Come restituiresti anche la sottostringa?
Approccio — Creare una chiave canonica per ogni parola
Creerei per ogni parola una chiave canonica e raggrupperei le chiavi uguali in una tabella hash. La chiave può essere la stringa ordinata oppure, con un alfabeto fisso, un vettore di frequenze. Con l’ordinamento, il tempo è O(n · m log m); con le frequenze, O(n · m), dove m è la lunghezza.
Possibili domande di approfondimento
Quale soluzione è migliore per stringhe molto lunghe?
Come gestiresti le maiuscole?
Come renderesti deterministico l’output?
Liste concatenate, alberi e grafi
Qui contano disciplina nell’uso dei puntatori, invarianti ricorsive, ordine di attraversamento e gestione sicura dei nodi visitati.
Approccio — Tre puntatori: precedente, corrente e successivo
Inizializzerei prev a null e current alla testa. A ogni iterazione salverei current.next, farei puntare current.next a prev e avanzerei entrambi i puntatori. Alla fine prev è la nuova testa. L’invariante è che prev punta sempre alla parte già invertita. Tempo O(n), spazio O(1).
Possibili domande di approfondimento
Come sarebbe la soluzione ricorsiva?
Cosa succede con una lista vuota?
Come invertiresti solo un intervallo?
Approccio — Propagare limiti inferiore e superiore validi
Confrontare soltanto i figli non basta. Attraverserei ricorsivamente l’albero passando l’intervallo consentito: a sinistra, il valore corrente diventa il limite superiore; a destra, quello inferiore. Se un nodo viola l’intervallo, l’albero non è valido. Chiarirei la regola per i duplicati. Tempo O(n), spazio O(h) per la ricorsione.
Possibili domande di approfondimento
Perché non basta confrontare i figli diretti?
Come funzionerebbe un attraversamento inorder?
Come eviteresti l’overflow nei limiti?
Approccio — BFS per livelli di distanza
Tratterei ogni cella attraversabile come un nodo ed eseguirei BFS dall’inizio. Contrassegnerei una cella quando la inserisco in coda per evitare duplicati. Il primo arrivo alla destinazione è minimo grazie all’ordine per livelli. Per restituire il percorso, salverei i predecessori. Tempo e spazio O(righe · colonne).
Possibili domande di approfondimento
Come restituiresti il percorso?
Cosa cambia con celle pesate?
Come gestiresti più punti di partenza?
Approccio — Associare ogni nodo originale al suo clone
Userei DFS o BFS e una tabella che associa ogni nodo originale al suo clone. Creerei e salverei il clone alla prima visita, quindi clonerei i vicini. Salvarlo prima evita cicli infiniti e conserva i vicini condivisi. Tempo e spazio O(V + E).
Possibili domande di approfondimento
Perché salvare il clone prima di attraversare i vicini?
Come cloneresti un grafo non connesso?
Quali test coprono cicli e cappi?
Programmazione dinamica e backtracking
L’aspetto essenziale è definire con precisione lo stato, formulare una transizione completa e potare correttamente i rami non validi.
Approccio — DP bottom-up per tutti gli importi
Definirei dp[a] come il numero minimo di monete per l’importo a. Imposterei dp[0] = 0 e il resto su infinito. Per ogni importo e moneta c ≤ a, aggiornerei dp[a] = min(dp[a], dp[a − c] + 1). Se l’obiettivo resta infinito, non può essere composto. Tempo O(importo · monete), spazio O(importo).
Possibili domande di approfondimento
Come ricostruiresti le monete utilizzate?
Cosa cambia se le monete sono limitate?
Perché un approccio greedy non funziona sempre?
Approccio — DP per posizione finale e ottimizzazione con ricerca binaria
La soluzione di base definisce dp[i] come la lunghezza massima che termina in i. Per ogni j < i con nums[j] < nums[i], aggiorna dp[i], ottenendo O(n²). La soluzione ottimizzata conserva il minor valore finale possibile per ogni lunghezza e lo sostituisce mediante ricerca binaria, raggiungendo O(n log n).
Possibili domande di approfondimento
Come restituiresti la sottosequenza?
Perché tails non rappresenta necessariamente la sottosequenza effettiva?
Come gestiresti valori uguali?
Approccio — Generare soltanto prefissi validi
Costruirei la stringa un passaggio alla volta. Si può aggiungere una parentesi aperta finché open < n e una chiusa solo se close < open. Quando la lunghezza raggiunge 2n, la combinazione è valida. Poiché non si creano mai prefissi non validi, lo spazio di ricerca viene potato in anticipo.
Possibili domande di approfondimento
Qual è la dimensione asintotica dell’output?
Come lo formuleresti in modo iterativo?
Quali casi base sono importanti?
Colloqui di progettazione di sistemi
Questi colloqui valutano la maturità tecnica. Per i profili senior contano compromessi, colli di bottiglia, affidabilità, osservabilità e responsabilità operativa.
Un processo pratico
1
Chiarire requisiti funzionali ed esclusioni deliberate.
2
Chiarire scala, latenza, disponibilità, coerenza, durabilità, privacy, costo e regioni.
3
Stimare utenti, richieste al secondo, spazio di archiviazione, larghezza di banda, hot key e rapporto letture/scritture.
4
Definire API e modelli dati prima di dettagliare l’architettura.
5
Progettare client, bilanciamento, servizi, database, code, cache, object storage e processi in background.
6
Individuare colli di bottiglia e guasti, come saturazione del database, coda arretrata o interruzione regionale.
7
Confrontare SQL/NoSQL, sincrono/asincrono e coerenza/disponibilità.
8
Concludere con monitoraggio, rollout, sicurezza, rate limit, ripristino ed evoluzione.
Approccio — Requisiti → API → generazione ID → archiviazione → reindirizzamento → scala
L’utente invia un URL lungo e riceve un codice; il reindirizzamento deve essere rapido. Le API principali sono createShortUrl e redirect. Il record contiene codice, destinazione, utente, creazione, scadenza e stato.
I codici possono derivare da ID sequenziali in Base62 oppure essere generati casualmente con rilevamento delle collisioni. La lettura usa una cache davanti al database, convalida stato e scadenza e risponde con 301 o 302. Le analisi vengono elaborate in modo asincrono tramite coda. Su larga scala aggiungerei cache edge, sharding per codice, rate limit e controllo degli abusi.
Possibili domande di approfondimento
Come genereresti codici univoci?
Quando useresti 301 invece di 302?
Come registreresti le analisi senza rallentare il servizio?
Ipotizzerei un feed di account seguiti. Gli oggetti centrali sono utente, follow, pubblicazione, contenuto, voce del feed e interazione. I servizi di pubblicazione, grafo, feed e ranking lavorano con cache, object storage e code.
Il fanout in scrittura consente letture rapide ma è costoso per gli account enormi. Il fanout in lettura riduce le scritture ma aumenta la latenza. Un approccio ibrido distribuisce in scrittura gli account normali e gestisce quelli grandi in lettura. Considererei inoltre freschezza, privacy, errori delle code, invalidazione della cache e spam.
I client mantengono una connessione WebSocket con i gateway. All’invio, il gateway autentica, usa una chiave di idempotenza, salva il messaggio, pubblica un evento e lo consegna ai destinatari connessi. Gli utenti offline ricevono una notifica push e si sincronizzano dall’ultimo punto visualizzato.
Il modello include conversazione, partecipante, messaggio, consegna, lettura e dispositivo. Le chiavi evitano i duplicati, le sequenze per conversazione garantiscono l’ordine, mentre conferme e tentativi ripetuti assicurano la consegna. Gli allegati vanno nell’object storage. Monitorerei latenza, tasso di consegna, connessioni, riconnessioni, coda ed errori di scrittura.
Possibili domande di approfondimento
Come garantiresti l’ordine?
Come supporteresti più dispositivi?
Cosa memorizzeresti per le conferme di lettura?
Debugging, test e preparazione alla produzione
Questi scenari distinguono chi scrive codice da chi gestisce software in modo responsabile.
Approccio — Convalidare il segnale → delimitare l’ambito → esaminare le modifiche → scomporre le dipendenze → mitigare
Confermerei il segnale su più sistemi di monitoraggio ed esaminerei p50, p95, p99 ed endpoint. Segmentare per regione, zona, versione, host, client, tipo e dipendenza aiuta a delimitare il problema.
Esaminerei deployment, configurazione, migrazioni, traffico, feature flag, cache e interruzioni downstream. I trace scompongono la latenza; controllerei anche database, hit rate, coda, CPU, memoria, garbage collection e pool. In presenza di impatto, mitigherei prima di dimostrare la causa perfetta tramite rollback, disattivazione di un flag, scalabilità o esclusione di una dipendenza lenta.
Possibili domande di approfondimento
Cosa faresti se aumentasse soltanto p99?
Come decideresti un rollback?
Quali dashboard ti servirebbero?
Approccio — Casi normali → limiti → input non validi → invarianti
Chiarirei tipi, combinazioni, scadenza, minimo, idoneità, arrotondamento, imposte e spedizione. Gli unit test coprirebbero nessuno sconto, percentuale, importo fisso, minimo esatto e inferiore, scaduto, utente non idoneo e cumulo.
Le invarianti sono: il prezzo non è mai negativo, lo sconto non supera l’importo idoneo, una promozione scaduta non si applica e lo stesso input produce lo stesso risultato. Se influisce sui pagamenti, aggiungerei test di integrazione e registrerei la motivazione di ogni decisione.
Possibili domande di approfondimento
Come testeresti l’arrotondamento valutario?
Cosa succede se il servizio promozioni non è disponibile?
Esaminerei in quest’ordine: risolve correttamente il problema? È manutenibile e presenta confini chiari? Quali rischi introduce per migrazione, concorrenza, sicurezza, prestazioni, compatibilità o osservabilità? I test coprono la modifica?
Distinguerei i blocchi dai suggerimenti. Correttezza, sicurezza o migrazione possono bloccare; il gusto personale no. Una review deve migliorare sia il codice sia la velocità del team, non dimostrare superiorità.
Possibili domande di approfondimento
Come risolveresti un disaccordo?
Quando un commento è bloccante?
Come eviteresti ritardi inutili?
Domande comportamentali e di collaborazione
Questi round affrontano responsabilità, collaborazione, decisioni tecniche, ambiguità e apprendimento. Le risposte migliori includono conseguenze tecniche reali.
Usa storie specifiche di ingegneria
Una storia solida identifica la decisione tecnica, il compromesso, le persone coinvolte, il risultato e il cambiamento successivo. Evita storie generiche applicabili a qualsiasi ruolo.
Sceglierei una decisione con alternative reali: sviluppare o acquistare, SQL o NoSQL, correzione rapida o riprogettazione, coerenza o disponibilità. Spiegherei contesto, vincoli e opzioni.
Indicherei il compromesso e le evidenze determinanti: traffico, scadenza di un cliente, incidenti, capacità o roadmap. Concluderei con risultato e apprendimento. Una risposta matura può ammettere cosa farei diversamente oggi.
Possibili domande di approfondimento
Chi non era d’accordo?
Quali informazioni avrebbero cambiato la decisione?
Inizierei dall’impatto: cosa non ha funzionato, chi ne ha risentito e quanto era grave. Descriverei poi il mio contributo a rilevamento, triage, rollback, mitigazione, comunicazione o analisi della causa.
Durante l’incidente, ripristinare il servizio conta più che dimostrare una teoria. Spiegherei come ho usato log, metriche, trace, flag, rollback o dipendenze. Dopo la mitigazione, descriverei causa e prevenzione: test migliori, rollout sicuro, monitoraggio o runbook, senza cercare colpevoli.
Per prima cosa allineerei l’obiettivo per l’utente e l’azienda. Molti conflitti nascono da ipotesi diverse. Spiegherei con precisione il vincolo tecnico: complessità, affidabilità, tempistica, manutenzione, sicurezza o prestazioni.
Invece di limitarmi a dire no, proporrei opzioni: un MVP più piccolo, un processo manuale temporaneo, una feature flag, lavoro per fasi o un design più semplice. In presenza di rischi importanti, documenterei alternative, conseguenze e raccomandazione. L’obiettivo non è far vincere Ingegneria, ma consentire al team di prendere una decisione consapevole.
Possibili domande di approfondimento
Cosa faresti se Prodotto insistesse sull’opzione rischiosa?
Come spiegheresti il debito tecnico?
Quando porteresti la questione al livello superiore?
Strategia di preparazione per Software Engineer
Combina schemi, simulazioni, progettazione di sistemi, storie e padronanza del tuo linguaggio. L’obiettivo è una velocità affidabile, non soltanto il riconoscimento.
Piano di preparazione di sei settimane
1
Settimana 1: array, stringhe, hash, due puntatori, finestre scorrevoli, stack, code e complessità.
2
Settimana 2: alberi, grafi, BFS, DFS, ricorsione, heap, intervalli e ricerca binaria; spiegare le invarianti.
3
Settimana 3: programmazione dinamica e backtracking; definire gli stati prima di programmare.
4
Settimana 4: problemi misti a tempo e revisione per schema, bug, limite, comunicazione o ritmo.
5
Settimana 5: se pertinente, progettazione con requisiti, API, dati, scala, guasti e compromessi.
6
Settimana 6: simulazioni, storie comportamentali e approfondimento dei progetti tecnici.
Priorità per tipologia di azienda
—
Grandi aziende tecnologiche: correttezza, progettazione di sistemi, comportamento e coerenza tra i round.
—
Startup: esecuzione pratica, debugging, responsabilità, prodotto e ambiguità.
—
Infrastrutture: sistemi distribuiti, affidabilità, reti, archiviazione, concorrenza e operations.
—
IA: pipeline di dati, serving dei modelli, valutazione, latenza, affidabilità e integrazione.
—
Fintech e sanità: correttezza, sicurezza, privacy, verificabilità, conformità e rollout prudente.
Non esercitarti soltanto sugli argomenti che preferisci
Evitare grafi, DP, ricorsione o progettazione rende evidente quella debolezza sotto pressione. Registra gli errori per argomento ed esercitati su ciò che ti rende lento o insicuro.
Punto chiave
Le risposte solide combinano chiarezza algoritmica, implementazione pulita, giudizio sulla produzione e collaborazione. Non si tratta di memorizzare, ma di derivare una soluzione corretta dai principi fondamentali entro i vincoli.
Esercitati dal vivo con queste domande
Interview Pilot suggerisce risposte in tempo reale durante i colloqui, aiutandoti a rispondere con chiarezza.