Pular para o conteúdo principal

Q&A: perguntas e respostas sobre backend, banco de dados e arquitetura

· 28 min para ler
Leandro Andrade
Leandro Andrade
Software Developer

Uma coletânea de perguntas e respostas sobre banco de dados, cache, segurança, arquitetura e JavaScript. Cada pergunta está colapsada: clique para revelar a resposta e aproveite para responder mentalmente antes.

Como usar

Leia a pergunta, formule sua resposta e só então expanda o bloco. A maioria das perguntas não tem uma resposta única — o valor está no trade-off que cada decisão carrega.

Antes de começar

Boa parte das perguntas a seguir não tem resposta absoluta: quase toda decisão de backend é a escolha de qual custo se está disposto a pagar. Índices trocam escrita por leitura, soft delete troca espaço por auditabilidade, microsserviços trocam simplicidade por independência de deploy e cache troca consistência por latência.

Alguns padrões se repetem ao longo das respostas e valem como heurística de leitura:

  • Nunca confie na entrada: valide no cliente pela experiência e no servidor pela segurança;
  • Cuidado com funções sobre colunas indexadas: elas invalidam o índice silenciosamente;
  • NULL não é vazio: é desconhecido, e isso muda a lógica das comparações em SQL;
  • Falhas são o caso comum em sistemas distribuídos: idempotência é requisito, não refinamento;
  • "Sempre" e "nunca" são sinais de alerta: a resposta quase sempre começa com "depende do contexto".

Mais importante do que decorar as respostas é conseguir justificar o trade-off de cada uma delas — é isso que diferencia uma decisão técnica de uma preferência pessoal.

Banco de dados: índices e planos de execução​

Quais dessas opções utiliza o índice do banco de dados? WHERE name LIKE 'Sam%' ou WHERE name LIKE '%Sam%'

A primeira usa índice, pois o banco consegue fazer uma varredura de intervalo (range scan) a partir de Sam.

A segunda faz full table scan, já que o valor pode corresponder a qualquer posição da string — não há prefixo conhecido para navegar na árvore do índice.

O que há de errado com a query SELECT * FROM users WHERE email LIKE '%@gmail.com'?

Não há nada de errado com a query em si. O problema é de performance: o % no início impede o uso de índices, forçando um full table scan.

Por que a query SELECT DISTINCT country FROM users é lenta em tabelas grandes?

Sem um índice em country, o banco precisa fazer um full table scan, deduplicar todos os valores lidos e só então retornar os valores únicos.

Com um índice, a operação pode ser resolvida por uma varredura do próprio índice — muito mais rápida.

Qual o problema da query SELECT * FROM orders WHERE created_at = '2026-01-01'?

A query está correta se a coluna created_at for do tipo DATE. Se for DATETIME ou TIMESTAMP, somente os registros com 2026-01-01 00:00:00 exatos serão retornados.

Para trazer todos os registros do dia, use um intervalo semiaberto:

SELECT * FROM orders
WHERE created_at >= '2026-01-01'
AND created_at < '2026-01-02';
Qual o problema da query SELECT * FROM logs WHERE YEAR(created_at) = 2026?

Aplicar uma função sobre a coluna impede o uso do índice: o banco precisa executar YEAR() para cada linha antes de aplicar o filtro, resultando em full table scan.

A abordagem correta é filtrar por intervalo, mantendo a coluna "limpa" do lado esquerdo da comparação:

SELECT * FROM logs
WHERE created_at >= '2026-01-01'
AND created_at < '2027-01-01';
O que há de errado com a query SELECT * FROM transactions WHERE amount > '1000'?

O valor entre aspas provoca uma conversão implícita de tipo (string para número) linha a linha, o que pode invalidar o índice e gerar full table scan.

dica

Use aspas apenas para strings e datas. Para colunas numéricas, compare com literais numéricos.

O que está errado na query SELECT * FROM users WHERE id NOT IN (SELECT user_id FROM banned_users)?

Se banned_users.user_id contiver ao menos um NULL, o NOT IN pode não retornar nenhum registro, porque a comparação passa a resultar em UNKNOWN.

A abordagem mais segura é:

SELECT * FROM users u
WHERE NOT EXISTS (
SELECT 1 FROM banned_users b WHERE b.user_id = u.id
);
Qual a diferença entre WHERE age = NULL e WHERE age IS NULL?

O SQL usa lógica trivalente: TRUE, FALSE e UNKNOWN. NULL não significa vazio nem zero — significa desconhecido.

Por isso, comparações como age = NULL, age != NULL ou age > NULL não retornam TRUE ou FALSE, mas sim UNKNOWN. Como a cláusula WHERE só mantém linhas cujo predicado é TRUE, nenhum registro é retornado.

A forma correta é WHERE age IS NULL.

Qual query é mais rápida: WHERE id IN (1, 2, 3) ou WHERE id = 1 OR id = 2 OR id = 3?

São praticamente equivalentes nos SGBDs modernos: o próprio otimizador reescreve as duas para o mesmo plano de execução. A diferença de performance, quando existe, é irrelevante.

A vantagem real do IN é legibilidade e manutenibilidade.

Por que a query abaixo é custosa?
SELECT * FROM users u
JOIN orders o ON u.id = o.user_id
JOIN payments p ON o.id = p.order_id

Pelos seguintes motivos:

  • sem índices nas colunas de junção, os relacionamentos viram full table scan;
  • o * traz todas as colunas de todas as tabelas envolvidas — muito mais dados do que o necessário;
  • sem WHERE ou LIMIT, o conjunto de resultados cresce sem limite.

Visualizar o plano com EXPLAIN (ou EXPLAIN ANALYZE) ajuda a evidenciar onde está o gargalo.

Por que a paginação com ORDER BY name LIMIT 10 quebra?

Porque a ordenação não é determinística quando existem valores duplicados em name. Linhas com o mesmo nome podem mudar de posição entre uma consulta e outra, fazendo com que registros sejam repetidos ou pulados entre páginas.

A correção é adicionar um critério de desempate único e ordenável:

ORDER BY name, id
Um tech lead diz: "os índices deixam a escrita mais lenta, vamos remover todos". O que você responde?

Os índices realmente tornam a escrita mais lenta — cada INSERT/UPDATE precisa manter as estruturas auxiliares. Mas sem índices, toda leitura vira full table scan.

Esse é o trade-off: escrita um pouco mais cara em troca de leitura ordens de grandeza mais barata. Índices devem ser revisados e removidos quando não são usados, não eliminados por princípio.

Banco de dados: modelagem e concorrência​

Qual a diferença entre optimistic locking e pessimistic locking?

No optimistic locking, pressupõe-se que não haverá conflitos. A operação flui normalmente e o conflito só é verificado no momento da escrita, geralmente com um campo version (ou timestamp) em cada linha.

Exemplo: o usuário leu o registro na versão 5 e envia a alteração. O UPDATE ocorre somente se a versão ainda for 5; em caso de sucesso, a versão é incrementada para 6. Se outro usuário também estava na versão 5, sua atualização falha e ele precisa reler o dado e tentar novamente.

UPDATE products
SET price = 99, version = version + 1
WHERE id = 42 AND version = 5;
  • Vantagem: rápido e escalável, sem esperas e sem locks.
  • Desvantagem: muitos conflitos geram muitas retentativas.

No pessimistic locking, pressupõe-se que haverá conflitos. A linha é travada para impedir alterações concorrentes: o primeiro usuário trava o registro, altera e salva; o segundo espera até a liberação (por exemplo, dois depósitos na mesma conta bancária).

  • Vantagem: bastante seguro.
  • Desvantagem: mais lento e sujeito a deadlocks.
Um tech lead diz: "nunca delete um dado, apenas marque is_deleted = true em todas as tabelas". Você concorda?

Isso se chama soft delete, e a decisão depende do uso do dado:

  • o dado precisa ser mantido por auditoria? Então soft delete;
  • o dado precisa ser mantido para sempre? Não — soft delete e depois hard delete;
  • o dado não é necessário? Hard delete.

Aplicar soft delete em tudo incha a base, aumenta o custo de armazenamento e deixa as queries mais complexas — adicionar WHERE is_deleted = false em toda consulta tem custo, inclusive de índice.

Uma estratégia comum é transicionar de soft delete para hard delete com base em um TTL, permitindo reverter a operação dentro de uma janela. Em vez de is_deleted = true, prefira deleted_at = timestamp: guarda a mesma informação e ainda registra quando ocorreu.

Portanto, se o dado é crítico e precisa continuar existindo mesmo após ser "deletado", a estratégia sugerida faz sentido — apenas não como regra universal.

Seu tech lead diz: "nunca use UUID como primary key". O que você responde?

Depende do contexto.

UUIDs são muito bons para:

  • sistemas distribuídos;
  • evitar colisão e enumeração de identificadores;
  • geração de ID offline (sem coordenador central);
  • identificadores externos (external_id).

As desvantagens — pensando em UUIDv4, já que com UUIDv7 o cenário muda — incluem:

  • índices maiores;
  • inserção mais lenta;
  • inserção aleatória na B-tree, gerando fragmentação e prejudicando a performance.
CritérioID sequencial (BIGINT)UUIDv4UUIDv7 / ULID
Tamanho no índiceMenorMaiorMaior
Ordenação temporalSimNãoSim
Fragmentação na B-treeBaixaAltaBaixa
Geração distribuídaRequer coordenaçãoNativaNativa
Enumerável externamenteSimNãoNão
Abordagem equilibrada

Use ID sequencial como chave interna e para relacionamentos entre tabelas, e UUID como identificador externo, exposto na API.

Por que UUIDs têm número de versão?

As versões de UUID não são revisões — não existe "versão mais nova e melhor". Cada versão representa uma estratégia diferente de geração:

  • v1: timestamp + identificador do host;
  • v4: aleatoriedade;
  • v7: ordenado por tempo, pensado para uso como chave em bancos de dados.
Qual a diferença entre CHAR e VARCHAR?
  • CHAR: tamanho fixo, sempre reserva o espaço completo definido (com preenchimento).
  • VARCHAR: tamanho variável, armazena apenas o que for utilizado.
Se o PostgreSQL suporta JSON, por que as pessoas ainda escolhem bancos NoSQL?

O JSONB do PostgreSQL atende muito bem quando o projeto precisa apenas de um campo flexível dentro de um modelo relacional.

A complexidade aumenta quando o requisito é modelagem plenamente orientada a documentos, sharding e escala horizontal nativa, ou esquemas que mudam com frequência em toda a estrutura. Nesses casos, um banco NoSQL costuma ser a opção mais adequada.

O tech lead diz: "nunca armazene JSON no PostgreSQL, vamos usar MongoDB". O que você responde?

O PostgreSQL já entrega:

  • consulta e indexação sobre JSON (JSONB, índices GIN);
  • transações ACID;
  • joins;
  • consistência relacional;
  • flexibilidade de esquema quando necessário (jsonb, arrays, tipos compostos).

O MongoDB pode ser muito útil, mas "precisar de JSON" não é razão suficiente para introduzir mais um banco na arquitetura — isso adiciona custo operacional, mais um ponto de falha e mais um modelo de consistência para gerenciar.

Faz sentido adotar MongoDB quando o dado é genuinamente orientado a documentos, com esquema flexível e sem relacionamentos rígidos.

Cache​

O que é cache stampede?

É o efeito que ocorre quando diversas requisições tentam regenerar o mesmo dado logo após sua expiração. Por exemplo: o cache expira e 100 requisições chegam simultaneamente para recomputar o mesmo valor.

Fluxo do problema:

  1. o cache expira;
  2. muitas requisições chegam;
  3. todas detectam cache miss;
  4. todas tentam recomputar;
  5. o sistema (ou a origem) sobrecarrega.

O uso de requisições in-flight (single-flight) resolve o problema: 100 requisições resultam em 1 chamada externa e 99 aguardando o mesmo resultado.

Qual a diferença entre warm cache e cold cache? Qual é melhor?

Com warm cache, os dados já estão em memória e a resposta é imediata. O ponto negativo é armazenar muitos dados que talvez nunca sejam acessados, além de ser inviável para datasets muito grandes.

Com cold cache, a primeira requisição vai ao banco e só então o resultado é armazenado. É relativamente mais lento no primeiro acesso, mas é sob demanda: apenas os dados efetivamente acessados ocupam memória.

Não há um "melhor" absoluto — o aquecimento prévio compensa quando o conjunto quente é pequeno e conhecido; caso contrário, o preenchimento sob demanda é mais eficiente.

Se o Redis é single thread, como consegue lidar com milhões de requisições por segundo?

O Redis usa a thread principal para executar os comandos, e esses comandos não fazem I/O bloqueante: os dados estão em memória, sem acesso a disco no caminho crítico. Com isso, cada operação termina em tempo muito curto.

A thread lê um comando, executa imediatamente, envia a resposta e passa para o próximo — sem troca de contexto entre threads e sem bloqueios por lock.

Autenticação e segurança​

Por que adicionar o prefixo Bearer em vez de enviar o token diretamente?

Porque Bearer não é parte do token: é o esquema de autenticação.

Em Authorization: Bearer <token>, o prefixo indica ao servidor como interpretar o valor, já que o HTTP suporta múltiplos esquemas (Basic, Digest, Bearer).

GET /api/v1/orders HTTP/1.1
Host: api.exemplo.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Sem o esquema, o valor é apenas uma string arbitrária, sem contrato de interpretação.

Se o JWT é stateless, como fazer o logout do usuário?

Um token não é "deletado" — não existe logout propriamente dito. A ideia é torná-lo inútil. Para isso:

  1. Blocklist no Redis: armazena os identificadores (jti) dos tokens invalidados;
  2. Expiração curta + refresh token: o access token vale 15 minutos e, no logout, o refresh token é removido;
  3. Token versioning: a cada logout, incrementa-se a versão do token no banco; tokens antigos passam a ser recusados imediatamente.
Quando um usuário faz logout ou redefine a senha, como encerrar todas as sessões instantaneamente usando JWT?

Possíveis abordagens:

  • rotacionar o salt do token;
  • rotacionar o secret de assinatura (invalida todas as sessões, inclusive de outros usuários).

Uma solução mais cirúrgica é armazenar uma versão de sessão do usuário no banco de dados. No logout ou na redefinição de senha, a versão é incrementada; toda validação de token compara a versão embutida no payload com a versão atual.

A vantagem é não precisar manter uma blocklist token a token — uma única coluna invalida todas as sessões daquele usuário.

A aplicação armazena o JWT no localStorage e um script malicioso passou a agir como o usuário. Como evitar?

O localStorage é acessível por qualquer JavaScript da página, o que o torna vulnerável a XSS. Medidas de mitigação:

  • armazenar o token em cookie com HttpOnly (inacessível ao js), Secure e SameSite;
  • sanitizar e validar todas as entradas, além de aplicar uma Content-Security-Policy;
  • usar expiração curta para o access token, com refresh token rotacionado.
Qual a vulnerabilidade do código abaixo e como corrigi-la?
import jwt from "jsonwebtoken";

const KEY = process.env.KEY;

export function verifyToken(token) {
return jwt.verify(token, KEY);
}

Sem restringir o algoritmo, um atacante pode:

  • alterar o algoritmo de RS256 para HS256;
  • usar a chave pública como secret HMAC;
  • forjar tokens arbitrários, como { "role": "admin" }.

O resultado é bypass de autenticação, escalação de privilégio e acesso administrativo sem comprometer a chave privada.

A correção é declarar explicitamente os algoritmos aceitos:

jwt.verify(token, KEY, { algorithms: ["RS256"] });
Um tech lead pergunta: "APIs públicas e privadas devem aceitar o mesmo JWT?". O que você responde?

Não é uma boa ideia. Serviços internos e externos têm limites de confiança e requisitos de segurança diferentes.

Se o mesmo token vale nos dois mundos, um JWT público comprometido dá acesso também à rede interna — uma brecha em um serviço externo vira imediatamente uma brecha interna.

O correto é separar emissores, audiências (aud) e escopos, tratando o tráfego interno como um domínio de confiança distinto.

Um tech lead diz: "o frontend pode acessar o banco de dados diretamente para melhorar a performance". O que você responde?

Além de expor credenciais de banco no browser, essa abordagem faz bypass de autenticação, autorização, validação de entrada e regras de negócio.

O ganho de latência é irrelevante diante do risco: qualquer usuário passa a poder executar consultas arbitrárias sobre a base.

Estamos construindo um webhook e qualquer um pode descobrir a URL. Como garantir que apenas clientes autorizados chamem o endpoint?

Primeiro, sempre use https e rejeite qualquer requisição http.

Camadas de proteção:

  • Whitelist de IPs autorizados a realizar a chamada;
  • Verificação de assinatura: calcule um HMAC-SHA256 sobre o raw payload + timestamp usando a secret compartilhada e compare com o cabeçalho de assinatura enviado.

Pseudocódigo da geração:

base = timestamp + "." + event_id + "." + rawBody
signature = HMAC-SHA256(base, your_secret_key)

headers = {
'x-timestamp': timestamp,
'x-event-id': '3f8a2e1b...',
'x-signature': 'v1=' + hex(signature),
}

E da verificação:

secret_key = "MY_SECRET"
payload = request.body
signature_from_provider = request.headers["x-signature"]

computed_signature = HMAC_SHA256(secret_key, payload)

if not constant_time_equals(computed_signature, signature_from_provider):
return 403 Forbidden

processRequest(request)
Detalhes que importam
  • Assine o corpo bruto (raw bytes), sem parse e re-serialização — qualquer reordenação de chaves quebra a assinatura.
  • Compare assinaturas em tempo constante, para evitar timing attacks.
  • Inclua o timestamp na assinatura e rejeite requisições antigas, evitando replay attacks.

Boas práticas complementares:

  • registre em log todas as requisições recebidas — ajuda em depuração, auditoria e compliance;
  • aplique rate limit e, se possível, um WAF para bloquear IPs suspeitos;
  • responda rápido e processe de forma assíncrona: espera-se que URLs de callback retornem 200 OK rapidamente. Processamento pesado deve ir para um background job.
Sua API interna não tem acesso externo. Ainda assim, você implementaria rate limiting?

Sim. Serviço confiável não significa serviço inofensivo. Uma chamada interna pode derrubar a API por bugs, configuração errada, retentativas infinitas ou por ter sido comprometida.

Rate limit é menos sobre bloquear atacantes e mais sobre proteger recursos compartilhados e manter o sistema resiliente.

Atacantes estão fazendo bypass do rate limit usando múltiplos IPs. Como proteger a API em produção?

Quando há autenticação, aplique o limite por conta/usuário, não apenas por IP, combinado com análise de comportamento (taxa de erro, padrão de acesso, horários).

Outras camadas possíveis:

  • device fingerprinting;
  • CAPTCHA em fluxos sensíveis;
  • WAF ou regras na borda (edge rules).

APIs e protocolos​

Por que o HTTP tem vários métodos (GET, POST, PUT, DELETE) em vez de apenas um?

Porque não se trata apenas de enviar dados, e sim de expressar a intenção da comunicação.

O método comunica semântica e contexto: o que pode ou não ser feito com o payload, se a operação é segura (safe), se é idempotente e se a resposta pode ser cacheada.

Um tech lead diz: "sempre use paginação com cursor, é sempre mais rápida que offset". Você concorda?

Não como regra absoluta.

Para datasets pequenos, a paginação por offset costuma ser melhor: permite "pular" para uma página específica e é mais simples de implementar e de usar.

Para datasets muito grandes (milhões de registros) e cenários de scroll infinito, a paginação por cursor funciona melhor, pois o custo não cresce com o número da página — o OFFSET obriga o banco a percorrer e descartar todas as linhas anteriores.

Um usuário enviou um arquivo de 50 MB, o limite da API é 5 MB e a resposta foi 500. O líder diz que a UI precisa ser corrigida. O que você responde?

A UI precisa de ajuste, mas o backend também: bons sistemas nunca confiam na entrada enviada pelo cliente.

Validações devem existir dos dois lados:

  • Frontend: pré-validação do tamanho no momento do upload, com mensagem clara antes de iniciar o envio;
  • Backend: mensagem de erro explícita e status code adequado — 413 Payload Too Large ou 400 Bad Request.
Semântica dos status

Erros 4xx indicam problema na requisição do cliente. Erros 5xx indicam falha inesperada do servidor. Devolver 500 para um limite conhecido esconde o problema real e polui a observabilidade.

Qual a diferença entre rate limit e throttling?
  • Rate limit: há um limite de requisições que o cliente pode enviar. Atingido o limite, as requisições seguintes são rejeitadas (429 Too Many Requests).
  • Throttling: a partir de certo volume, as próximas requisições ficam mais lentas, sendo enfileiradas em vez de recusadas.

Resumindo:

EstratégiaExcedenteQuando usar
Rate limitRejeitadoClientes que sabem tratar 429 e repetir
ThrottlingAtrasadoClientes que não conseguem fazer retentativa
Qual endpoint escala melhor: /users/123456789 ou /users/f495190a-e814-4409-a0ff-fe6fdfa2f795?

UUIDs, especialmente UUIDv7 ou ULID, escalam melhor em sistemas distribuídos: não exigem coordenador central de IDs, facilitam sharding entre regiões, são únicos globalmente e evitam ataques de enumeração. A desvantagem é ocupar mais espaço.

Identificadores autoincrementais oferecem escrita mais rápida e indexação mais eficiente. A desvantagem aparece em sistemas distribuídos ou na fusão (merge) de bases.

Na prática, a abordagem que costuma equilibrar os dois lados é manter o ID autoincremental como identificador interno e de relacionamento, expondo o UUID como identificador público.

Se o TCP garante a entrega, por que ainda perdemos pacotes?

O TCP não impede que pacotes sejam perdidos — ele lida com a perda.

Pacotes se perdem por congestionamento, interferência em Wi-Fi, hardware defeituoso ou sobrecarga na rede. O TCP detecta a perda e retransmite, entregando o dado de forma confiável e ordenada à aplicação.

A garantia é de entrega, não de zero perdas — e é por isso que perda de pacotes se manifesta como latência, não como corrupção de dados.

Arquitetura e sistemas distribuídos​

Se escalar horizontalmente é melhor, por que alguns sistemas ainda escalam verticalmente?

Escala horizontal significa sistema distribuído, o que traz aumento de complexidade: load balancing, estado distribuído, depuração entre hosts, observabilidade, consistência. Em muitos cenários, isso eleva o custo operacional total.

Na escala vertical, os direcionadores são simplicidade e agilidade — tanto na construção quanto na manutenção. Adicionar CPU, memória e disco costuma ser mais rápido do que montar toda a infraestrutura adjacente.

Para cargas de trabalho de baixo a médio porte, a escala vertical atende muito bem.

Se microsserviços escalam melhor, por que muitas empresas estão voltando ao monolito?

Microsserviços trazem complexidades e paradigmas para os quais muitas empresas não estão preparadas ou não têm know-how.

Microsserviços fazem sentido quando os serviços são genuinamente independentes: deploy independente, escala independente, times separados e capacidade de continuar entregando valor mesmo com uma indisponibilidade parcial.

Muitas empresas retornam ao monolito porque percebem que seus serviços são altamente dependentes entre si, compartilham o mesmo fluxo de dados e precisam constantemente de informações uns dos outros. Nesse cenário, separar tudo gera um monolito distribuído: todo o acoplamento do monolito, somado a mais latência, deploy mais complexo e problemas de observabilidade.

Por isso o monolito modular vem sendo adotado: um único deploy, mas com contextos separados em pacotes de fronteiras bem definidas.

dica

A arquitetura correta depende do problema que se deseja resolver, não de buzzwords.

Dois serviços se chamam mutuamente. Em teste funciona, em produção ocorrem deadlocks. O que está errado no design?

Os dois serviços fazem chamadas síncronas circulares (A espera por B enquanto B espera por A). Em ambiente de teste funciona pela baixa concorrência; em produção, sob carga, os pools de conexão e threads se esgotam e o ciclo trava.

Para corrigir, quebre o ciclo: torne uma das direções assíncrona, use uma fila de mensagens ou reavalie a fronteira entre os serviços — a dependência circular costuma ser sinal de que os dois pertencem ao mesmo contexto.

Por que sistemas distribuídos preferem operações idempotentes?

Porque operações idempotentes permitem retentativas seguras, sem efeito colateral: enviar a mesma cobrança duas vezes não deve gerar duplicidade.

Em sistemas distribuídos, a rede falha, requisições sofrem timeout e respostas se perdem — retentativas são inevitáveis. Portanto, idempotência não é opcional, é requisito básico.

O sistema está enviando e-mails duplicados. Como tornar o fluxo de envio idempotente?

Crie uma chave única determinística para cada envio (por exemplo, usuario_id + tipo_de_email + referencia) e trate o envio como uma máquina de estados:

Um registro em sending nunca deve ser reenviado automaticamente — é justamente o estado ambíguo.

Mantenha também o controle de confirmação do provedor de e-mail: saber se ele recebeu o pedido é o que permite decidir com segurança entre reenviar ou não em uma retentativa.

Cada microsserviço tem seu banco. O negócio pediu um dashboard em tempo real com joins entre serviços, em uma sprint, sem quebrar o isolamento. Qual o plano?

Adote uma abordagem orientada a eventos: cada microsserviço publica eventos de mudança relevantes e um modelo de leitura (read model) agrega esses dados para alimentar o dashboard.

Isso pode ser feito com um broker de mensagens (Kafka, RabbitMQ) ou com uma ferramenta de CDC (Change Data Capture), que observa os logs do banco e gera eventos automaticamente — útil quando não há tempo de alterar os serviços.

Consistência eventual é aceitável para a maioria dos dashboards.

Em uma sprint, é viável entregar um MVP com alguns gráficos e contadores, acompanhado de documentação deixando claro o modelo de consistência eventual e quais são os próximos passos.

Se containers Docker são leves, por que ainda usamos VMs?

Containers são leves porque compartilham o kernel e recursos do sistema operacional do host.

VMs fornecem emulação completa de um sistema operacional, entregando um nível de isolamento muito maior — o que importa em cenários com requisitos fortes de segurança, multi-tenancy ou necessidade de kernels diferentes.

Troubleshooting e performance​

Nos logs, GET /api/v1/products?sort=price&order=asc aparece com 10 mil requisições por minuto. Quais os próximos passos?
  • Banco de dados: verifique se existe índice na coluna price e use o query plan para confirmar que não há full table scan.
  • Cache: se não houver camada de cache, adicione — por exemplo, um Redis com a lista já ordenada e um TTL compatível com a frequência de alteração dos produtos. Isso evita que o banco refaça a mesma ordenação repetidamente.
  • Cliente: verifique se o frontend não está repetindo a mesma requisição sem necessidade (falta de debounce, re-render em excesso, ausência de cache local).
O sistema está fora do ar. Sem erros e sem logs. O que fazer?

Primeiro, confirme se ele está realmente fora: um curl em uma rota de healthcheck responde?

curl -sS -o /dev/null -w "%{http_code} %{time_total}s\n" https://api.exemplo.com/health

Se a aplicação estiver online, o problema provavelmente está antes dela: load balancer, DNS, certificado, regras de rede ou service discovery. A ausência total de logs é justamente a pista de que as requisições não estão chegando à aplicação.

Seu banco está com 100% de CPU e várias queries em timeout. Você não pode adicionar hardware e tem 15 minutos. O que faz?

Contenção imediata: finalize as queries mais pesadas, adicione índices que sejam claramente necessários e habilite cache nos caminhos mais quentes.

Depois de apagar o incêndio: identifique a consulta que mais consome recursos (pg_stat_statements ou equivalente) e atue na otimização de forma estruturada — plano de execução, índice adequado, reescrita da query ou revisão do acesso da aplicação.

O usuário diz que o sistema está lento, mas CPU, memória e rede estão normais. O que você olha em seguida?

A saturação raramente está onde o dashboard básico mostra. Pontos a verificar:

  • latência das queries no banco;
  • locks e contenção no banco;
  • índices ausentes ou grandes demais;
  • saturação do thread pool;
  • pool de conexões esgotado;
  • crescimento das filas de processamento;
  • taxa de cache hit;
  • dependências de APIs externas;
  • I/O de disco.
dica

Recursos "normais" com sistema lento quase sempre indicam espera, não trabalho. Procure onde algo está bloqueado aguardando um recurso compartilhado.

Por que, em alguns casos, recursão é mais lenta que iteração para resolver o mesmo problema?

A recursão adiciona o overhead de chamadas de função: cada chamada cria um novo stack frame, o que consome memória e traz risco de stack overflow.

A iteração usa um laço simples, com custo constante e sem esse overhead por elemento.

Um staff engineer diz: "async torna tudo mais rápido". Você concorda?

Não necessariamente. async não torna uma operação individual mais rápida.

O benefício principal é não bloquear a aplicação enquanto se espera por operações de I/O, aproveitando melhor CPU e demais recursos.

A consequência costuma ser aumento de throughput (mais trabalho concluído por unidade de tempo), ainda que a latência de uma operação isolada permaneça praticamente a mesma.

Estruturas de dados e algoritmos​

Você tem um arquivo de 100 GB e precisa encontrar o IP mais frequente, com memória limitada. Como fazer?
  • Faça streaming do arquivo, lendo linha a linha, em vez de carregá-lo na memória.
  • Mantenha um heap (ou Map) de tamanho fixo para os top N resultados mais frequentes.
  • Alternativamente, escreva totais parciais em disco durante a leitura e faça o merge depois — a estratégia clássica de external sorting / map-reduce.
  • Se um valor aproximado for suficiente, o algoritmo Count-Min Sketch resolve com uso de memória sublinear.
O que é um Bloom filter?

É uma estrutura de dados probabilística (um array de bits) usada para verificar rapidamente se um elemento possivelmente pertence a um conjunto.

Como funciona: geram-se diferentes índices do array a partir de diferentes hashes para o mesmo input. O hash produz um número grande; divide-se esse número pelo tamanho do array e o resto da divisão é a posição correspondente.

Como o resto da divisão pode gerar o mesmo índice para inputs diferentes, aplica-se variação ao input para obter posições distintas:

  • input + "a1"
  • input + "a2"
  • input + "a3"

As garantias são assimétricas:

  • garante que um elemento não existe;
  • não garante que um elemento existe.

Fluxo de uso:

consulta → bloom filter
→ bit == 0 → não existe → evita consulta no banco (rápido)
→ bit == 1 → talvez exista → consulta o banco

O "talvez exista" ocorre porque inputs diferentes podem mapear para os mesmos índices (falso positivo).

Cenário de uso: conjuntos com milhões de registros, onde a maioria das consultas é por elementos inexistentes e cada consulta ao banco custa caro.

JavaScript​

Qual a diferença entre os operadores ??=, ||= e &&=?
let a = null;
let b = 0;
let c = 'hello';

a ??= 'default'; // a = 'default' (era null)
b ||= 'fallback'; // b = 'fallback' (0 é falsy)
c &&= 'updated'; // c = 'updated' (string não vazia é truthy)
  • ??=: atribui se o valor for null ou undefined;
  • ||=: atribui se o valor for falsy (0, '', false, null, undefined, NaN);
  • &&=: atribui se o valor for truthy.
aviso

A diferença entre ??= e ||= importa quando 0 e '' são valores válidos: ||= os sobrescreve, ??= os preserva.

Qual a diferença entre call, apply e bind?

As três servem para controlar qual será o valor de this dentro da função. A diferença está em quando a função executa e como os argumentos são passados:

MétodoInvocaçãoArgumentosRetorno
callImediataListaResultado da função
applyImediataArrayResultado da função
bindPosteriorListaUma nova função
function apresentar(cidade, pais) {
console.log(`Olá, eu sou ${this.nome}, moro em ${cidade}, ${pais}`);
}

const pessoa = { nome: "Ana" };

// call: argumentos como lista, executa agora
apresentar.call(pessoa, "Brasília", "Brasil");

// apply: argumentos como array, executa agora
apresentar.apply(pessoa, ["Brasília", "Brasil"]);

// bind: retorna uma nova função, não executa
const apresentarAna = apresentar.bind(pessoa, "Brasília", "Brasil");

console.log(apresentarAna); // imprime a função, não executa
apresentarAna(); // agora sim, executa