Desafios de entrevista em Go · parte 3 de 11
Nubank - Authorizer
avançadoPublicada em · 5 min de leitura
Dados da empresa e da vaga
- Empresa: Nubank (Nu Holdings Ltd.)
- Sede: São Paulo, Brasil (com polos em Buenos Aires, Cidade do México, Berlim e contratação remota global)
- Perfil da vaga: Senior Backend Software Engineer — Core Banking & Authorizer Systems
- Modelo de trabalho: Remoto internacional / Híbrido
- Faixa salarial estimada da vaga:
- Sênior: R$ 26.000 a R$ 38.000/mês / $75.000 a $120.000 USD/ano + Equity (RSU)
- Staff / Principal: R$ 38.000 a R$ 55.000+/mês / $120.000 a $180.000+ USD/ano
- Stack principal do time: Go, Clojure, Kafka, AWS, Docker, Kubernetes, Datomic
Contexto do desafio técnico
O Authorizer (Autorizador de Transações) é o desafio prático de arquitetura e código mais representativo do core bancário do Nubank.
O objetivo é implementar uma aplicação que processe um fluxo contínuo de operações via stdin simulando um autorizador de cartão de crédito em tempo real. A aplicação deve manter o estado da conta em memória, validar regras de negócio rígidas (como limite disponível, cartões bloqueados, transações em alta frequência e transações duplicadas) e imprimir o estado atualizado da conta e as eventuais violações no stdout.
O avaliador do Nubank analisa:
- Composição e Clean Architecture: Código desacoplado, sem acoplamento entre regras de validação.
- Imutabilidade e gestão de estado: Funções puras que transformam o estado de forma segura e determinística.
- Modelagem de regras com padrão Strategy / Chain of Responsibility: Facilidade de adicionar novas regras de negócio sem alterar o fluxo central do autorizador.
- Testes automatizados exaustivos: Testes unitários para cada regra isolada e testes de integração de ponta a ponta.
Formato de entrada e saída
1. Entrada (stdin)
O programa recebe uma sequência de linhas contendo objetos JSON. Existem dois tipos de operações:
A. Criação de Conta (account)
{"account": {"active-card": true, "available-limit": 1000}}
B. Autorização de Transação (transaction)
{"transaction": {"merchant": "Uber", "amount": 25, "time": "2026-02-13T10:00:00.000Z"}}
2. Saída (stdout)
Para cada linha recebida, o programa deve emitir o estado consolidado da conta e um array com os nomes das violações de regras identificadas:
Sucesso (sem violações):
{"account": {"active-card": true, "available-limit": 975}, "violations": []}
Rejeição (com violações):
{"account": {"active-card": true, "available-limit": 1000}, "violations": ["insufficient-limit"]}
Regras de negócio e validações
Uma transação pode violar múltiplas regras simultaneamente. Nesse caso, a saída deve listar todas as violações ocorridas na ordem de validação, e o limite da conta não deve ser debitado.
1. Inicialização de conta
account-not-initialized: Se uma transação for processada antes da inicialização de uma conta.account-already-initialized: Se uma operação de criação de conta for executada quando a conta já foi inicializada anteriormente.
2. Estado do cartão e limite de crédito
card-not-active: Uma transação não pode ser aprovada se o cartão estiver inativo ("active-card": false).insufficient-limit: O valor da transação não pode exceder o limite disponível (available-limit) no momento.
3. Detecção de fraudes e frequência (Janela de Tempo Móvel de 2 Minutos)
high-frequency-small-interval: Não devem existir mais de 3 transações aprovadas em um intervalo inferior a 2 minutos.doubled-transaction: Não deve existir mais de 1 transação aprovada com o mesmo comerciante (merchant) e o mesmo valor (amount) em um intervalo inferior a 2 minutos.
Observação sobre a janela temporal: Apenas transações aprovadas anteriormente entram no histórico que afeta a contagem de frequência e duplicidade. Transações rejeitadas não afetam as regras futuras.
Casos de teste oficiais da avaliação
Cenário 1: Inicialização e transações bem-sucedidas
Entrada (stdin):
{"account": {"active-card": true, "available-limit": 100}}
{"transaction": {"merchant": "Burger King", "amount": 20, "time": "2026-02-13T10:00:00.000Z"}}
{"transaction": {"merchant": "Hering", "amount": 30, "time": "2026-02-13T10:00:10.000Z"}}
Saída esperada (stdout):
{"account": {"active-card": true, "available-limit": 100}, "violations": []}
{"account": {"active-card": true, "available-limit": 80}, "violations": []}
{"account": {"active-card": true, "available-limit": 50}, "violations": []}
Cenário 2: Cartão inativo e limite insuficiente
Entrada (stdin):
{"account": {"active-card": false, "available-limit": 100}}
{"transaction": {"merchant": "Burger King", "amount": 150, "time": "2026-02-13T10:00:00.000Z"}}
Saída esperada (stdout):
{"account": {"active-card": false, "available-limit": 100}, "violations": []}
{"account": {"active-card": false, "available-limit": 100}, "violations": ["card-not-active", "insufficient-limit"]}
Cenário 3: Alta frequência e transação duplicada
Entrada (stdin):
{"account": {"active-card": true, "available-limit": 500}}
{"transaction": {"merchant": "Uber", "amount": 20, "time": "2026-02-13T10:00:00.000Z"}}
{"transaction": {"merchant": "Uber", "amount": 20, "time": "2026-02-13T10:00:30.000Z"}}
{"transaction": {"merchant": "Uber", "amount": 20, "time": "2026-02-13T10:01:00.000Z"}}
{"transaction": {"merchant": "Uber", "amount": 20, "time": "2026-02-13T10:01:30.000Z"}}
Saída esperada (stdout):
{"account": {"active-card": true, "available-limit": 500}, "violations": []}
{"account": {"active-card": true, "available-limit": 480}, "violations": []}
{"account": {"active-card": true, "available-limit": 480}, "violations": ["doubled-transaction"]}
{"account": {"active-card": true, "available-limit": 480}, "violations": ["doubled-transaction"]}
{"account": {"active-card": true, "available-limit": 480}, "violations": ["high-frequency-small-interval", "doubled-transaction"]}
Arquitetura recomendada em Go
cmd/
authorizer/
main.go # Loop de leitura e escrita JSON
internal/
domain/
account.go # Entidades Account e Transaction
violations.go # Constantes tipadas de violação
authorizer/
service.go # Orquestrador de transações
validator.go # Interface Validator
rules/
initialized.go # Validação de conta existente
active_card.go # Validação de cartão ativo
limit.go # Validação de saldo disponível
frequency.go # Validação de janela de 2 minutos (alta frequência)
doubled.go # Validação de janela de 2 minutos (mesmo merchant/amount)
Design Pattern idiomático de Validações em Go:
package authorizer
import (
"time"
)
type Violation string
const (
AccountNotInitialized Violation = "account-not-initialized"
AccountAlreadyInitialized Violation = "account-already-initialized"
CardNotActive Violation = "card-not-active"
InsufficientLimit Violation = "insufficient-limit"
HighFrequencySmallInterval Violation = "high-frequency-small-interval"
DoubledTransaction Violation = "doubled-transaction"
)
type Account struct {
ActiveCard bool `json:"active-card"`
AvailableLimit int `json:"available-limit"`
}
type Transaction struct {
Merchant string `json:"merchant"`
Amount int `json:"amount"`
Time time.Time `json:"time"`
}
type Validator interface {
Validate(account *Account, history []Transaction, current Transaction) *Violation
}
type Service struct {
account *Account
history []Transaction
validators []Validator
initialized bool
}
func (s *Service) ProcessTransaction(t Transaction) ([]Violation, *Account) {
if !s.initialized {
return []Violation{AccountNotInitialized}, nil
}
var violations []Violation
for _, v := range s.validators {
if violation := v.Validate(s.account, s.history, t); violation != nil {
violations = append(violations, *violation)
}
}
if len(violations) == 0 {
s.account.AvailableLimit -= t.Amount
s.history = append(s.history, t)
}
return violations, s.account
}
O que os avaliadores do Nubank procuram no código
- Separação de responsabilidades: Não colocar a lógica de cálculo de tempo ou limite dentro do loop de
main.go. Cada regra deve ser uma struct que implementa uma interface simples. - Manipulação de datas com
time.Time: Uso correto detime.Parse(time.RFC3339Nano, ...)e métodos comot.Sub()para calcular a janela de 2 minutos (2 * time.Minute). - Testes parametrizados: Testar as regras de frequência e duplicação com baterias de transações em milissegundos para garantir que o algoritmo não apresente erros de arredondamento de timestamp.