Perguntas de entrevista para backend Go: guia prático

Milad Bonakdar
Autor
Prepare-se para entrevistas backend em Go com perguntas sobre goroutines, channels, context, tratamento de erros, testes, servidores HTTP e design de sistemas.
Introdução
Entrevistas backend em Go costumam avaliar se você consegue construir serviços simples e confiáveis: goroutines e channels, cancelamento com context, tratamento explícito de erros, testes, middleware HTTP e decisões de arquitetura. Uma boa resposta explica o que cada recurso faz e também quando você evitaria usá-lo.
Use este guia para praticar respostas objetivas e conecte cada tema a um projeto real: uma API que você criou, um worker pool que depurou, uma race condition que corrigiu ou um serviço que encerrou com segurança durante um deploy.
Conceitos Essenciais de Go
1. O que torna Go diferente de outras linguagens como Java ou Python?
Resposta: Go foi criado para código de sistemas legível e fácil de manter. Em uma entrevista, vale destacar:
- Simplicidade: Go evita herança e sobrecarga de métodos; equipes usam composição, interfaces pequenas e limites claros entre pacotes.
- Concorrência: Goroutines, channels, primitivas de
syncecontexttornam explícito o trabalho concorrente no backend. - Compilação: Go gera binários nativos com inicialização rápida e deploy simples.
- Biblioteca padrão: Pacotes como
net/http,context,encoding/jsonetestingcobrem muito trabalho backend antes de adicionar frameworks.
Raridade: Comum Dificuldade: Fácil
2. Explique a diferença entre Arrays e Slices.
Resposta:
- Arrays: Sequências de tamanho fixo de elementos do mesmo tipo. O tamanho faz parte do tipo (por exemplo,
[5]inté diferente de[10]int). Eles são tipos de valor; atribuir um array a outro copia todos os elementos. - Slices: Visões dinâmicas e flexíveis de um array subjacente. Eles consistem em um ponteiro para o array, um comprimento e uma capacidade. Slices são como referências; passar um slice para uma função permite a modificação dos elementos subjacentes sem copiar todos os dados.
Raridade: Comum Dificuldade: Fácil
3. Como as Interfaces funcionam em Go? O que é implementação implícita?
Resposta: Interfaces em Go são coleções de assinaturas de métodos.
- Implementação Implícita: Ao contrário de Java ou C#, um tipo não declara explicitamente que implementa uma interface (sem a palavra-chave
implements). Se um tipo define todos os métodos declarados em uma interface, ele automaticamente implementa essa interface. - Duck Typing: "Se anda como um pato e grasna como um pato, é um pato." Isso desacopla a definição da implementação, tornando o código mais flexível e fácil de simular para testes.
Raridade: Comum Dificuldade: Média
4. O que é a palavra-chave defer e como ela funciona?
Resposta:
defer agenda uma chamada de função para ser executada imediatamente antes da função retornar. É comumente usado para limpeza de recursos, como fechar arquivos, desbloquear mutexes ou fechar conexões de banco de dados.
- Ordem LIFO: As chamadas adiadas são executadas na ordem Last-In-First-Out (último a entrar, primeiro a sair).
- Avaliação de Argumentos: Os argumentos para funções adiadas são avaliados quando a instrução
deferé executada, não quando a chamada é executada.
Exemplo:
Raridade: Comum Dificuldade: Fácil
Concorrência
5. Explique Goroutines e como elas diferem das threads do sistema operacional.
Resposta:
- Goroutines: Threads leves gerenciadas pelo runtime Go. Elas começam com uma pequena pilha (por exemplo, 2 KB) que cresce e diminui dinamicamente. Milhares de goroutines podem ser executadas em uma única thread do sistema operacional.
- Threads do SO: Gerenciadas pelo kernel, têm pilhas grandes fixas (por exemplo, 1 MB) e a troca de contexto é cara.
- Escalonamento M:N: O runtime Go multiplexa M goroutines em N threads do sistema operacional, lidando com o escalonamento de forma eficiente no espaço do usuário.
Raridade: Muito Comum Dificuldade: Média
6. O que são Channels? Buffered vs. Unbuffered?
Resposta: Channels são condutos tipados que permitem que as goroutines se comuniquem e sincronizem a execução.
- Unbuffered Channels: Não têm capacidade. Uma operação de envio bloqueia até que um receptor esteja pronto, e vice-versa. Eles fornecem forte sincronização.
- Buffered Channels: Têm uma capacidade. Uma operação de envio só bloqueia se o buffer estiver cheio. Uma operação de recebimento só bloqueia se o buffer estiver vazio. Eles desacoplam o remetente e o receptor até certo ponto.
Raridade: Comum Dificuldade: Média
7. Como você lida com Race Conditions em Go?
Resposta: Uma race condition ocorre quando várias goroutines acessam memória compartilhada concorrentemente, e pelo menos um acesso é uma escrita.
- Detecção: Use o detector de corrida integrado:
go run -raceougo test -race. - Prevenção:
- Channels: "Não se comunique compartilhando memória; em vez disso, compartilhe memória comunicando."
- Pacote Sync: Use
sync.Mutexousync.RWMutexpara bloquear seções críticas. - Operações Atômicas: Use
sync/atomicpara contadores ou flags simples.
Raridade: Comum Dificuldade: Difícil
8. Para que serve a instrução select?
Resposta:
A instrução select permite que uma goroutine espere por várias operações de comunicação. Ela bloqueia até que um de seus casos possa ser executado, então ela executa esse caso. Se vários estiverem prontos, ela escolhe um aleatoriamente.
- Timeouts: Podem ser implementados usando
time.After. - Operações Não Bloqueantes: Um caso
defaulttorna o select não bloqueante se nenhum outro caso estiver pronto.
Exemplo:
Raridade: Média Dificuldade: Média
Tratamento de Erros & Robustez
9. Como funciona o Tratamento de Erros em Go?
Resposta:
Go trata erros como valores. As funções retornam um tipo error (geralmente como o último valor de retorno) em vez de lançar exceções.
- Verificar Erros: Os chamadores devem verificar explicitamente se o erro é
nil. - Erros Personalizados: Você pode criar tipos de erro personalizados implementando a interface
error(que tem um único métodoError() string). - Wrapping: Go 1.13 introduziu o wrapping de erros (
fmt.Errorf("%w", err)) para adicionar contexto, preservando o erro original para inspeção (usandoerrors.Iseerrors.As).
Raridade: Comum Dificuldade: Fácil
10. O que são Panic e Recover? Quando você deve usá-los?
Resposta:
- Panic: Interrompe o fluxo normal de controle e começa a entrar em pânico. É semelhante a uma exceção, mas deve ser reservado para erros irrecuperáveis (por exemplo, desreferência de ponteiro nulo, índice fora dos limites).
- Recover: Uma função integrada que recupera o controle de uma goroutine em pânico. Só é útil dentro de uma função
defer. - Uso: Geralmente desencorajado para o fluxo de controle normal. Use valores
errorpara condições de erro esperadas. Panic/recover é usado principalmente para situações verdadeiramente excepcionais ou dentro de bibliotecas/frameworks para evitar que uma falha derrube todo o servidor.
Raridade: Média Dificuldade: Média
Design de Sistemas & Backend
11. Como você estruturaria uma aplicação web em Go?
Resposta: Embora Go não imponha uma estrutura, um padrão comum é o "Standard Go Project Layout":
/cmd: Aplicações principais (pontos de entrada)./pkg: Código de biblioteca que pode ser usado por aplicações externas./internal: Código de aplicação e biblioteca privado (aplicado pelo compilador Go)./api: Especificações OpenAPI/Swagger, definições de protocolo./configs: Arquivos de configuração.- Arquitetura Limpa: Separar preocupações em camadas (Delivery/Handler, Usecase/Service, Repository/Data) para tornar o aplicativo testável e sustentável.
Raridade: Comum Dificuldade: Média
12. Como o pacote context funciona e por que ele é importante?
Resposta:
O pacote context carrega deadlines, sinais de cancelamento e valores da requisição através de APIs e goroutines.
- Cancelamento: Se o cliente desconecta ou uma operação pai é cancelada, o trabalho descendente deve observar
ctx.Done()e parar. - Timeouts: Use
context.WithTimeoutoucontext.WithDeadlineem consultas ao banco e chamadas externas, e chame a função cancel retornada, geralmente comdefer cancel(). - Valores: Guarde apenas metadados da requisição, não parâmetros opcionais nem objetos grandes.
Raridade: Muito Comum Dificuldade: Difícil
13. O que é Injeção de Dependência e como ela é feita em Go?
Resposta: Injeção de Dependência (DI) é um padrão de design onde um objeto recebe outros objetos dos quais depende.
- Em Go: Geralmente implementado passando dependências (como uma conexão de banco de dados ou um logger) para o construtor de um struct ou função de fábrica, geralmente por meio de interfaces.
- Benefícios: Torna o código mais modular e testável (fácil de trocar um DB real por um mock).
- Frameworks: Embora a DI manual seja preferida pela simplicidade, bibliotecas como
google/wireouuber-go/digexistem para grafos complexos.
Raridade: Média Dificuldade: Média
Banco de Dados & Ferramentas
14. Como você lida com JSON em Go?
Resposta:
Go usa o pacote encoding/json.
- Struct Tags: Use tags como
`json:"nome_do_campo"`para mapear campos de struct para chaves JSON. - Marshal: Converte um struct Go para uma string JSON (slice de bytes).
- Unmarshal: Analisa dados JSON em um struct Go.
- Streaming:
json.Decoderejson.Encodersão melhores para payloads grandes, pois processam fluxos de dados.
Raridade: Comum Dificuldade: Fácil
15. Quais são algumas ferramentas comuns de Go que você usa?
Resposta:
go mod: Gerenciamento de dependências.go fmt: Formata o código para o estilo padrão.go vet: Examina o código em busca de construções suspeitas.go test: Executa testes e benchmarks.pprof: Ferramenta de criação de perfil para analisar o uso de CPU e memória.delve: Depurador para Go.
Raridade: Comum Dificuldade: Fácil
Tópicos Avançados & Melhores Práticas
16. O que são Generics em Go e quando você deve usá-los?
Resposta: Generics (introduzidos no Go 1.18) permitem que você escreva funções e estruturas de dados que funcionam com qualquer um de um conjunto de tipos, em vez de um tipo específico.
- Parâmetros de Tipo: Definidos usando colchetes
[]. ex.,func Map[K comparable, V any](m map[K]V) ... - Constraints: Interfaces que definem o conjunto de tipos permitidos (por exemplo,
any,comparable). - Uso: Use generics quando o mesmo algoritmo ou estrutura de dados for realmente independente do tipo, como sets, helpers ou utilitários de coleções. Comece com código comum; adicione parâmetros de tipo quando a duplicação for real. Se uma interface pequena expressar melhor o comportamento, prefira a interface.
Raridade: Comum Dificuldade: Médio
17. Explique Testes Table-Driven em Go.
Resposta: O teste table-driven é um padrão preferido em Go, onde os casos de teste são definidos como um slice de structs (a "tabela"). Cada struct contém os argumentos de entrada e a saída esperada.
- Benefícios:
- Separação clara da lógica de teste e dos dados de teste.
- Fácil de adicionar novos casos de teste (basta adicionar uma linha à tabela).
- Mensagens de falha claras mostrando exatamente qual entrada falhou.
- Exemplo:
Raridade: Comum Dificuldade: Fácil
18. O que é o Middleware Pattern em servidores HTTP Go?
Resposta:
Middleware é uma função que envolve um http.Handler para executar lógica de pré ou pós-processamento antes de passar o controle para o próximo handler.
- Assinatura:
func(next http.Handler) http.Handler - Casos de Uso: Logging, Autenticação, Recuperação de Panic, Rate Limiting, CORS.
- Encadeamento: O middleware pode ser encadeado (por exemplo,
Log(Auth(Handler))).
Exemplo:
Raridade: Muito Comum Dificuldade: Média
19. Como você implementa o Graceful Shutdown em um servidor Go?
Resposta: O graceful shutdown garante que um servidor pare de aceitar novas requisições, mas termine de processar as requisições ativas antes de sair.
- Mecanismo:
- Escute os sinais do sistema operacional (SIGINT, SIGTERM) usando
os/signal. - Crie um
context.WithTimeoutpara permitir uma janela de limpeza (por exemplo, 5-10 segundos). - Chame
server.Shutdown(ctx)nohttp.Server. - Feche as conexões do DB e outros recursos.
- Escute os sinais do sistema operacional (SIGINT, SIGTERM) usando
- Importância: Evita perda de dados e erros do cliente durante as implantações.
Raridade: Comum Dificuldade: Difícil
20. Quando você deve usar sync.Map em vez de um mapa regular com um Mutex?
Resposta:
sync.Map é uma implementação de mapa thread-safe na biblioteca padrão.
- Casos de Uso:
- Cache Contention: Quando a entrada para uma determinada chave é escrita apenas uma vez, mas lida muitas vezes (por exemplo, caches de carregamento lento).
- Chaves Disjuntas: Quando várias goroutines leem, gravam e sobrescrevem entradas para conjuntos de chaves disjuntos.
- Trade-off: Para casos de uso geral (atualizações frequentes de leitura/gravação), um
mapregular protegido por umsync.RWMutexé frequentemente mais rápido e tem melhor segurança de tipo (já quesync.Mapusaany).
Raridade: Incomum Dificuldade: Difícil


