Perguntas para Entrevista de Desenvolvedor Backend (Go): Guia Completo

Milad Bonakdar
Autor
Domine o desenvolvimento backend em Go com perguntas essenciais para entrevistas, abrangendo concorrência, interfaces, tratamento de erros e design de sistemas. Preparação perfeita para entrevistas de desenvolvedores Go.
Introdução
Go (Golang) se tornou uma linguagem dominante para construir sistemas de backend escaláveis, microsserviços e aplicações nativas da nuvem. Sua simplicidade, modelo de concorrência robusto e desempenho a tornam uma das principais escolhas para equipes de engenharia modernas.
Este guia cobre perguntas essenciais para entrevistas de Desenvolvedores Backend especializados em Go. Exploramos conceitos básicos da linguagem, padrões de concorrência, tratamento de erros e design de sistemas para te ajudar a brilhar na sua próxima entrevista.
Conceitos Essenciais de Go
1. O que torna Go diferente de outras linguagens como Java ou Python?
Resposta: Go foi projetada pelo Google para lidar com os desafios do desenvolvimento de software em larga escala. As principais diferenças incluem:
- Simplicidade: Go tem um conjunto pequeno de palavras-chave e carece de recursos complexos como herança ou sobrecarga de métodos, priorizando a legibilidade.
- Concorrência: Suporte de primeira classe para concorrência via Goroutines e Channels, tornando mais fácil escrever programas concorrentes escaláveis.
- Compilação: Go compila diretamente para código de máquina (binários estaticamente ligados), oferecendo inicialização rápida e velocidades de execução sem uma máquina virtual (JVM).
- Coleta de Lixo: Coleta de lixo eficiente otimizada para baixa latência.
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 é essencial para gerenciar valores com escopo de requisição, sinais de cancelamento e prazos em limites de API e goroutines.
- Cancelamento: Se um usuário cancelar uma requisição, o contexto é cancelado e todas as goroutines geradas fazendo o trabalho para essa requisição devem parar para economizar recursos.
- Timeouts:
context.WithTimeoutgarante que as consultas de banco de dados ou chamadas de API externa não fiquem penduradas para sempre. - Valores: Pode carregar dados específicos da requisição, como ID do usuário ou tokens de autenticação (use com moderação).
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-os para reduzir a duplicação de código para algoritmos que se aplicam a vários tipos (como classificação, filtragem ou estruturas de dados como Sets/Trees). Evite o uso excessivo; se uma Interface for suficiente, use-a.
Raridade: Comum Dificuldade: Média
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


