Fundamentos Intermediários de Programação
Você saiu da zona de iniciante: o próximo passo é decidir com consciência. Estruturas de dados, algoritmos, boas práticas e o caminho até produção, no seu ritmo.
Ser intermediário é saber o suficiente para tomar decisões bem pensadas. É quando você consegue justificar as escolhas que faz.
Pode ser que saiu de um bootcamp, terminou um curso ou já trabalhe um tempo com código. Dominou lógica e sintaxe de uma linguagem. E aí aparece uma oportunidade maior: você vê que código pequeno funciona bem, mas quando fica grande, você quer entender por que algumas escolhas causam problemas. Você ouve falar em árvore, fila, grafo e quer conectar esses conceitos ao que já sabe. Você observa como os sêniors decidem e começa a perceber os padrões por trás.
Este guia não é currículo. É o alicerce que sustenta você diante de novos problemas. Você já consegue programar. Agora vem entender por que um código funciona bem em pequena escala e cai em produção, por que escolher uma estrutura de dados faz diferença, e como o código sai da sua máquina para a de alguém usando a aplicação de verdade.
Pertence aqui. Cada coisa que não sabe ainda é só uma coisa que não sabe YET.
Estruturas de dados: escolher é uma superpower
Iniciante pensa: "uso um array para tudo". Funciona para muita coisa. Até que você descobre que tem ferramentas melhores para problemas específicos.
O que você precisa é reconhecer que cada estrutura existe com um propósito, saber para que serve e quando usar uma em vez de outra. No dia em que alguém fala "usa uma fila aqui" ou "isso seria mais rápido com um grafo", você entende por quê. Isso é intermediário.
Linked List (lista ligada)
Cada item aponta para o próximo, como uma corrente. Você ganha velocidade em inserções e remoções, perde em acessar um item específico (precisa percorrer do começo). Quando está adicionando e removendo o tempo todo, vale a pena.
Stack (pilha)
Tira o último que entrou (LIFO: Last In, First Out). Aperta Ctrl+Z? Pilha. Função chamando função? Pilha. Parser de expressão matemática? Pilha também. Se tem ordem de "reverter" ou "desfazer", provavelmente há uma stack invisível embaixo.
Queue (fila)
O primeiro que entra é o primeiro que sai (FIFO: First In, First Out). Fila de impressão, fila de processamento de pagamento, chat em tempo real. Se a ordem importa como "chegou", provavelmente queue.
Tree (árvore)
Hierarquia. DOM do navegador é uma árvore. Sistema de arquivos é árvore. Banco de dados indexado é árvore. Quase tudo que tem pai e filho é árvore.
Graph (grafo)
Nós e conexões que não seguem hierarquia rígida. GPS traçando rota? Grafo. Rede social? Grafo. Recomendação de produtos? Grafo. Quando há relacionamentos complexos de "muitos pra muitos", é grafo.
O sinal de estar chegando lá: você consegue explicar por que cada estrutura é a escolha certa para seu caso. Você vai lembrar que existem quando o problema pedir, e saber qual puxar. A definição técnica fica com você com o tempo.
Algoritmos e padrões: reconhecer é 80% do trabalho
O ganho real é conseguir olhar pro problema e pensar "isso cheira a busca binária" ou "isso parece caso de dividir pra conquistar". A teoria ajuda, mas o reconhecimento do padrão é o que conta.
Busca Binária
Procura algo em dados ordenados dividindo o problema pela metade a cada passo. Se tem 1 milhão de itens, procura linear testa todos (lento). Busca binária descarta metade a cada tentativa. Muito mais rápido.
Algoritmo de Dijkstra
Encontra o menor caminho entre dois pontos. Usado em GPS e cálculo de rotas de rede. Essencial se lida com grafos e precisa de otimização de rota.
Tem uma regra de ouro que funciona: se está percorrendo TUDO sempre, há um algoritmo melhor esperando. Antes de sair "otimizando código", questione a estratégia. Às vezes o ganho vem da mudança de algoritmo, não de refactoring.
Recursividade: função que chama a si mesma
É elegante e poderosa quando bem feita, mas exige atenção.
Uma função que chama ela mesma dentro dela. Muito usada em árvores, grafos e problemas de backtracking. O ponto crítico: você precisa de caso base (o ponto onde a função para de chamar a si mesma). Sem caso base, você cria um loop infinito que estoura memória. Com caso base bem definido, fica seguro e legível.
[!ATENÇÃO] Recursividade é elegante. Mas às vezes é menos legível que um loop simples e come mais memória. Intermediário sabe quando usar e quando fazer loop mesmo.
Big O Notation: como performance piora com escala
É o jeito de medir "e se colocar 10 vezes mais dados?" ou "e se colocar 1 milhão?". Não é tempo exato (sua máquina é diferente da minha), é padrão de crescimento.
- O(1): constante. Se tem 10 itens ou 10 milhões, o tempo é o mesmo. Exemplo: pegar item de um array pelo índice.
- O(log n): eficiente. Cresce lentamente conforme dados crescem. Exemplo: busca binária.
- O(n): linear. Tempo cresce junto com dados. Exemplo: percorrer um array do começo ao fim.
- O(n²): cuidado aqui. Cresce rápido. Dois loops aninhados, ambos percorrem tudo. Se tem 1000 itens, já são 1 milhão de operações.
O intermediário entende que performance é uma questão de ESCALA, não de código "sujo". Às vezes o algoritmo em si é O(n²) e o ganho real vem ao trocar por outro que é O(n log n). Não é refactoring, é redesenho de como resolve o problema.
Paradigmas de programação: escolha a ferramenta certa
Você aproveita o que cada paradigma oferece. Precisa entender o modelo mental de cada um e quando ele brilha.
Programação Orientada a Objetos (POO)
Classes, objetos, encapsulamento, herança. Agrupa dados e comportamento no mesmo lugar. Se conhece uma classe que tem dados e métodos, está vendo POO.
Programação Funcional
Funções puras (mesma entrada sempre dá mesma saída), imutabilidade (não modifica dado), menos efeitos colaterais. Dados e comportamento são separados.
O intermediário sabe misturar conscientemente. Código que tem classes mas também usa map/filter é pragmático e eficaz. A mistura inteligente de paradigmas é uma característica do código bom.
Boas práticas de desenvolvimento: código é lido muito mais do que é escrito
Talvez 10 vezes mais. Seu código de hoje vai ser lido por você daqui 6 meses (quando não vai lembrar por que fez) e por outro dev que entrou no projeto.
Clean Code
Nomes claros (variável chamada userData diz mais que x), funções pequenas, responsabilidade única. Se função faz mais de uma coisa, quebra. Se nome não diz o que faz, renomeia.
Versionamento profissional
Commits focados (um feature, um bug, não mistura). Mensagens claras que explicam o porquê, não só o quê. Branches com nome descritivo. É histórico do projeto, não log de cansaço.
Tipos de teste
- Unitário: testa uma função isolada em cenários diferentes.
- Integração: testa módulos juntos, como se comunicam.
- End-to-end: testa fluxo real de usuário na aplicação.
Código bem testado é andar com clareza. Você avança com segurança, sabendo que algo quebrou antes de chegar em produção.
Test Driven Development (TDD)
O fluxo é simples:
- Escreve o teste (vai falhar porque função não existe).
- Escreve o código mínimo que faz passar.
- Refatora sem quebrar o teste.
Nem todo projeto precisa começar com TDD, mas o benefício é claro: força você a pensar antes de codar, reduz bugs e cria código que é fácil de testar. Naturalmente leva você a escrever modular, porque código modular é testável.
Domain Driven Design (DDD)
É sobre modelar o domínio bem, e isso vai muito além de pastas com nomes bonitos. Erro comum: confundir estrutura de diretórios com DDD (quando na verdade é um jeito de pensar).
Os pilares:
- Linguagem ubíqua: time e código falam igual. Se no negócio chamam de "compra", no código é
compra, nãotransaction. Mesma palavra em todo lugar. - Separar regra de negócio de infraestrutura: a lógica que define como negócio funciona fica isolada de detalhe de banco de dados ou API. Regra fica testável.
- Contextos bem definidos: cada parte do domínio tem seu espaço. Usuário no contexto de Autenticação é diferente de Usuário no contexto de Billing.
DDD não é ferramenta que você "ativa". É jeito de pensar sobre o que está construindo.
Design Patterns: soluções recorrentes para problemas recorrentes
Não são leis. São guias que a comunidade descobriu que funcionam em certas situações.
- Factory: cria objetos sem expor detalhes. Você pede um objeto, alguém cria pra você.
- Strategy: permite trocar o algoritmo em tempo de execução. Múltiplas formas de fazer a mesma coisa.
- Observer: notifica vários objetos quando algo muda em outro. Se muda um modelo, todas as views que observam atualizam.
- Singleton: garante uma única instância de uma classe em toda aplicação. Use com propósito claro, porque pode criar acoplamento invisível se não tiver atenção.
Tem uma referência visual excelente em refactoring.guru/design-patterns se precisar desenhar.
O sinal de estar chegando lá: reconhecer o padrão e entender o propósito, mesmo sem lembrar o nome. Alguém fala "apliquei um strategy" e você pensa "ah, agora consigo trocar o algoritmo dinamicamente". Isso é intermediário de verdade.
CI/CD e DevOps básico: código não termina no commit
Termina quando está rodando em produção, alguém usando, trazendo valor.
CI (Integração Contínua): cada vez que manda código, testes e build rodam automaticamente. Você descobre problema rápido, não quando vai fazer deploy.
CD (Deploy Contínuo): a entrega para produção também é automática (ou um clique). O código sai da branch para o mundo real sem passos manuais propensos a erro.
O intermediário entende que o caminho até produção é tão importante quanto o código que escreve. Código que funciona em produção é código que entrega valor real.
Docker: empacotar com tudo que precisa
Um container é sua aplicação mais as dependências, isoladas num pacote. Resolve o clássico "funciona na minha máquina, por que não funciona no servidor?".
Você empacota tudo que a app precisa num container. Qualquer máquina que roda Docker roda a mesma coisa. Facilita testes, deploy, é base para orquestração com Kubernetes. Pensa em container como "seu laptop, mas minimal, só o que a aplicação precisa".
Cloud (AWS, GCP, Azure): entendimento acessível desde intermediário
Você não precisa saber tudo de cloud para começar. Mas alguns conceitos bem entendidos mudam completamente a forma como você resolve problemas.
- Computação sob demanda: você não mantém servidor físico, aluga recurso quando precisa.
- Diferença entre serviço gerenciado (provedora cuida de manutenção e updates) vs. servidor que você configura sozinho.
- Custos crescem com uso e escala automática funciona puxando máquinas novas quando precisa.
O sinal de intermediário em cloud: conseguir subir, monitorar e gerenciar um serviço simples na plataforma escolhida. Especialização em todas as 200 serviços vem depois, se precisar. O intermediário é capaz e confiante com o essencial.
Otimizações de performance: meça antes de otimizar
Antes de "melhorar performance", recolha dados. Otimizar sem medir é chutar no escuro e queimar tempo.
Estratégias comuns que funcionam:
- Cache: guarda resultado de operação cara para não refazer. Se precisa calcular algo complexo 10 vezes por dia, cache 1 vez.
- Reduzir I/O: operações de disco e rede são lentas. Minimize leituras desnecessárias.
- Lazy loading: carrega só o que vai usar, quando vai usar. Se página tem 100 imagens, carrega 10, resto quando desce.
- Escolha certa de estrutura de dados: já cobrimos ali em cima. Array vs. árvore vs. grafo faz diferença.
Pair Programming: dois devs, um teclado
Não é sobre velocidade. É sobre qualidade.
Os ganhos reais: menos bugs (um olha enquanto outro digita), mais aprendizado (vocês conversam o tempo todo), decisões melhores (dois pensadores costumam pensar melhor que um). E tem vantagem invisível: você aprende jeitos diferentes de resolver problema só de trabalhar lado a lado com alguém.
Checklist de intermediário
Fecha um item? Marque. Alguns itens abertos? Você está exatamente no caminho certo.
- Entendo Big O e como escala de dados impacta performance real?
- Sei quando usar árvore, fila ou grafo em vez de array?
- Escrevo testes, mesmo que ainda não em 100% dos cenários?
- Versionamento: commits focados, mensagens que explicam por quê, branches com nome?
- Entendo o caminho do código até produção (CI/CD, deploy, quem vê)?
- Consigo conversar sobre arquitetura sem travar?
- Reconheço padrões recorrentes, mesmo sem lembrar o nome?
Se fechou esses itens, você tem um alicerce sólido. Próximo passo é aprofundar na sua área: se é front-end, vai em performance, acessibilidade, design system. Se é back-end, vai em database, cache, escalabilidade. Se é mobile, vai em lifecycle, performance no device, UX em rede lenta.
O fundamento é universal e dura a carreira toda. A especialização é onde você deita raízes, estuda fundo, e se torna fera.