DevGuideTK
← Trilha Backend

Escolha da linguagem

O que muda de verdade entre Node, Python, Go, Java e C# em modelo de execução, ecossistema e operação.

A pergunta errada e a pergunta certa

"Qual é a linguagem mais rápida?" é a pergunta mais comum e a menos útil. Num backend típico, um pedido passa a maior parte do tempo esperando: o banco responder, o cache responder, outro serviço responder. A linguagem decide o que acontece nos poucos milissegundos entre essas esperas.

As perguntas úteis são outras:

  • Como a linguagem executa o código e quanto tempo leva para iniciar?
  • Como ela atende muitos pedidos ao mesmo tempo?
  • Quanta memória ela ocupa, e como se comporta dentro de um container?
  • Quais bibliotecas existem para o problema, e quem na equipe sabe usá-las?

Este tópico compara as cinco linguagens usadas nos exemplos da trilha por esses critérios.

Como cada uma executa o código

LinguagemExecuçãoConsequência
Node (JavaScript, TypeScript)Interpretada e compilada durante a execução (JIT) pelo V8Partida rápida; o TypeScript é convertido em JavaScript antes
PythonInterpretada pelo CPythonPartida rápida, execução de código Python puro mais lenta
GoCompilada antes, para um binário nativoPartida muito rápida, um único arquivo para implantar
JavaCompilada para bytecode e depois otimizada pela JVM durante a execução (JIT)Partida mais lenta, desempenho que melhora depois de minutos de uso
C#Compilada para bytecode e otimizada pelo .NET durante a execução (JIT), com opção de compilação nativaPartida intermediária, desempenho alto depois do aquecimento

A compilação durante a execução tem um efeito pouco lembrado: o código fica mais rápido depois de rodar por um tempo. Medimos isso com um servidor Java (Jetty) limitado a uma CPU: 2.500 pedidos por segundo nos primeiros 15 segundos, 5.500 nos 15 seguintes e 10.000 depois de 30 segundos. Um serviço que acabou de iniciar, numa escala automática, atende os primeiros pedidos com desempenho bem menor que o de regime.

Como cada uma atende muitos pedidos

Este é o ponto em que as linguagens mais diferem, e onde moram as armadilhas.

LinguagemModeloArmadilha principal
NodeUm laço de eventos numa única thread; entrada e saída não bloqueiamUm cálculo pesado trava todos os pedidos ao mesmo tempo
PythonUma trava global (GIL) deixa só uma thread executar código Python por vez; asyncio para entrada e saídaCódigo síncrono dentro de uma rota assíncrona trava o laço; paralelismo de CPU exige vários processos
GoGoroutines leves, distribuídas pelo runtime entre as CPUsGoroutines que nunca terminam vazam memória em silêncio
JavaThreads do sistema; desde o Java 21, threads virtuais levesBibliotecas antigas que bloqueiam threads de plataforma
C#async/await sobre um pool de threadsChamar .Result ou .Wait() em código assíncrono esgota o pool

O padrão comum: as cinco atendem bem milhares de conexões esperando entrada e saída, cada uma do seu jeito. Os problemas aparecem quando o código bloqueia onde o modelo espera que ele não bloqueie, ou quando há trabalho pesado de CPU. O tópico de concorrência aprofunda os modelos.

Memória e partida, medidas

Construímos o mesmo servidor mínimo (uma rota que devolve um JSON pequeno) em cada linguagem, como imagem de container, e medimos com cada container limitado a uma CPU e 512 MB de memória:

ImagemPartida até o primeiro pedidoMemória paradaMemória sob carga
Node 24 (node:http)235 MB0,34 s18 MB23 MB
Python 3.13 (FastAPI e Uvicorn)212 MB0,70 s44 MB48 MB
Go (net/http)14 MB0,18 s6 MB15 MB
Java 21 (Jetty 12)461 MB1,44 s73 MB106 MB
C# .NET 9 (ASP.NET)331 MB0,41 s17 MB27 MB

Esses números importam de verdade em três situações: serverless e escala a partir de zero, em que a partida é paga pelo usuário que espera; muitos serviços pequenos, em que a memória de cada um se soma; e escala automática rápida, em que imagem e partida definem quanto tempo uma cópia nova leva para ajudar.

E a velocidade?

No mesmo teste, medimos quantos pedidos por segundo cada servidor atendeu, com 50 conexões simultâneas:

Pedidos por segundoLatência p99
Go, ajustado para uma CPU~55.0002 ms
Node~32.0003 ms
C#~31.00054 ms
Java com Jetty, depois de 30 s de aquecimento~10.00077 ms
Python com FastAPI, um processo~3.20027 ms

O que o teste ensinou sobre medir

As primeiras rodadas deram números errados, e cada erro é uma lição sobre como as linguagens se comportam em containers:

Primeiro resultadoCausaDepois de corrigir
Go: 7.000 pedidos por segundo, p99 de 84 msO Go via as 8 CPUs da máquina, e não o limite de 1 CPU do container; o sistema operacional freava o processoCom GOMAXPROCS=1: 55.000 pedidos por segundo, p99 de 2 ms
Java: 1.200 pedidos por segundo, latência fixa de 41 msO servidor HTTP embutido no JDK, com uma interação conhecida entre o algoritmo de Nagle e as confirmações atrasadas do TCPTrocado por um servidor de produção (Jetty)
Java com Jetty: 3.400 pedidos por segundoSó 5 segundos de aquecimento do JIT10.000 depois de 30 segundos

A primeira linha é a mais importante para quem usa Go em produção. O Go 1.25 passou a considerar o limite de CPU do container, mas com um mínimo de duas threads e só quando o go.mod declara a versão 1.25 ou mais nova. Com o go.mod na versão 1.24, ele continuou vendo as 8 CPUs da máquina. O nível Implementação mostra como cada runtime enxerga os limites do container.

Por que isso importa quando a IA escreve o código

Um assistente escreve código correto nas cinco linguagens, mas raramente leva em conta o modelo de execução de cada uma:

  • Um cálculo pesado ou uma leitura síncrona de arquivo dentro de uma rota Node trava o servidor inteiro.
  • Uma biblioteca síncrona chamada dentro de uma rota async do Python trava o laço de eventos.
  • Um .Result em C# ou um synchronized longo em Java prende as threads sob carga.
  • A configuração de memória e CPU do container é ignorada, e o processo morre por falta de memória ou é freado.
  • A escolha da linguagem é justificada por um benchmark de "olá mundo".

O próximo nível compara as linguagens pelos critérios que costumam decidir a escolha.

Prompts para aprofundar

Ao abrir no ChatGPT ou no Claude, o prompt é enviado ao serviço escolhido pela URL e também fica copiado na sua área de transferência.

  • Escolher a linguagem em cinco minutos

    Explique em no máximo cinco minutos de leitura o que realmente muda entre Node, Python, Go, Java e C# para construir backends, para alguém que conhece só uma delas.
    
    Cubra só o essencial: como cada uma executa o código, como atende muitos pedidos ao mesmo tempo, quanto ocupa de memória e por que a velocidade raramente é o que decide.
    
    No final, me faça três perguntas curtas para eu verificar se entendi, e corrija as minhas respostas.
  • Modelos de execução e concorrência

    Quero entender como Node, Python, Go, Java e C# executam código e atendem pedidos simultâneos.
    
    Aja como um mentor de backend. Pergunte primeiro quais dessas linguagens eu já uso.
    
    Para cada linguagem, uma por vez, explique:
    1. Como o código é executado: interpretado, compilado antes ou compilado durante a execução.
    2. O modelo de concorrência: laço de eventos, GIL e asyncio, goroutines, threads e threads virtuais, async e await.
    3. O que acontece quando uma rota faz um cálculo pesado de CPU.
    4. O que acontece quando uma rota chama uma função que bloqueia.
    
    Depois de cada linguagem, faça uma pergunta de cenário e espere a minha resposta. No final, monte comigo uma tabela comparando as cinco.
  • Por que a linguagem raramente é o gargalo

    Quero entender por que a escolha da linguagem raramente define o desempenho de um backend.
    
    Explique, com números aproximados e deixando claro que são ordens de grandeza:
    1. Quanto tempo uma rota típica gasta em consultas ao banco, chamadas a outros serviços e no próprio código.
    2. O que um benchmark de "olá mundo" mede e o que ele não mede.
    3. Em que tipos de sistema a linguagem passa a fazer diferença: processamento de CPU, latência muito baixa, custo de memória com muitos serviços, serverless.
    4. Como a compilação durante a execução (JIT) afeta os primeiros minutos de um serviço.
    
    Depois me apresente quatro sistemas e pergunte se a linguagem seria decisiva para o desempenho de cada um. Corrija as minhas respostas.
  • Runtimes dentro de containers

    Quero entender como cada runtime se comporta dentro de um container com limites de CPU e memória.
    
    Explique, para Node, Python, Go, Java e C#:
    1. Se o runtime percebe o limite de CPU do container, e o que acontece quando ele acha que tem mais CPUs do que tem.
    2. Se o runtime percebe o limite de memória, e como define o tamanho do heap ou o momento de coletar lixo.
    3. Quais configurações ajustam esse comportamento.
    
    Depois, me ajude a rodar um pequeno programa em cada linguagem que mostra o que o runtime enxerga, num container com --cpus=1 e --memory=512m, e explique os resultados.