Como passar em uma entrevista de Go: principais perguntas com exemplos de código
A entrevista para a posição de desenvolvedor Go não é apenas uma verificação de sintaxe, mas também uma compreensão profunda do modelo de concorrência, gerenciamento de memória e estilo idiomático da linguagem. Neste artigo, abordamos as 10 principais perguntas que são mais frequentemente feitas em entrevistas de Golang, com respostas detalhadas e exemplos de código. O material é adequado tanto para o nível Junior quanto para o Middle.
1. Goroutines vs Threads do SO: qual a diferença?
Esta é uma pergunta básica que verifica a compreensão do modelo de execução do Go. A resposta deve ser clara:
- Tamanho da pilha: Uma goroutine inicia com ~2 KB de pilha, que cresce dinamicamente. Uma thread do SO tem uma pilha fixa (1-8 MB).
- Troca de contexto: Goroutines são alternadas no espaço do usuário (Go scheduler). Threads, através de chamadas de sistema (mais caro).
- Custo de criação: Uma goroutine custa ~4 KB de memória, uma thread ~1 MB. É possível executar milhares de goroutines.
// Exemplo: 10000 goroutinesfunc main() { for i := 0; i < 10000; i++ { go func(n int) { fmt.Println("Goroutine", n) }(i) } time.Sleep(time.Second)}2. Canais: bufferizados e não bufferizados
Pergunta sobre a compreensão de sincronização. Explique a diferença com um exemplo:
- Canal não bufferizado: O envio é bloqueado até o recebimento. Garante sincronia.
- Canal bufferizado: O envio não é bloqueado até que o buffer esteja cheio. Permite transmissão assíncrona.
// Canal não bufferizadoch := make(chan int)go func() { ch <- 42 }()val := <-ch // Bloqueia até que o valor chegue
// Canal bufferizadobufCh := make(chan int, 2)bufCh <- 1 // Não bloqueiabufCh <- 2 // Não bloqueia// bufCh <- 3 // Bloqueia - buffer cheio3. Select e manipulação de múltiplos canais
O operador select permite aguardar várias operações em canais. Frequentemente perguntam sobre default e timeouts:
func main() { ch1 := make(chan string) ch2 := make(chan string)
go func() { time.Sleep(2 * time.Second); ch1 <- "from ch1" }() go func() { time.Sleep(1 * time.Second); ch2 <- "from ch2" }()
select { case msg := <-ch1: fmt.Println(msg) case msg := <-ch2: fmt.Println(msg) case <-time.After(3 * time.Second): fmt.Println("Timeout") }}4. Interfaces: interface vazia e type assertion
Verificação da compreensão de interface{} (any) e conversão segura de tipos:
func describe(i interface{}) { switch v := i.(type) { case int: fmt.Printf("Inteiro: %d", v) case string: fmt.Printf("String: %s", v) default: fmt.Printf("Tipo desconhecido: %T", v) }}
func main() { describe(42) describe("hello") describe(3.14)}5. Gerenciamento de memória: pilha e heap, escape analysis
Pergunta sobre como o Go decide onde alocar variáveis. O ponto-chave é o escape analysis:
- Se o compilador perceber que uma variável pode ser usada após o retorno de uma função, ela "escapa" para o heap.
- O uso de ponteiros, interfaces e closures frequentemente causa alocação no heap.
// A variável x "escapa" para o heapfunc createPointer() *int { x := 42 return &x // x não pode estar na pilha}6. Defer: ordem de execução e particularidades
Defer é executado pelo princípio LIFO (Last In, First Out). Os argumentos são avaliados no momento da declaração, não da execução:
func main() { x := 10 defer