dicembre 21, 2025
11 min di lettura

Domande di colloquio per sviluppatori Backend (Go): Guida completa

interview
career-advice
job-search
Domande di colloquio per sviluppatori Backend (Go): Guida completa
MB

Milad Bonakdar

Autore

Padroneggia lo sviluppo backend in Go con domande essenziali per il colloquio che coprono concorrenza, interfacce, gestione degli errori e progettazione del sistema. Preparazione perfetta per i colloqui da sviluppatore Go.


Introduzione

Go (Golang) è diventato un linguaggio dominante per la creazione di sistemi backend scalabili, microservizi e applicazioni cloud-native. La sua semplicità, il solido modello di concorrenza e le prestazioni lo rendono una scelta ideale per i moderni team di ingegneria.

Questa guida copre le domande essenziali per i colloqui per sviluppatori Backend specializzati in Go. Esploreremo i concetti fondamentali del linguaggio, i modelli di concorrenza, la gestione degli errori e la progettazione del sistema per aiutarti a superare il tuo prossimo colloquio.


Concetti di base di Go

1. Cosa rende Go diverso da altri linguaggi come Java o Python?

Risposta: Go è stato progettato da Google per affrontare le sfide dello sviluppo di software su larga scala. Le differenze principali includono:

  • Semplicità: Go ha un piccolo set di parole chiave e manca di funzionalità complesse come l'ereditarietà o l'overloading dei metodi, privilegiando la leggibilità.
  • Concorrenza: Supporto di prima classe per la concorrenza tramite Goroutine e Canali, che semplifica la scrittura di programmi concorrenti scalabili.
  • Compilazione: Go compila direttamente in codice macchina (binari collegati staticamente), offrendo velocità di avvio ed esecuzione elevate senza una macchina virtuale (JVM).
  • Garbage Collection: Garbage collection efficiente ottimizzato per una bassa latenza.

Rarità: Comune Difficoltà: Facile


2. Spiega la differenza tra Array e Slice.

Risposta:

  • Array: Sequenze di dimensioni fisse di elementi dello stesso tipo. La dimensione fa parte del tipo (ad esempio, [5]int è diverso da [10]int). Sono tipi di valore; assegnare un array a un altro copia tutti gli elementi.
  • Slice: Viste dinamiche e flessibili di un array sottostante. Sono costituiti da un puntatore all'array, una lunghezza e una capacità. Le slice sono simili a riferimenti; passare una slice a una funzione consente la modifica degli elementi sottostanti senza copiare l'intero set di dati.

Rarità: Comune Difficoltà: Facile


3. Come funzionano le Interfacce in Go? Cos'è l'implementazione implicita?

Risposta: Le interfacce in Go sono raccolte di firme di metodi.

  • Implementazione implicita: A differenza di Java o C#, un tipo non dichiara esplicitamente di implementare un'interfaccia (nessuna parola chiave implements). Se un tipo definisce tutti i metodi dichiarati in un'interfaccia, implementa automaticamente tale interfaccia.
  • Duck Typing: "Se cammina come un'anatra e starnazza come un'anatra, è un'anatra". Questo disaccoppia la definizione dall'implementazione, rendendo il codice più flessibile e più facile da simulare per i test.

Rarità: Comune Difficoltà: Media


4. Cos'è la parola chiave defer e come funziona?

Risposta: defer pianifica una chiamata di funzione da eseguire immediatamente prima che la funzione ritorni. Viene comunemente utilizzato per la pulizia delle risorse, come la chiusura di file, lo sblocco di mutex o la chiusura di connessioni al database.

  • Ordine LIFO: Le chiamate differite vengono eseguite in ordine Last-In-First-Out.
  • Valutazione degli argomenti: Gli argomenti delle funzioni differite vengono valutati quando viene eseguita l'istruzione defer, non quando viene eseguita la chiamata.

Esempio:

func readFile(filename string) {
    f, err := os.Open(filename)
    if err != nil {
        return
    }
    defer f.Close() // Assicura che il file venga chiuso quando la funzione esce
    // ... process file
}

Rarità: Comune Difficoltà: Facile


Concorrenza

5. Spiega le Goroutine e in cosa differiscono dai thread del sistema operativo.

Risposta:

  • Goroutine: Thread leggeri gestiti dal runtime di Go. Iniziano con uno stack piccolo (ad esempio, 2 KB) che cresce e si riduce dinamicamente. Migliaia di goroutine possono essere eseguite su un singolo thread del sistema operativo.
  • Thread del sistema operativo: Gestiti dal kernel, hanno stack fissi di grandi dimensioni (ad esempio, 1 MB) e il cambio di contesto è costoso.
  • Pianificazione M:N: Il runtime di Go multiplexa M goroutine su N thread del sistema operativo, gestendo la pianificazione in modo efficiente nello spazio utente.

Rarità: Molto comune Difficoltà: Media


6. Cosa sono i Canali? Bufferizzati vs. Non bufferizzati?

Risposta: I canali sono condotti tipizzati che consentono alle goroutine di comunicare e sincronizzare l'esecuzione.

  • Canali non bufferizzati: Non hanno capacità. Un'operazione di invio si blocca finché un ricevitore non è pronto e viceversa. Forniscono una forte sincronizzazione.
  • Canali bufferizzati: Hanno una capacità. Un'operazione di invio si blocca solo se il buffer è pieno. Un'operazione di ricezione si blocca solo se il buffer è vuoto. Disaccoppiano in qualche modo mittente e destinatario.

Rarità: Comune Difficoltà: Media


7. Come gestisci le Race Condition in Go?

Risposta: Una race condition si verifica quando più goroutine accedono contemporaneamente alla memoria condivisa e almeno un accesso è una scrittura.

  • Rilevamento: Utilizzare il rilevatore di race integrato: go run -race o go test -race.
  • Prevenzione:
    • Canali: "Non comunicare condividendo la memoria; invece, condividi la memoria comunicando."
    • Pacchetto Sync: Utilizzare sync.Mutex o sync.RWMutex per bloccare le sezioni critiche.
    • Operazioni atomiche: Utilizzare sync/atomic per contatori o flag semplici.

Rarità: Comune Difficoltà: Difficile


8. A cosa serve l'istruzione select?

Risposta: L'istruzione select consente a una goroutine di attendere più operazioni di comunicazione. Si blocca finché uno dei suoi casi non può essere eseguito, quindi esegue quel caso. Se più casi sono pronti, ne sceglie uno a caso.

  • Timeout: Può essere implementato utilizzando time.After.
  • Operazioni non bloccanti: Un caso default rende la selezione non bloccante se nessun altro caso è pronto.

Esempio:

select {
case msg := <-ch1:
    fmt.Println("Received", msg)
case ch2 <- "hello":
    fmt.Println("Sent hello")
case <-time.After(1 * time.Second):
    fmt.Println("Timeout")
}

Rarità: Media Difficoltà: Media


Gestione degli errori e robustezza

9. Come funziona la gestione degli errori in Go?

Risposta: Go tratta gli errori come valori. Le funzioni restituiscono un tipo error (di solito come ultimo valore restituito) invece di generare eccezioni.

  • Controlla errori: I chiamanti devono controllare esplicitamente se l'errore è nil.
  • Errori personalizzati: È possibile creare tipi di errore personalizzati implementando l'interfaccia error (che ha un singolo metodo Error() string).
  • Wrapping: Go 1.13 ha introdotto il wrapping degli errori (fmt.Errorf("%w", err)) per aggiungere contesto preservando l'errore originale per l'ispezione (utilizzando errors.Is e errors.As).

Rarità: Comune Difficoltà: Facile


10. Cosa sono Panic e Recover? Quando dovresti usarli?

Risposta:

  • Panic: Interrompe il normale flusso di controllo e inizia a generare panico. È simile a un'eccezione, ma dovrebbe essere riservato a errori non recuperabili (ad esempio, dereferenziazione di puntatore nullo, indice fuori dai limiti).
  • Recover: Una funzione integrata che riprende il controllo di una goroutine in panico. È utile solo all'interno di una funzione defer.
  • Utilizzo: Generalmente sconsigliato per il normale flusso di controllo. Utilizzare i valori error per le condizioni di errore previste. Panic/recover viene utilizzato principalmente per situazioni veramente eccezionali o all'interno di librerie/framework per impedire a un arresto anomalo di abbattere l'intero server.

Rarità: Media Difficoltà: Media


Progettazione del sistema e backend

11. Come struttureresti un'applicazione web Go?

Risposta: Sebbene Go non imponga una struttura, uno standard comune è il "Layout del progetto Go standard":

  • /cmd: Applicazioni principali (punti di ingresso).
  • /pkg: Codice della libreria che può essere utilizzato da applicazioni esterne.
  • /internal: Codice privato dell'applicazione e della libreria (applicato dal compilatore Go).
  • /api: Specifiche OpenAPI/Swagger, definizioni di protocollo.
  • /configs: File di configurazione.
  • Architettura pulita: Separare le preoccupazioni in livelli (Delivery/Handler, Usecase/Service, Repository/Data) per rendere l'app testabile e manutenibile.

Rarità: Comune Difficoltà: Media


12. Come funziona il pacchetto context e perché è importante?

Risposta: Il pacchetto context è essenziale per la gestione di valori con ambito di richiesta, segnali di annullamento e scadenze tra i confini dell'API e le goroutine.

  • Annullamento: Se un utente annulla una richiesta, il contesto viene annullato e tutte le goroutine generate che eseguono il lavoro per tale richiesta devono interrompersi per risparmiare risorse.
  • Timeout: context.WithTimeout garantisce che le query del database o le chiamate API esterne non si blocchino per sempre.
  • Valori: Può contenere dati specifici della richiesta come ID utente o token di autenticazione (usare con parsimonia).

Rarità: Molto comune Difficoltà: Difficile


13. Cos'è l'iniezione delle dipendenze e come viene eseguita in Go?

Risposta: L'iniezione delle dipendenze (DI) è un modello di progettazione in cui un oggetto riceve altri oggetti da cui dipende.

  • In Go: Solitamente implementato passando le dipendenze (come una connessione al database o un logger) nel costruttore o nella funzione factory di uno struct, spesso tramite interfacce.
  • Vantaggi: Rende il codice più modulare e testabile (facile sostituire il database reale con un mock).
  • Framework: Sebbene la DI manuale sia preferibile per semplicità, esistono librerie come google/wire o uber-go/dig per grafici complessi.

Rarità: Media Difficoltà: Media


Database e strumenti

14. Come gestisci JSON in Go?

Risposta: Go utilizza il pacchetto encoding/json.

  • Tag Struct: Utilizzare tag come `json:"field_name"` per mappare i campi dello struct alle chiavi JSON.
  • Marshal: Converte uno struct Go in una stringa JSON (slice di byte).
  • Unmarshal: Analizza i dati JSON in uno struct Go.
  • Streaming: json.Decoder e json.Encoder sono migliori per payload di grandi dimensioni poiché elaborano flussi di dati.

Rarità: Comune Difficoltà: Facile

15. Quali sono alcuni strumenti Go comuni che utilizzi?

Risposta:

  • go mod: Gestione delle dipendenze.
  • go fmt: Formatta il codice in stile standard.
  • go vet: Esamina il codice alla ricerca di costrutti sospetti.
  • go test: Esegue test e benchmark.
  • pprof: Strumento di profilazione per l'analisi dell'utilizzo di CPU e memoria.
  • delve: Debugger per Go.

Rarità: Comune Difficoltà: Facile


Argomenti avanzati e migliori pratiche

16. Cosa sono i Generics in Go e quando dovresti usarli?

Risposta: I Generics (introdotti in Go 1.18) consentono di scrivere funzioni e strutture di dati che funzionano con qualsiasi insieme di tipi, anziché con un tipo specifico.

  • Parametri di tipo: Definiti utilizzando parentesi quadre []. ad esempio, func Map[K comparable, V any](m map[K]V) ...
  • Vincoli: Interfacce che definiscono l'insieme di tipi ammissibili (ad esempio, any, comparable).
  • Utilizzo: Utilizzarli per ridurre la duplicazione del codice per algoritmi che si applicano a più tipi (come l'ordinamento, il filtraggio o strutture di dati come Set/Trees). Evitare l'uso eccessivo; se un'interfaccia è sufficiente, utilizzare quella.

Rarità: Comune Difficoltà: Media


17. Spiega i Table-Driven Tests in Go.

Risposta: Il table-driven testing è un modello preferito in Go in cui i casi di test sono definiti come una slice di struct (la "tabella"). Ogni struct contiene gli argomenti di input e l'output previsto.

  • Vantaggi:
    • Netta separazione della logica di test e dei dati di test.
    • Facile aggiungere nuovi casi di test (basta aggiungere una riga alla tabella).
    • Messaggi di errore chiari che mostrano esattamente quale input non è riuscito.
  • Esempio:
func TestAdd(t *testing.T) {
    tests := []struct {
        a, b, expected int
    }{
        {1, 1, 2},
        {2, -2, 0},
    }
    for _, tt := range tests {
        result := Add(tt.a, tt.b)
        if result != tt.expected {
            t.Errorf("Add(%d, %d): expected %d, got %d", tt.a, tt.b, tt.expected, result)
        }
    }
}

Rarità: Comune Difficoltà: Facile


18. Cos'è il Middleware Pattern nei server HTTP Go?

Risposta: Il middleware è una funzione che racchiude un http.Handler per eseguire la logica di pre- o post-elaborazione prima di passare il controllo al gestore successivo.

  • Firma: func(next http.Handler) http.Handler
  • Casi d'uso: Logging, Autenticazione, Panic Recovery, Rate Limiting, CORS.
  • Concatenamento: Il middleware può essere concatenato (ad esempio, Log(Auth(Handler))).

Esempio:

func LoggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        log.Println(r.Method, r.URL.Path)
        next.ServeHTTP(w, r)
    })
}

Rarità: Molto comune Difficoltà: Media


19. Come implementi lo spegnimento graduale in un server Go?

Risposta: Lo spegnimento graduale garantisce che un server smetta di accettare nuove richieste ma termini l'elaborazione delle richieste attive prima di uscire.

  • Meccanismo:
    1. Ascolta i segnali del sistema operativo (SIGINT, SIGTERM) utilizzando os/signal.
    2. Crea un context.WithTimeout per consentire una finestra di pulizia (ad esempio, 5-10 secondi).
    3. Chiama server.Shutdown(ctx) sull'http.Server.
    4. Chiudi le connessioni DB e altre risorse.
  • Importanza: Impedisce la perdita di dati e gli errori del client durante le implementazioni.

Rarità: Comune Difficoltà: Difficile


20. Quando dovresti usare sync.Map invece di una normale mappa con un Mutex?

Risposta: sync.Map è un'implementazione di mappa thread-safe nella libreria standard.

  • Casi d'uso:
    1. Cache Contention: Quando la voce per una determinata chiave viene scritta solo una volta ma letta molte volte (ad esempio, cache a caricamento lento).
    2. Chiavi disgiunte: Quando più goroutine leggono, scrivono e sovrascrivono voci per insiemi di chiavi disgiunti.
  • Compromesso: Per i casi d'uso generali (aggiornamenti di lettura/scrittura frequenti), una normale map protetta da sync.RWMutex è spesso più veloce e ha una migliore sicurezza dei tipi (poiché sync.Map utilizza any).

Rarità: Non comune Difficoltà: Difficile

Newsletter subscription

Consigli di carriera settimanali che funzionano davvero

Ricevi le ultime idee direttamente nella tua casella di posta

Decorative doodle

Il Tuo Prossimo Colloquio Dista Solo un Curriculum

Crea un curriculum professionale e ottimizzato in pochi minuti. Non servono competenze di design—solo risultati comprovati.

Crea il mio curriculum

Condividi questo post

Riduci il Tempo di Scrittura del Curriculum del 90%

La persona in cerca di lavoro media impiega più di 3 ore per formattare un curriculum. La nostra IA lo fa in meno di 15 minuti, portandoti alla fase di candidatura 12 volte più velocemente.