Preparar-se para entrevistas tecnicas com Kotlin exige conhecer nao apenas a sintaxe, mas tambem os conceitos por tras das decisoes de design da linguagem. Neste guia, compilamos as perguntas mais frequentes em processos seletivos, com respostas detalhadas e exemplos praticos.

Perguntas sobre Fundamentos

1. Qual a diferenca entre val e var?

Esta e quase sempre a primeira pergunta. A resposta vai alem do basico:

val nome = "Kotlin"   // Referencia imutavel (similar a final em Java)
var contador = 0      // Referencia mutavel

// Importante: val nao significa que o OBJETO e imutavel
val lista = mutableListOf(1, 2, 3)
lista.add(4)  // Funciona! A referencia nao muda, mas o conteudo sim

// Para imutabilidade real, use colecoes imutaveis
val listaImutavel = listOf(1, 2, 3)
// listaImutavel.add(4)  // Nao compila!

O ponto chave e explicar que val torna a referencia imutavel, nao o objeto em si. Para imutabilidade profunda, voce precisa usar tipos imutaveis.

2. Explique Null Safety em Kotlin

// Tipos nullable vs non-nullable
var nome: String = "Kotlin"   // Nao pode ser null
var apelido: String? = null    // Pode ser null

// Operadores de null safety
val tamanho1: Int? = apelido?.length        // Safe call
val tamanho2: Int = apelido?.length ?: 0    // Elvis operator
val tamanho3: Int = apelido!!.length        // Not-null assertion (perigoso)

// Smart cast
fun processar(valor: String?) {
    if (valor != null) {
        // Aqui, valor e automaticamente String (nao String?)
        println(valor.length)
    }
}

// let para trabalhar com nullables
apelido?.let { nomeNaoNulo ->
    println("Apelido: $nomeNaoNulo")
    println("Tamanho: ${nomeNaoNulo.length}")
}

Mencione que null safety elimina NullPointerException em tempo de compilacao, e que o operador !! deve ser evitado em codigo de producao.

3. O que sao Data Classes?

data class Usuario(
    val id: String,
    val nome: String,
    val email: String,
    val ativo: Boolean = true
)

// O compilador gera automaticamente:
// - equals() e hashCode() baseados nas propriedades do construtor primario
// - toString() formatado
// - copy() para criar copias com modificacoes
// - componentN() para destructuring

val user = Usuario("1", "Ana", "ana@email.com")
val copia = user.copy(nome = "Maria")

// Destructuring
val (id, nome, email) = user
println("$nome ($email)")

// Util em colecoes
val usuarios = listOf(user, copia)
val nomes = usuarios.map { (_, nome, _) -> nome }

4. Sealed Classes vs Enum Classes

// Enum - Conjunto fixo de constantes
enum class Cor { VERMELHO, VERDE, AZUL }

// Sealed class - Hierarquia restrita com dados diferentes por tipo
sealed class Resultado {
    data class Sucesso(val dados: String) : Resultado()
    data class Erro(val mensagem: String, val codigo: Int) : Resultado()
    data object Carregando : Resultado()
}

fun tratar(resultado: Resultado): String {
    return when (resultado) {
        is Resultado.Sucesso -> "OK: ${resultado.dados}"
        is Resultado.Erro -> "Erro ${resultado.codigo}: ${resultado.mensagem}"
        is Resultado.Carregando -> "Aguarde..."
    }
    // O when e EXAUSTIVO - o compilador verifica todos os casos
}

A diferenca chave: enums tem instancias fixas e identicas em estrutura, sealed classes permitem subtipos diferentes com dados diferentes.

Perguntas sobre Coroutines

5. O que sao Coroutines e como funcionam?

// Coroutines sao threads leves gerenciadas pelo Kotlin
// Uma suspend function so pode ser chamada de outra suspend function
// ou de um coroutine builder

suspend fun buscarDados(): List<String> {
    delay(1000) // Suspende sem bloquear a thread
    return listOf("dado1", "dado2")
}

// Coroutine builders
fun exemplo(scope: CoroutineScope) {
    // launch - Fire and forget, retorna Job
    scope.launch {
        val dados = buscarDados()
        processar(dados)
    }

    // async - Retorna Deferred com resultado
    scope.launch {
        val deferred = async { buscarDados() }
        val dados = deferred.await()
    }

    // runBlocking - Bloqueia a thread atual (usar apenas em testes/main)
    runBlocking {
        val dados = buscarDados()
    }
}

6. Explique Dispatchers e Structured Concurrency

// Dispatchers controlam em qual thread a coroutine executa
suspend fun exemploDispatchers() {
    // Dispatchers.Main - Thread principal (UI no Android)
    // Dispatchers.IO - Otimizado para I/O (rede, disco)
    // Dispatchers.Default - Otimizado para CPU

    withContext(Dispatchers.IO) {
        // Operacoes de rede ou banco de dados aqui
    }

    withContext(Dispatchers.Default) {
        // Calculos pesados aqui
    }
}

// Structured concurrency garante que coroutines filhas
// sao canceladas quando o escopo pai e cancelado
suspend fun buscarTudo() = coroutineScope {
    val usuarios = async { api.getUsuarios() }
    val produtos = async { api.getProdutos() }

    // Se api.getUsuarios() falhar, api.getProdutos() e cancelado automaticamente
    Pair(usuarios.await(), produtos.await())
}

7. Qual a diferenca entre Flow, StateFlow e SharedFlow?

// Flow - Stream frio (cold), emite valores sob demanda
fun temperaturas(): Flow<Double> = flow {
    while (true) {
        emit(sensor.lerTemperatura())
        delay(1000)
    }
}

// StateFlow - Sempre tem um valor atual, hot stream
class ContadorViewModel : ViewModel() {
    private val _contagem = MutableStateFlow(0)
    val contagem: StateFlow<Int> = _contagem.asStateFlow()

    fun incrementar() {
        _contagem.update { it + 1 }
    }
}

// SharedFlow - Hot stream sem valor inicial, configuracel
class EventBus {
    private val _eventos = MutableSharedFlow<Evento>(
        replay = 0,
        extraBufferCapacity = 64,
        onBufferOverflow = BufferOverflow.DROP_OLDEST
    )
    val eventos: SharedFlow<Evento> = _eventos.asSharedFlow()

    suspend fun emitir(evento: Evento) {
        _eventos.emit(evento)
    }
}

Perguntas sobre Funcoes

8. Extension Functions e Scope Functions

// Extension functions adicionam funcionalidades a tipos existentes
fun String.isValidEmail(): Boolean {
    return matches(Regex("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$"))
}

val valido = "user@email.com".isValidEmail() // true

// Scope functions: let, run, with, apply, also
data class Configuracao(
    var host: String = "",
    var porta: Int = 0,
    var timeout: Long = 0
)

// apply - Configura um objeto e retorna ele mesmo
val config = Configuracao().apply {
    host = "localhost"
    porta = 8080
    timeout = 5000
}

// let - Transforma valor, util com nullables
val resultado = texto?.let { it.trim().uppercase() }

// also - Executa acao lateral, retorna o objeto original
val lista = mutableListOf(1, 2, 3).also {
    println("Lista criada com ${it.size} elementos")
}

// run - Executa bloco no contexto do objeto, retorna resultado
val tamanhoFormatado = texto.run {
    trim()
    "Texto tem $length caracteres"
}

9. Higher-Order Functions e Lambdas

// Higher-order function recebe ou retorna funcoes
fun <T> List<T>.customFilter(
    predicate: (T) -> Boolean
): List<T> {
    val resultado = mutableListOf<T>()
    for (item in this) {
        if (predicate(item)) {
            resultado.add(item)
        }
    }
    return resultado
}

// Uso
val pares = listOf(1, 2, 3, 4, 5).customFilter { it % 2 == 0 }

// inline - Evita overhead de criacao de objetos lambda
inline fun <T> medirTempo(bloco: () -> T): Pair<T, Long> {
    val inicio = System.currentTimeMillis()
    val resultado = bloco()
    val duracao = System.currentTimeMillis() - inicio
    return Pair(resultado, duracao)
}

Perguntas de Design e Arquitetura

10. Como voce estrutura um projeto Kotlin?

// Clean Architecture tipica
// domain/ - Regras de negocio puras
//   model/
//   repository/ (interfaces)
//   usecase/
// data/ - Implementacoes de acesso a dados
//   repository/ (implementacoes)
//   remote/
//   local/
// presentation/ - UI e ViewModels
//   screen/
//   viewmodel/
//   component/

// Exemplo de Use Case
class TransferirDinheiroUseCase(
    private val contaRepository: ContaRepository,
    private val transacaoRepository: TransacaoRepository
) {
    suspend operator fun invoke(
        origem: ContaId,
        destino: ContaId,
        valor: Money
    ): Result<Transacao> = runCatching {
        require(valor.isPositive()) { "Valor deve ser positivo" }

        val contaOrigem = contaRepository.buscar(origem)
            ?: throw ContaNaoEncontradaException(origem)

        require(contaOrigem.saldo >= valor) { "Saldo insuficiente" }

        val transacao = Transacao(
            origem = origem,
            destino = destino,
            valor = valor,
            timestamp = Instant.now()
        )

        contaRepository.debitar(origem, valor)
        contaRepository.creditar(destino, valor)
        transacaoRepository.salvar(transacao)
    }
}

Dicas para a Entrevista

  1. Explique seu raciocinio: Nao apenas de a resposta, explique por que
  2. Mencione trade-offs: Mostra maturidade tecnica
  3. Use exemplos reais: Relacione conceitos com situacoes praticas
  4. Admita quando nao sabe: E muito melhor que inventar uma resposta
  5. Faca perguntas: Mostra interesse genuino na empresa e no projeto

Conclusao

Preparar-se para entrevistas Kotlin vai alem de decorar respostas. O objetivo e demonstrar que voce entende os conceitos profundamente e sabe aplica-los em cenarios reais. Pratique escrevendo codigo, revise os conceitos fundamentais e mantenha-se atualizado com as novidades da linguagem. Com preparacao adequada, voce estara confiante para enfrentar qualquer processo seletivo.