Guia

Como criar um juiz online com uma API de execução de código

Um juiz online, o motor por trás dos sites de treino no estilo LeetCode e das maratonas de programação, roda uma submissão contra casos de teste ocultos e devolve um veredito. Em cima de uma API de execução de código, o ciclo inteiro é uma requisição, e você nunca precisa rodar um sandbox por conta própria. Este guia monta o juiz passo a passo, com código para copiar em Node.js ou Python.

Pela equipe do OnlineCompiler.io. Atualizado em .

Also in EnglishTambién en español

Como funciona um juiz online

Todo juiz online, do corretor automático de uma universidade ao LeetCode, segue o mesmo ciclo:

  1. Um problema tem casos de teste ocultos: uma entrada e a saída que um programa correto imprime para ela.
  2. Um aluno envia o código e escolhe uma linguagem.
  3. Para cada teste, o juiz roda o código num sandbox, com a entrada do teste na entrada padrão (stdin).
  4. Ele compara a saída do programa com a saída esperada e confere o tempo e a memória.
  5. Ele devolve um veredito: Accepted (aceito) se todos os testes passaram, ou a primeira falha.

O passo 3 é a parte difícil de construir sozinho: rodar código não confiável com segurança exige isolamento, limites de recursos e nenhum acesso à rede. POST /api/run-tests/ faz os passos 3 a 5 numa só requisição: você envia o código e os testes, e ela devolve um veredito para cada teste, um veredito geral e uma nota.

1. Guarde os casos de teste

Mantenha os testes de cada problema no seu servidor, nunca no navegador. Cada teste é um texto de entrada e a saída esperada, mais os limites do problema. Um primeiro problema clássico lê dois números e imprime a soma:

usage.js
import { judge } from "./judge.js";

const problem = {
  timeLimit: 2, // segundos por teste
  partialCredit: false,
  tests: [
    { input: "2 3\n", expected: "5\n" },
    { input: "-7 10\n", expected: "3\n" },
    { input: "2000000000 2000000000\n", expected: "4000000000\n" },
  ],
};

const submission = {
  compiler: "g++-15",
  code: `#include <iostream>
int main() { int a, b; std::cin >> a >> b; std::cout << a + b << "\\n"; }`,
};

console.log(await judge(problem, submission, { submit: true }));
// { verdict: "wrong_answer", score: 0, passed: 2, total: 3, ... }: o int estoura. Com long long, dá "accepted".

O terceiro teste é de propósito: quatro bilhões não cabem num int de 32 bits (o maior é 2.147.483.647), então uma solução em C++ que usa int imprime um número negativo errado. Bons conjuntos de testes incluem casos-limite como este. Cada entrada pode ter até 100 KB.

2. Envie o código e os testes

Uma requisição leva o código, a linguagem e todos os testes: um input, o expected_output e se o teste é hidden (oculto). Cada teste roda no seu próprio sandbox novo, no máximo 2 por vez no Free, na ordem em que você os lista, e cada um conta como uma execução. Uma requisição leva até 8 testes no Free e até 80 no Business, e tem um orçamento de 60 segundos; uma requisição que passa disso recebe 400 com o maior time_limit que caberia. Se o servidor estiver ocupado, ele responde 429; espere um pouco e tente de novo. Se continuar ocupado, os testes que não conseguirem começar em 45 segundos voltam como skipped e o veredito geral é incomplete; rode a requisição de novo. A função judge() abaixo trata os dois casos.

3. A saída é comparada para você

Comparar byte a byte reprova alunos por um espaço no fim da linha ou por uma quebra de linha final que falta, então o checker padrão, trim, tira os espaços no fim de cada linha e as linhas em branco do final, e depois compara exatamente. Para outros problemas, defina checker como exact, tokens (ignora todas as diferenças de espaçamento) ou float (números dentro da tolerance, 1e-6 por padrão). Problemas com várias respostas válidas precisam de um programa verificador, que a API não roda: compare esses no seu próprio código, a partir do output de uma execução visível.

Correção de questões de pandas e SQL

Questões de dados têm dois problemas que os checkers simples não resolvem: uma tabela não tem ordem fixa de linhas, e uma coluna de ponto flutuante raramente bate até o último dígito. O checker rows resolve os dois. Ele compara as linhas da saída como um conjunto sem ordem (uma linha que aparece duas vezes precisa aparecer duas vezes). Uma linha com |, vírgula ou tabulação é dividida em campos em cada um desses caracteres, mantendo os campos vazios para que cada valor fique na sua coluna; qualquer outra linha é dividida nos espaços. Os números batem dentro da tolerance, e todos os outros campos precisam bater exatamente. Envie os dados uma vez em files; o sandbox de cada teste recebe a sua própria cópia no diretório de trabalho (8 arquivos e 1 MB no máximo).

Para SQL, use duckdb-1.5: a consulta do aluno lê os arquivos com read_csv, e o DuckDB imprime cada linha com | entre as colunas e sem linha de cabeçalho, então escreva a saída esperada do mesmo jeito.

Uma questão de SQL, corrigida com rows
curl -X POST https://api.onlinecompiler.io/api/run-tests/ \
  -H "Authorization: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "compiler": "duckdb-1.5",
    "code": "SELECT region, sum(amount) FROM read_csv('\''sales.csv'\'') GROUP BY region;",
    "files": [
      {"name": "sales.csv", "content": "region,amount\nnorth,10\nsouth,20\nnorth,5.5\n"}
    ],
    "tests": [
      {"expected_output": "south|20.0\nnorth|15.5\n"}
    ],
    "checker": "rows"
  }'

Para pandas, use python-3.14 (3.0.6), ou python-3.14-pandas2 (2.3.3) se as suas questões foram escritas para o pandas 2. Faça o programa imprimir uma linha da tabela por linha, e dê à questão um código inicial que já faça isso:

Uma resposta em pandas, com os mesmos arquivos e o mesmo checker
import pandas as pd

df = pd.read_csv("sales.csv")
out = df.groupby("region", as_index=False)["amount"].sum()
for row in out.itertuples(index=False):
    print(row.region, row.amount)

Importar as bibliotecas leva um instante (cerca de 0,55 s para o pandas e até 3 s para o conjunto inteiro, nas nossas medições), então aumente o time_limit acima do padrão nas questões de pandas. A lista de pacotes está na tabela de pacotes, e a requisição completa, na referência do run-tests (as duas em inglês).

4. Leia os vereditos

Cada teste recebe um veredito, e a resposta tem um veredito geral: o do primeiro teste, na ordem que você definiu, que falhou.

VereditoQuando
time_limit_exceededCódigo de saída 124, ou time acima do time_limit do problema mais a folga de inicialização de Java (1 s), Groovy (1,5 s) e F# (2,5 s)
memory_limit_exceededEncerrado em 512 MB (código de saída 137). Há um só limite de memória; um limite menor exige conferir memory numa execução visível.
output_limit_exceededA saída padrão (stdout) passou de 64 KB
compilation_errorA compilação falhou; compile_error traz a mensagem quando um teste visível não compilou e, nas requisições de C e C++ que compilam uma vez só, sempre que algum teste é visível
runtime_errorQualquer outro código de saída diferente de zero, ou um sinal; error traz o stack trace num teste visível
wrong_answerA execução terminou sem erro, mas o checker rejeitou a saída
acceptedA execução terminou sem erro e a saída confere
skippedO teste nem começou: um teste anterior falhou com stop_on_failure ligado (o padrão na pontuação tudo ou nada), o programa não compilou ou o corte de início de 45 segundos chegou antes
incompleteSó como veredito geral: o corte de início pulou testes e nenhum falhou, em geral porque o servidor ou as outras execuções da sua conta deixaram os sandboxes ocupados. Repita a requisição; todos os testes rodam e contam de novo.

score vai de 0 a 100: tudo ou nada por padrão, ou, com percent e weighted, a fração dos testes (ou do weight deles) que passaram.

5. Julgue uma submissão inteira

Juntando tudo, o juiz é uma função:

judge.js (Node 18+)
// judge.js: roda uma submissão contra os testes de um problema com uma só chamada à API.
const RUN_TESTS_URL = "https://api.onlinecompiler.io/api/run-tests/";
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

// submit: true oculta todos os testes, então a resposta traz os vereditos e nada mais.
// submit: false (o botão Executar) também devolve a saída e o erro de cada teste.
export async function judge(problem, submission, { submit = false } = {}) {
  for (let attempt = 0; ; attempt++) {
    const res = await fetch(RUN_TESTS_URL, {
      method: "POST",
      headers: {
        Authorization: process.env.ONLINECOMPILER_API_KEY,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        compiler: submission.compiler,
        code: submission.code,
        // time_limit interrompe cada teste no limite do problema, sem contar a compilação.
        time_limit: problem.timeLimit,
        tests: problem.tests.map((t) => ({
          input: t.input,
          expected_output: t.expected,
          hidden: submit,
        })),
        // Pare na primeira falha, como na ICPC. "percent" continua depois de uma falha e pontua todos.
        scoring: problem.partialCredit ? "percent" : "all_or_nothing",
      }),
    });
    // O endpoint responde 429 quando está ocupado: espere e tente de novo.
    if (res.status === 429 && attempt < 5) {
      await wait(500 * 2 ** attempt);
      continue;
    }
    if (!res.ok) throw new Error(`Run failed: HTTP ${res.status} ${await res.text()}`);
    const result = await res.json(); // { verdict, score, passed, total, compile_error, tests }
    // "incomplete": os sandboxes estavam ocupados, então alguns testes não começaram a tempo. Testes
    // pulados não custam nada, mas repetir roda e conta todos os testes de novo, então repita poucas vezes.
    if (result.verdict === "incomplete" && attempt < 3) {
      await wait(2000);
      continue;
    }
    return result;
  }
}
judge.py
# judge.py: roda uma submissão contra os testes de um problema com uma só chamada à API.
import os
import time

import requests

RUN_TESTS_URL = "https://api.onlinecompiler.io/api/run-tests/"


def judge(problem, compiler, code, submit=False):
    # submit=True oculta todos os testes, então a resposta traz os vereditos e nada mais.
    # submit=False (o botão Executar) também devolve a saída e o erro de cada teste.
    body = {
        "compiler": compiler,
        "code": code,
        # time_limit interrompe cada teste no limite do problema, sem contar a compilação.
        "time_limit": problem["time_limit"],
        "tests": [
            {"input": t["input"], "expected_output": t["expected"], "hidden": submit}
            for t in problem["tests"]
        ],
        # Pare na primeira falha, como na ICPC. "percent" continua depois de uma falha e pontua todos.
        "scoring": "percent" if problem.get("partial_credit") else "all_or_nothing",
    }
    for attempt in range(6):
        res = requests.post(
            RUN_TESTS_URL,
            headers={"Authorization": os.environ["ONLINECOMPILER_API_KEY"]},
            json=body,
            timeout=90,
        )
        # O endpoint responde 429 quando está ocupado: espere e tente de novo.
        if res.status_code == 429 and attempt < 5:
            time.sleep(0.5 * 2**attempt)
            continue
        res.raise_for_status()
        result = res.json()  # {"verdict", "score", "passed", "total", "compile_error", "tests"}
        # "incomplete": os sandboxes estavam ocupados, então alguns testes não começaram a tempo. Testes
        # pulados não custam nada, mas repetir roda e conta todos os testes de novo, então repita poucas vezes.
        if result["verdict"] == "incomplete" and attempt < 3:
            time.sleep(2)
            continue
        return result

Por padrão, ela para no primeiro teste que falha, como fazem as competições no estilo ICPC. Para nota parcial, no estilo IOI, defina partialCredit: assim um teste que falha não interrompe os outros, e a nota é a fração que passou. Um teste oculto volta só com o seu verdict: sem saída, erro, código de saída, tempo ou memória, e sem a saída do compilador, porque um programa consegue vazar uma entrada oculta por qualquer um deles. Mostre aos alunos o veredito e o número do teste que falhou, nunca a entrada oculta nem a saída esperada. Use submit: false no botão Executar dos testes de exemplo, em que o aluno vê a saída.

6. Escale com o endpoint assíncrono

/api/run-tests/ mantém a requisição aberta até todos os testes terminarem, e uma requisição leva no máximo tantos testes quanto o limite de pendentes do seu plano. Quando uma turma inteira envia antes do prazo, coloque as execuções numa fila: o endpoint assíncrono responde na hora, e cada resultado é enviado por POST para a URL de callback definida na sua chave de API. Coloque os seus próprios ids em extra_params, e eles voltam junto com o resultado.

Coloque todos os testes na fila e colete os resultados
// Coloca todos os testes na fila de uma vez. Cada resultado vai por POST para a URL de callback da sua chave de API.
for (const [i, test] of problem.tests.entries()) {
  await fetch("https://api.onlinecompiler.io/api/run-code/", {
    method: "POST",
    headers: {
      Authorization: process.env.ONLINECOMPILER_API_KEY,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      compiler: submission.compiler,
      code: submission.code,
      input: test.input,
      extra_params: { submissionId: submission.id, test: i },
    }),
  });
}

// No seu handler de callback, extra_params diz de qual submissão e de qual teste é cada resultado:
app.post("/judge/callback", (req, res) => {
  const { submissionId, test } = req.body.extra_params;
  saveResult(submissionId, test, req.body); // output, error, exit_code, signal, truncated, time, memory
  res.sendStatus(200);
});

Duas observações sobre custo: cada caso de teste é uma execução, e cada execução da fila compila o programa de novo, então uma submissão em C++ com 20 testes compila 20 vezes; /api/run-tests/ compila programas em C, C++ e Java uma vez por requisição quando há mais testes do que os que rodam ao mesmo tempo, então prefira esse endpoint quando a submissão cabe numa requisição. Use esta rota quando uma submissão tiver mais testes do que uma requisição a /api/run-tests/ aceita, ou quando você preferir não manter uma requisição aberta. Para escolher um plano a partir dos seus usuários, submissões e testes, use o estimador de planos.

7. Vindo do Judge0: o endpoint de lote (batch)

Se você já tem um cliente do Judge0, a API compatível com o Judge0 (em inglês) também compara a saída, e você pode manter o cliente como está. Envie o expected_output de cada teste, e ela compara a saída do jeito que o checker trim faz, ignorando os espaços no fim de cada linha e no final. Um lote envia o conjunto de testes inteiro numa requisição:

Julgue todos os testes num lote
const JUDGE0 = "https://api.onlinecompiler.io/api/judge0";
const headers = {
  "X-Auth-Token": process.env.ONLINECOMPILER_API_KEY,
  "Content-Type": "application/json",
};

const res = await fetch(`${JUDGE0}/submissions/batch`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    submissions: problem.tests.map((test) => ({
      language_id: 105, // C++ (GCC 15); GET /languages lista os ids
      source_code: submission.code,
      stdin: test.input,
      expected_output: test.expected,
      cpu_time_limit: problem.timeLimit,
    })),
  }),
});
if (!res.ok) throw new Error(`Batch failed: HTTP ${res.status}`);
const tokens = (await res.json()).map((s) => s.token).join(",");

// Consulta de novo até nenhum teste estar In Queue (1) ou Processing (2).
let results;
do {
  await new Promise((resolve) => setTimeout(resolve, 1000));
  const poll = await fetch(
    `${JUDGE0}/submissions/batch?tokens=${tokens}&fields=status,time,memory`,
    { headers },
  );
  ({ submissions: results } = await poll.json());
} while (results.some((r) => r.status.id <= 2));

console.log(results.map((r) => r.status.description));
// [ "Accepted", "Accepted", "Wrong Answer" ]: o estouro do int de novo.

Cada teste volta com um status do Judge0: 3 Accepted, 4 Wrong Answer, 5 Time Limit Exceeded (acima de cpu_time_limit ou do teto de 30 segundos), 6 Compilation Error e de 7 a 12 para erros de execução. memory_limit é aceito, mas não é aplicado, então confira memory você mesmo se um problema precisar de um limite menor que 512 MB. Um lote leva até 20 testes e não mais que o limite de pendentes da conta (8 no Free), então divida conjuntos de testes maiores. Cada teste continua sendo uma execução.

8. Mantenha tudo seguro

  • Mantenha a chave de API e os testes no seu servidor. O navegador só envia o código e recebe um veredito.
  • Limite as submissões por usuário, para um aluno não gastar toda a sua cota.
  • Trate a saída como texto não confiável. Escape a saída antes de mostrá-la numa página.
  • A parte do sandbox já está resolvida: cada execução ganha um sandbox gVisor novo, sem rede, com 512 MB de memória, 2 CPUs e 16 processos.

Faça uma maratona de programação na sua faculdade

Não há uma competição hospedada aqui: nenhuma página de competição, inscrição ou placar para ativar. A API é o juiz, e o resto é pequeno o bastante para montar para um único evento. A versão mais simples tem três partes. Primeiro, uma lista de problemas, com os testes de cada problema guardados no seu servidor:

schema.sql (SQLite)
CREATE TABLE problems (
  id INTEGER PRIMARY KEY,
  title TEXT NOT NULL,
  statement TEXT NOT NULL,
  time_limit REAL NOT NULL DEFAULT 2
);
CREATE TABLE tests (
  problem_id INTEGER NOT NULL REFERENCES problems(id),
  input TEXT NOT NULL,
  expected TEXT NOT NULL
);
CREATE TABLE submissions (
  id INTEGER PRIMARY KEY,
  contestant TEXT NOT NULL,
  problem_id INTEGER NOT NULL REFERENCES problems(id),
  verdict TEXT NOT NULL,
  submitted_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);

Segundo, uma rota de envio que julga cada submissão com /api/run-tests/, pela função judge() do passo 5, e guarda só o veredito:

contest.py
# contest.py: a rota de envio de uma maratona, com o judge() do passo 5 e SQLite.
import sqlite3

from judge import judge

db = sqlite3.connect("contest.db")


def submit(contestant, problem_id, compiler, code):
    (time_limit,) = db.execute(
        "SELECT time_limit FROM problems WHERE id = ?", (problem_id,)
    ).fetchone()
    tests = [
        {"input": test_input, "expected": expected}
        for test_input, expected in db.execute(
            "SELECT input, expected FROM tests WHERE problem_id = ?", (problem_id,)
        )
    ]
    # submit=True oculta todos os testes, então o resultado traz os vereditos e nada mais.
    result = judge({"time_limit": time_limit, "tests": tests}, compiler, code, submit=True)
    db.execute(
        "INSERT INTO submissions (contestant, problem_id, verdict) VALUES (?, ?, ?)",
        (contestant, problem_id, result["verdict"]),
    )
    db.commit()
    return result["verdict"]  # mostre isto ao competidor, nunca os testes

Terceiro, o placar, ordenado por problemas resolvidos e depois por tempo de penalidade, como na ICPC:

leaderboard.sql
-- Estilo ICPC: mais problemas resolvidos e, depois, a menor penalidade: os minutos de :start até cada
-- primeira resposta aceita, mais 20 por tentativa rejeitada antes dela.
WITH first_ac AS (
  SELECT contestant, problem_id, MIN(submitted_at) AS solved_at
  FROM submissions
  WHERE verdict = 'accepted'
  GROUP BY contestant, problem_id
)
SELECT
  f.contestant,
  COUNT(*) AS solved,
  SUM(
    (strftime('%s', f.solved_at) - strftime('%s', :start)) / 60
    + 20 * (
      SELECT COUNT(*) FROM submissions s
      WHERE s.contestant = f.contestant
        AND s.problem_id = f.problem_id
        AND s.submitted_at < f.solved_at
        AND s.verdict NOT IN ('compilation_error', 'incomplete')
    )
  ) AS penalty
FROM first_ac f
GROUP BY f.contestant
ORDER BY solved DESC, penalty ASC;

Rode a consulta com :start definido como o horário de início da maratona em UTC, como 2026-11-14 09:00:00. Erros de compilação e execuções incomplete não contam como tentativas rejeitadas, e um competidor aparece no placar assim que resolve um problema. Cada submissão custa uma execução por teste: no Free, um problema pode ter até 8 testes, e 30 execuções por minuto são 3 submissões de 8 testes por minuto. Isso serve para a rodada de treino de um clube de programação, não para um salão cheio de competidores enviando ao mesmo tempo; a tabela abaixo dimensiona cada plano.

Backend para uma plataforma de provas de programação

Se você está construindo um produto de provas ou de provas práticas de laboratório, a API pode ser o núcleo da correção. Ela cobre a parte que é difícil de construir com segurança e deixa a prova em si com você:

A API cobreVocê constrói ou traz
Rodar código não confiável: um sandbox novo para cada teste, sem redeSupervisão da prova (proctoring): monitoramento de webcam, de tela e de troca de abas
Testes visíveis e ocultos, com pontos (weight) e nota parcialUm banco de questões: problemas, casos de teste e versões, guardados no seu servidor
Um tempo limite para cada teste, até 30 segundos, e 5 checkers de saídaListas de alunos, logins e o calendário das provas
46 linguagensDetecção de plágio, com uma ferramenta como o MOSS ou o JPlag
O endpoint assíncrono, que coloca as execuções numa fila e envia cada resultado para a sua URL de callbackProvas offline: toda execução vai para os nossos servidores pela internet, então um laboratório sem acesso à internet precisa de um juiz próprio

Quantos alunos cada plano atende ao mesmo tempo

Cada teste é uma execução, e cada plano limita quantas execuções a sua conta começa por minuto. Considere o pior momento de uma prova prática, quando todos os alunos enviam uma vez no mesmo minuto. Dividir o limite de cada plano pelos testes por submissão dá o tamanho da turma que cabe:

PlanoExecuções por minutoAlunos ao mesmo tempo, 4 testes cadaAlunos ao mesmo tempo, 8 testes cada
Free3073
Starter60157
Pro1203015
Business (em breve)3007537

Assim, o Pro permite 120 execuções por minuto: 30 alunos enviando 4 testes cada ficam abaixo do limite, e 200 alunos precisam de 7 minutos para a mesma rodada. Dentro do minuto, as submissões que chegam juntas também entram na fila: uma conta roda 4 testes por vez no Pro, com até 40 na fila ou rodando, e acima disso a API responde HTTP 429, que o judge() tenta de novo. Esses são os limites da sua conta, não uma garantia de capacidade: os sandboxes são compartilhados, então um servidor ocupado ainda pode responder 429 ou devolver incomplete. Para uma prova com tempo marcado e uma turma grande, fale com a gente antes.

O que a API não faz por você

  • Não guarda problemas nem casos de teste: o seu servidor guarda e envia junto com cada submissão.
  • /api/run-tests/ julga um programa contra tantos testes por requisição quanto o limite de pendentes do seu plano. Os endpoints run-code levam um teste por requisição e deixam o veredito para o seu código. O endpoint compatível com o Judge0 compara a saída e aceita lotes de até 20 testes, como no passo 7.
  • Os checkers são trim, exact, tokens, float e rows. Não há programa verificador para problemas com várias respostas válidas: compare esses no seu próprio código.
  • A saída é guardada até 64 KB por fluxo de saída, então problemas com saída maior precisam de um verificador dentro do próprio programa ou de outra abordagem.
  • Suporta 46 linguagens.

Se você prefere deixar os problemas e os casos de teste com o fornecedor, o módulo Problems do Sphere Engine guarda tudo isso. Veja como ele se compara ou como todas as opções se comparam (as duas páginas em inglês). Vai corrigir trabalhos de uma disciplina em vez de rodar um juiz? O guia de correção automática de código cobre pontos, nota parcial e a correção de uma pasta inteira de submissões.

Perguntas frequentes

Como corrijo uma questão de pandas ou SQL?

Envie os dados como arquivos junto com a requisição, faça o programa do aluno imprimir a resposta com uma linha da tabela por linha e defina checker como rows, que ignora a ordem das linhas e aceita números dentro de uma tolerância. Use python-3.14 para o pandas 3.0.6 ou python-3.14-pandas2 para o pandas 2.3.3, e duckdb-1.5 para SQL. Os arquivos valem para todos os testes, até 8 arquivos e 1 MB no total.

Como faço um clone do LeetCode?

Você precisa de quatro partes: um editor de código no navegador (Monaco ou CodeMirror), os problemas e os casos de teste no seu próprio banco de dados, uma rota no backend que envia o código e os testes para uma API de execução de código e uma página que mostra os vereditos. Com /api/run-tests/, a rota do backend é uma única requisição, e você nunca roda um sandbox; este guia monta essa rota em Node.js e Python.

Cada teste compila o programa de novo?

Em C, C++ e Java, não, quando a requisição tem mais testes do que os que rodam ao mesmo tempo: um sandbox compila o programa, e todos os testes rodam o que foi compilado. As outras linguagens compiladas compilam em cada teste, então, com compiladores lentos como Kotlin, Scala e Crystal, cabem menos testes numa requisição.

O que significam AC, WA, TLE, MLE, RE e CE?

São os vereditos clássicos dos juízes online, com as siglas em inglês. Accepted (AC, aceito): a saída bateu. Wrong Answer (WA, resposta errada): o programa rodou, mas imprimiu outra coisa. Time Limit Exceeded (TLE, tempo limite excedido): ainda estava rodando quando chegou o tempo limite. Memory Limit Exceeded (MLE, limite de memória excedido): foi encerrado no limite de 512 MB. Runtime Error (RE, erro de execução): qualquer outra falha ou código de saída diferente de zero. Compilation Error (CE, erro de compilação): o código não compilou. A API devolve esses vereditos como accepted, wrong_answer, time_limit_exceeded, memory_limit_exceeded, runtime_error e compilation_error.

Como os juízes online comparam a saída?

A maioria ignora os espaços no fim de cada linha e as linhas em branco no fim da saída, e depois compara exatamente. Problemas com várias respostas válidas, ou com respostas em ponto flutuante, precisam de um verificador (checker): comparar tokens, aceitar uma pequena tolerância, como 1e-6, ou rodar um programa verificador que valida a resposta.

Como defino um tempo limite menor que 30 segundos?

Envie time_limit, em segundos, com /api/run-tests/ (vale para cada teste; 2 se você não enviar) ou com uma execução avulsa. O programa é parado nesse limite com código de saída 124, e a compilação antes dele não conta; compilação e execução juntas continuam limitadas a 30 segundos. Java, Groovy e F# compilam ao iniciar, então ganham uma folga de inicialização além do limite: Java (1 s), Groovy (1,5 s) e F# (2,5 s). A memória tem um limite só, 512 MB; para um limite menor, compare o campo memory (em KB) do resultado com o seu limite.

Quantas chamadas à API uma submissão usa?

Uma por caso de teste, seja enviando os testes um por vez, numa requisição a /api/run-tests/ ou num lote (batch) do Judge0. Um problema com 20 testes usa 20 execuções por submissão, então as 100.000 execuções por mês do plano Free cobrem 5.000 submissões desse tamanho.

Os alunos conseguem ver os casos de teste ocultos?

Não, se a correção roda no seu servidor. O seu servidor guarda os casos de teste e a chave de API, envia tudo para a API com hidden definido como true e devolve ao aluno só o veredito. Um teste oculto volta com o veredito e nada mais.

Posso fazer uma maratona de programação da minha faculdade no OnlineCompiler.io?

Não como uma competição hospedada: não há páginas de competição, inscrições nem placares para ativar. A API julga cada submissão, e o resto é pequeno: uma tabela de problemas, uma rota de envio que chama /api/run-tests/ com testes ocultos e uma consulta para o placar. Esta página traz as três partes.

Dá para usar como backend de uma plataforma de provas de programação?

Para a correção, sim: testes visíveis e ocultos, pontos, um tempo limite para cada teste e 46 linguagens. Supervisão da prova (proctoring), detecção de plágio, o banco de questões, as listas de alunos e as provas offline ficam por sua conta, para construir ou comprar, e toda execução precisa de conexão com a internet até os nossos servidores.

Quais linguagens o juiz aceita?

Qualquer uma das 46 que a API suporta: Python (3.14), C (GCC 15), C++ (G++ 15), Java (OpenJDK 25), C# (.NET SDK 10), F# (.NET SDK 10), Go (1.26), Rust (1.93), PHP (8.5), Ruby (4.0), Haskell (GHC 9.12), TypeScript (Deno), JavaScript (Node.js 24), SQL (SQLite 3), DuckDB (1.5), Bash (5.2), Fortran (gfortran 15), Visual Basic (.NET SDK 10), Kotlin (2.4), R (4.6), Swift (6.4), COBOL (GnuCOBOL 3.2), Perl (5.44), Julia (1.13), Pascal (Free Pascal 3.2), Octave (11), Assembly (NASM 3), Lua (5.5), OCaml (5.4), Common Lisp (SBCL 2.6), Erlang (OTP 29), Elixir (1.20), Prolog (SWI-Prolog 10), D (LDC 1.43), FreeBASIC (1.10), Dart (3.13), Clojure (1.12), Groovy (6.0), Scala (3.10), Zig (0.17), Nim (2.2), Crystal (1.22), Ada (GNAT 15), PowerShell (7.6), Racket (9.3), Objective-C (Clang 21).