Entrevistas Técnicas para Vagas Rust: Guia Completo de Preparação

Prepare-se para entrevistas técnicas em Rust. Perguntas comuns sobre ownership, lifetimes, traits, coding challenges, system design e dicas práticas para se destacar.

Conseguir uma entrevista para uma vaga Rust já é uma conquista. Agora, a preparação adequada é o que vai transformar essa oportunidade em uma oferta de emprego. Este guia cobre todos os aspectos de uma entrevista técnica para posições Rust, desde perguntas conceituais até coding challenges e system design.

Entrevistas para vagas Rust tendem a ser diferentes das entrevistas tradicionais de software. Além dos algoritmos e estruturas de dados comuns, os entrevistadores frequentemente exploram conceitos específicos da linguagem – ownership, lifetimes, trait system – que revelam se o candidato realmente entende o Rust ou apenas conhece a sintaxe.

Formato Típico de Entrevista Rust

A maioria das empresas segue um processo com múltiplas etapas:

EtapaFormatoDuraçãoO que Avaliam
TriagemConversa com RH/recrutador30 minFit cultural, expectativas salariais
Técnica 1Perguntas conceituais45-60 minConhecimento teórico de Rust
CodingLive coding ou take-home60-90 min / 3-5 diasHabilidade prática de programação
System DesignDesign de sistema45-60 minCapacidade arquitetural
BehavioralPerguntas comportamentais30-45 minSoft skills, trabalho em equipe
FinalConversa com liderança30 minAlinhamento de valores e objetivos

Perguntas Conceituais sobre Rust

Ownership e Borrowing

Estas são de longe as perguntas mais comuns em entrevistas Rust. Prepare respostas claras e com exemplos.

Pergunta 1: Explique o sistema de ownership do Rust. Por que ele existe?

Resposta modelo:

O sistema de ownership do Rust é um conjunto de regras verificadas em tempo de compilação que governa como a memória é gerenciada. Existem três regras fundamentais:

  1. Cada valor tem uma variável que é sua dona (owner)
  2. Só pode haver um owner por vez
  3. Quando o owner sai do escopo, o valor é descartado (dropped)

Ele existe para garantir segurança de memória sem garbage collector. Diferente de C/C++, onde o programador gerencia memória manualmente (propenso a bugs), e diferente de Java/Go, que usam GC (custo em runtime), Rust consegue ambas as coisas: segurança e performance zero-cost.

fn exemplo_ownership() {
    let s1 = String::from("olá");
    let s2 = s1;           // s1 é movido para s2
    // println!("{}", s1);  // ERRO: s1 não é mais válido
    println!("{}", s2);     // OK

    let s3 = s2.clone();   // cópia explícita (deep copy)
    println!("{} {}", s2, s3); // ambos válidos
}

Pergunta 2: Qual a diferença entre &T e &mut T? Quais são as regras?

Resposta modelo:

&T é uma referência imutável (shared reference) e &mut T é uma referência mutável (exclusive reference). As regras são:

  • Você pode ter múltiplas referências imutáveis simultaneamente OU
  • Exatamente uma referência mutável, mas não ambas ao mesmo tempo

Isso previne data races em tempo de compilação. A analogia é com leitores/escritores: muitos podem ler simultaneamente, mas se alguém está escrevendo, ninguém mais pode ler ou escrever.

fn exemplo_borrowing() {
    let mut dados = vec![1, 2, 3];

    // Múltiplas referências imutáveis: OK
    let r1 = &dados;
    let r2 = &dados;
    println!("{:?} {:?}", r1, r2);

    // Referência mutável após imutáveis não serem mais usadas: OK
    dados.push(4);

    // Referência mutável exclusiva
    let r3 = &mut dados;
    r3.push(5);
    // let r4 = &dados; // ERRO: não pode ter &T enquanto &mut T existe
}

Lifetimes

Pergunta 3: O que são lifetimes e quando você precisa anotá-las explicitamente?

Resposta modelo:

Lifetimes são a forma do compilador rastrear por quanto tempo referências são válidas. Na maioria dos casos, o compilador infere as lifetimes automaticamente (lifetime elision). Você precisa anotá-las explicitamente quando:

  1. Uma função retorna uma referência e recebe múltiplas referências como parâmetro
  2. Uma struct armazena referências
  3. O compilador não consegue determinar qual referência de entrada se relaciona com a saída
// O compilador não sabe se o retorno vem de x ou y
fn mais_longo<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

// Struct com lifetime
struct Extrator<'a> {
    texto: &'a str,
}

impl<'a> Extrator<'a> {
    fn primeira_palavra(&self) -> &'a str {
        self.texto.split_whitespace().next().unwrap_or("")
    }
}

Pergunta 4: Explique a diferença entre 'static e lifetime genérica.

Resposta modelo:

'static indica que a referência vive por toda a duração do programa. String literals, por exemplo, são &'static str porque são embutidos no binário.

Uma lifetime genérica como 'a é determinada pelo contexto de uso. Ela pode ser tão curta quanto um bloco ou tão longa quanto todo o programa.

Importante: T: 'static não significa que T é uma referência estática; significa que T não contém referências não-estáticas (ou seja, T é owned ou contém apenas referências 'static).

Traits e Enums

Pergunta 5: Qual a diferença entre generics com trait bounds e trait objects?

Resposta modelo:

Generics com trait bounds usam monomorfização (static dispatch): o compilador gera código especializado para cada tipo concreto. Trait objects (dyn Trait) usam vtable (dynamic dispatch): o tipo é resolvido em runtime.

Generics são mais rápidos (zero overhead) mas aumentam o tamanho do binário. Trait objects são mais flexíveis (permitem coleções heterogêneas) mas têm custo de indireção.

// Static dispatch: monomorphized, inline possível
fn imprimir_static(item: &impl std::fmt::Display) {
    println!("{}", item);
}

// Dynamic dispatch: vtable, mais flexível
fn imprimir_dynamic(item: &dyn std::fmt::Display) {
    println!("{}", item);
}

// Coleção heterogênea: precisa de trait object
fn colecao_heterogenea() {
    let items: Vec<Box<dyn std::fmt::Display>> = vec![
        Box::new(42),
        Box::new("texto"),
        Box::new(3.14),
    ];
    for item in &items {
        println!("{}", item);
    }
}

Pergunta 6: Quando usar enum vs. trait object para polimorfismo?

Resposta modelo:

Use enum quando o conjunto de variantes é fixo e conhecido em compile time. Enums permitem pattern matching exaustivo e são stack-allocated.

Use trait object quando o conjunto de tipos é aberto/extensível, ou quando os tipos vêm de crates externas. Trait objects requerem heap allocation (via Box) e não permitem pattern matching.

Regra geral: se você controla todos os tipos e eles são poucos, use enum. Se precisa de extensibilidade, use trait.

Error Handling

Pergunta 7: Compare as abordagens de tratamento de erro em Rust.

Resposta modelo:

Rust tem várias abordagens, cada uma para um contexto diferente:

AbordagemQuando UsarExemplo
Result<T, E>Erros recuperáveis em bibliotecasfn parse() -> Result<i32, ParseError>
Option<T>Ausência de valorfn buscar() -> Option<Usuario>
panic!Bugs irrecuperáveisassert!(index < len)
? operadorPropagar erros na call chainlet dados = ler_arquivo()?;
anyhowAplicações (error boxing)fn main() -> anyhow::Result<()>
thiserrorBibliotecas (error types)#[derive(thiserror::Error)]
use thiserror::Error;

#[derive(Error, Debug)]
enum ServiceError {
    #[error("Usuário não encontrado: {0}")]
    UsuarioNaoEncontrado(i64),

    #[error("Erro de banco de dados")]
    Database(#[from] sqlx::Error),

    #[error("Validação falhou: {0}")]
    Validacao(String),
}

fn buscar_usuario(id: i64) -> Result<Usuario, ServiceError> {
    if id <= 0 {
        return Err(ServiceError::Validacao("ID deve ser positivo".into()));
    }
    // ... implementação
    Ok(Usuario { id, nome: "teste".into() })
}

Coding Challenges

Desafio 1: Implementar uma Cache LRU

Este é um desafio clássico que testa conhecimento de coleções, ponteiros e design de API.

use std::collections::HashMap;
use std::collections::VecDeque;

struct LruCache<K, V> {
    capacidade: usize,
    mapa: HashMap<K, V>,
    ordem: VecDeque<K>,
}

impl<K: Eq + std::hash::Hash + Clone, V> LruCache<K, V> {
    fn new(capacidade: usize) -> Self {
        LruCache {
            capacidade,
            mapa: HashMap::with_capacity(capacidade),
            ordem: VecDeque::with_capacity(capacidade),
        }
    }

    fn get(&mut self, chave: &K) -> Option<&V> {
        if self.mapa.contains_key(chave) {
            // Move para o final (mais recente)
            self.ordem.retain(|k| k != chave);
            self.ordem.push_back(chave.clone());
            self.mapa.get(chave)
        } else {
            None
        }
    }

    fn put(&mut self, chave: K, valor: V) {
        if self.mapa.contains_key(&chave) {
            self.ordem.retain(|k| k != &chave);
        } else if self.mapa.len() >= self.capacidade {
            // Remove o menos recente
            if let Some(antiga) = self.ordem.pop_front() {
                self.mapa.remove(&antiga);
            }
        }
        self.ordem.push_back(chave.clone());
        self.mapa.insert(chave, valor);
    }

    fn len(&self) -> usize {
        self.mapa.len()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn teste_lru_basico() {
        let mut cache = LruCache::new(2);
        cache.put("a", 1);
        cache.put("b", 2);
        assert_eq!(cache.get(&"a"), Some(&1));

        cache.put("c", 3); // remove "b" (menos recente)
        assert_eq!(cache.get(&"b"), None);
        assert_eq!(cache.get(&"c"), Some(&3));
    }
}

Desafio 2: Flatten de Iterador Aninhado

struct FlattenIter<I>
where
    I: Iterator,
    I::Item: IntoIterator,
{
    outer: I,
    inner: Option<<I::Item as IntoIterator>::IntoIter>,
}

impl<I> FlattenIter<I>
where
    I: Iterator,
    I::Item: IntoIterator,
{
    fn new(iter: I) -> Self {
        FlattenIter {
            outer: iter,
            inner: None,
        }
    }
}

impl<I> Iterator for FlattenIter<I>
where
    I: Iterator,
    I::Item: IntoIterator,
{
    type Item = <I::Item as IntoIterator>::Item;

    fn next(&mut self) -> Option<Self::Item> {
        loop {
            if let Some(ref mut inner) = self.inner {
                if let Some(item) = inner.next() {
                    return Some(item);
                }
            }
            match self.outer.next() {
                Some(next_inner) => {
                    self.inner = Some(next_inner.into_iter());
                }
                None => return None,
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn teste_flatten() {
        let dados = vec![vec![1, 2], vec![3], vec![4, 5, 6]];
        let resultado: Vec<i32> = FlattenIter::new(dados.into_iter()).collect();
        assert_eq!(resultado, vec![1, 2, 3, 4, 5, 6]);
    }

    #[test]
    fn teste_flatten_vazio() {
        let dados: Vec<Vec<i32>> = vec![vec![], vec![], vec![]];
        let resultado: Vec<i32> = FlattenIter::new(dados.into_iter()).collect();
        assert!(resultado.is_empty());
    }
}

Desafio 3: Thread-Safe Counter com Trait

use std::sync::{Arc, Mutex, atomic::{AtomicU64, Ordering}};

trait Counter: Send + Sync {
    fn incrementar(&self);
    fn valor(&self) -> u64;
    fn resetar(&self);
}

// Implementação com Mutex
struct MutexCounter {
    valor: Mutex<u64>,
}

impl MutexCounter {
    fn new() -> Self {
        MutexCounter {
            valor: Mutex::new(0),
        }
    }
}

impl Counter for MutexCounter {
    fn incrementar(&self) {
        let mut guard = self.valor.lock().unwrap();
        *guard += 1;
    }

    fn valor(&self) -> u64 {
        *self.valor.lock().unwrap()
    }

    fn resetar(&self) {
        *self.valor.lock().unwrap() = 0;
    }
}

// Implementação com Atomic (mais performática)
struct AtomicCounter {
    valor: AtomicU64,
}

impl AtomicCounter {
    fn new() -> Self {
        AtomicCounter {
            valor: AtomicU64::new(0),
        }
    }
}

impl Counter for AtomicCounter {
    fn incrementar(&self) {
        self.valor.fetch_add(1, Ordering::Relaxed);
    }

    fn valor(&self) -> u64 {
        self.valor.load(Ordering::Relaxed)
    }

    fn resetar(&self) {
        self.valor.store(0, Ordering::Relaxed);
    }
}

// Teste comparativo
fn benchmark_counter(counter: Arc<dyn Counter>, num_threads: usize, ops_per_thread: u64) {
    let mut handles = vec![];

    for _ in 0..num_threads {
        let c = Arc::clone(&counter);
        handles.push(std::thread::spawn(move || {
            for _ in 0..ops_per_thread {
                c.incrementar();
            }
        }));
    }

    for handle in handles {
        handle.join().unwrap();
    }

    let esperado = (num_threads as u64) * ops_per_thread;
    assert_eq!(counter.valor(), esperado);
}

Desafio 4: Parser de Expressões Simples

#[derive(Debug, PartialEq)]
enum Token {
    Numero(f64),
    Mais,
    Menos,
    Vezes,
    Dividir,
    AbreParentese,
    FechaParentese,
}

fn tokenizar(expressao: &str) -> Result<Vec<Token>, String> {
    let mut tokens = Vec::new();
    let mut chars = expressao.chars().peekable();

    while let Some(&c) = chars.peek() {
        match c {
            ' ' => { chars.next(); }
            '+' => { tokens.push(Token::Mais); chars.next(); }
            '-' => { tokens.push(Token::Menos); chars.next(); }
            '*' => { tokens.push(Token::Vezes); chars.next(); }
            '/' => { tokens.push(Token::Dividir); chars.next(); }
            '(' => { tokens.push(Token::AbreParentese); chars.next(); }
            ')' => { tokens.push(Token::FechaParentese); chars.next(); }
            '0'..='9' | '.' => {
                let mut num_str = String::new();
                while let Some(&c) = chars.peek() {
                    if c.is_ascii_digit() || c == '.' {
                        num_str.push(c);
                        chars.next();
                    } else {
                        break;
                    }
                }
                let num: f64 = num_str.parse()
                    .map_err(|_| format!("Número inválido: {}", num_str))?;
                tokens.push(Token::Numero(num));
            }
            _ => return Err(format!("Caractere inesperado: {}", c)),
        }
    }

    Ok(tokens)
}

fn avaliar(expressao: &str) -> Result<f64, String> {
    let tokens = tokenizar(expressao)?;
    let mut pos = 0;
    let resultado = parse_expressao(&tokens, &mut pos)?;
    if pos != tokens.len() {
        return Err("Tokens inesperados no final".to_string());
    }
    Ok(resultado)
}

fn parse_expressao(tokens: &[Token], pos: &mut usize) -> Result<f64, String> {
    let mut resultado = parse_termo(tokens, pos)?;

    while *pos < tokens.len() {
        match tokens[*pos] {
            Token::Mais => {
                *pos += 1;
                resultado += parse_termo(tokens, pos)?;
            }
            Token::Menos => {
                *pos += 1;
                resultado -= parse_termo(tokens, pos)?;
            }
            _ => break,
        }
    }

    Ok(resultado)
}

fn parse_termo(tokens: &[Token], pos: &mut usize) -> Result<f64, String> {
    let mut resultado = parse_fator(tokens, pos)?;

    while *pos < tokens.len() {
        match tokens[*pos] {
            Token::Vezes => {
                *pos += 1;
                resultado *= parse_fator(tokens, pos)?;
            }
            Token::Dividir => {
                *pos += 1;
                let divisor = parse_fator(tokens, pos)?;
                if divisor == 0.0 {
                    return Err("Divisão por zero".to_string());
                }
                resultado /= divisor;
            }
            _ => break,
        }
    }

    Ok(resultado)
}

fn parse_fator(tokens: &[Token], pos: &mut usize) -> Result<f64, String> {
    if *pos >= tokens.len() {
        return Err("Expressão incompleta".to_string());
    }

    match tokens[*pos] {
        Token::Numero(n) => {
            *pos += 1;
            Ok(n)
        }
        Token::AbreParentese => {
            *pos += 1;
            let resultado = parse_expressao(tokens, pos)?;
            if *pos >= tokens.len() || tokens[*pos] != Token::FechaParentese {
                return Err("Parêntese não fechado".to_string());
            }
            *pos += 1;
            Ok(resultado)
        }
        _ => Err(format!("Token inesperado: {:?}", tokens[*pos])),
    }
}

System Design para Posições Rust

O que é Esperado

Em entrevistas de system design para vagas Rust, os entrevistadores querem ver que você:

  1. Pensa em performance: escolhe estruturas de dados e algoritmos adequados
  2. Considera concorrência: sabe usar async, threads e primitivas de sincronização
  3. Prioriza segurança: tipo system, error handling, validação
  4. Conhece o ecossistema: sabe quais crates usar para cada problema

Exemplo: Design de um Rate Limiter

Requisitos:

  • Limitar requisições por IP
  • Janela deslizante de 1 minuto
  • Configurável (X requisições por minuto)
  • Thread-safe para uso em web server
use std::collections::HashMap;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::sync::RwLock;

struct RateLimiter {
    limite: u32,
    janela: Duration,
    registros: Arc<RwLock<HashMap<String, Vec<Instant>>>>,
}

impl RateLimiter {
    fn new(limite: u32, janela: Duration) -> Self {
        RateLimiter {
            limite,
            janela,
            registros: Arc::new(RwLock::new(HashMap::new())),
        }
    }

    async fn permitir(&self, chave: &str) -> bool {
        let agora = Instant::now();
        let mut registros = self.registros.write().await;

        let timestamps = registros
            .entry(chave.to_string())
            .or_insert_with(Vec::new);

        // Remove timestamps fora da janela
        timestamps.retain(|&t| agora.duration_since(t) < self.janela);

        if timestamps.len() < self.limite as usize {
            timestamps.push(agora);
            true
        } else {
            false
        }
    }

    async fn limpar_expirados(&self) {
        let agora = Instant::now();
        let mut registros = self.registros.write().await;
        registros.retain(|_, timestamps| {
            timestamps.retain(|&t| agora.duration_since(t) < self.janela);
            !timestamps.is_empty()
        });
    }
}

Pontos para discutir na entrevista:

  • Escalabilidade: para múltiplas instâncias, usar Redis em vez de memória local
  • Precisão: janela fixa vs. janela deslizante vs. token bucket
  • Limpeza: background task para remover registros expirados
  • Distribuição: algoritmo para rate limiting distribuído
  • Configuração: limites diferentes por endpoint, por tipo de usuário

Take-Home Projects

O que as Empresas Esperam

Um take-home típico para vaga Rust envolve:

  • Prazo: 3-5 dias (dedique 4-8 horas no total)
  • Escopo: aplicação funcional com escopo bem definido
  • Qualidade: código limpo, testado, documentado

Checklist para um Take-Home Exemplar

## Antes de Enviar

### Código
- [ ] Código compila sem warnings (`cargo clippy`)
- [ ] Formatação padronizada (`cargo fmt`)
- [ ] Sem dependências desnecessárias
- [ ] Tratamento de erros adequado (sem unwrap em produção)
- [ ] Nomes descritivos em variáveis e funções

### Testes
- [ ] Testes unitários para lógica core
- [ ] Testes de integração para APIs
- [ ] Todos passando (`cargo test`)
- [ ] Edge cases cobertos

### Documentação
- [ ] README com instruções de setup e uso
- [ ] Decisões de design documentadas
- [ ] API documentada (se aplicável)
- [ ] Exemplos de uso

### Git
- [ ] Commits atômicos e descritivos
- [ ] Histórico limpo (sem "fix typo" em sequência)
- [ ] .gitignore configurado

Perguntas Comportamentais

Perguntas Comuns e Como Responder

“Conte sobre um bug difícil que você resolveu.”

Use o framework STAR (Situação, Tarefa, Ação, Resultado):

Situação: “Tínhamos um serviço em Rust que apresentava deadlocks esporadicamente em produção.”

Tarefa: “Eu precisava identificar e corrigir o problema sem interromper o serviço.”

Ação: “Adicionei tracing estruturado para mapear a sequência de aquisição de locks. Descobri que dois handlers adquiriam dois mutexes em ordem diferente. Refatorei para usar um único lock abrangente e depois otimizei com RwLock.”

Resultado: “Os deadlocks desapareceram completamente. Documentei o padrão para o time evitar no futuro e adicionamos um clippy lint customizado.”

“Por que Rust?”

Destaque benefícios concretos que se alinham com o produto da empresa. Por exemplo:

  • “Segurança de memória sem GC é essencial para nosso domínio de [low-latency/embedded/etc.]”
  • “O type system nos permite refatorar com confiança em um codebase grande”
  • “A performance nos permite fazer mais com menos infraestrutura”

“Como você lida com a curva de aprendizado do Rust na equipe?”

“Organizei sessões semanais de pair programming focadas em conceitos de ownership. Criei um documento interno com padrões comuns e os erros de compilação mais frequentes com suas soluções. Em 2 meses, os novos membros já estavam produtivos.”

Dicas de Live Coding

Antes da Entrevista

  1. Pratique com timer: resolva problemas no LeetCode em Rust com limite de tempo
  2. Configure seu ambiente: tenha VS Code com rust-analyzer pronto
  3. Prepare templates: snippets para testes, error types, structs comuns
  4. Domine o básico: Vec, HashMap, String, Option, Result devem fluir naturalmente

Durante a Entrevista

  1. Pense em voz alta: explique seu raciocínio antes de escrever código
  2. Comece com a assinatura: defina tipos de entrada e saída primeiro
  3. Escreva testes antes: mostra maturidade e ajuda a pensar em edge cases
  4. Compile frequentemente: não espere terminar tudo para compilar
  5. Peça clarificação: se algo não está claro, pergunte
  6. Não entre em pânico com erros do compilador: leia a mensagem com calma
// Modelo mental para resolver problemas em entrevista:

// 1. Entender o problema (repita em suas palavras)
// 2. Definir tipos de entrada/saída
fn resolver(input: &[i32]) -> Vec<i32> {
    todo!()
}

// 3. Escrever testes primeiro
#[test]
fn teste_basico() {
    assert_eq!(resolver(&[1, 2, 3]), vec![3, 2, 1]);
}

#[test]
fn teste_vazio() {
    assert_eq!(resolver(&[]), vec![]);
}

// 4. Implementar solução simples (brute force)
// 5. Otimizar se necessário
// 6. Discutir complexidade (tempo e espaço)

Erros Comuns em Entrevistas

ErroComo Evitar
Esquecer de & ao iterarUse for item in &vec ou for &item in &vec
Confundir String e &strFunções recebem &str, structs armazenam String
Usar unwrap() sem justificarUse ? ou trate o erro explicitamente
Ignorar edge casesTeste com input vazio, um elemento, negativos
Código sem testesSempre escreva pelo menos 2-3 testes
Over-engineeringComece simples, otimize depois

Preparação Final: Checklist da Semana Anterior

7 Dias Antes

  • Revisar ownership, borrowing e lifetimes
  • Resolver 2-3 problemas de coding challenge por dia
  • Reler documentação das crates que a empresa usa
  • Preparar respostas para perguntas comportamentais
  • Pesquisar sobre a empresa e seus produtos Rust

1 Dia Antes

  • Testar microfone, câmera e conexão (se remota)
  • Ter VS Code com rust-analyzer funcionando
  • Preparar editor de texto para anotações
  • Descansar bem e dormir cedo

No Dia

  • Chegar/conectar 5 minutos antes
  • Ter água por perto
  • Respirar fundo e lembrar: você foi convidado porque tem potencial
  • Se travar, pense em voz alta e peça ajuda

Conclusão

Entrevistas técnicas são uma habilidade que melhora com prática. O conhecimento técnico é a base, mas a comunicação clara, a capacidade de raciocinar sob pressão e a humildade de pedir esclarecimento são igualmente importantes.

Lembre-se: o entrevistador não espera perfeição. Ele quer ver como você pensa, como aborda problemas e como se comporta quando enfrenta dificuldades. Mostre seu processo de raciocínio, demonstre conhecimento do Rust idiomático e, acima de tudo, seja genuíno.

A comunidade Rust Brasil tem grupos onde membros praticam mock interviews. Aproveite esse recurso. Quanto mais entrevistas simuladas você fizer, mais natural será a real.

Boa sorte na sua próxima entrevista. Você está mais preparado do que imagina.