Guía
Cómo crear un juez en línea con una API de ejecución de código
Un juez en línea, el motor detrás de los sitios de práctica tipo LeetCode y de los concursos de programación, ejecuta un envío contra casos de prueba ocultos y devuelve un veredicto. Sobre una API de ejecución de código, todo el ciclo es una sola solicitud y nunca tienes que ejecutar un sandbox por tu cuenta. Esta guía lo construye paso a paso, con código para copiar en Node.js o Python.
Por el equipo de OnlineCompiler.io. Actualizado el .
Also in EnglishTambém em português
Cómo funciona un juez en línea
Todo juez en línea, desde el autocalificador de una universidad hasta LeetCode, sigue el mismo ciclo:
- Un problema tiene casos de prueba ocultos: una entrada y la salida que imprime un programa correcto para ella.
- Un estudiante envía su código y elige un lenguaje.
- Para cada prueba, el juez ejecuta el código en un sandbox, con la entrada de la prueba en la entrada estándar (stdin).
- Compara la salida del programa con la salida esperada y revisa el tiempo y la memoria.
- Devuelve un veredicto: Accepted (aceptado) si pasaron todas las pruebas, o el primer fallo.
El paso 3 es la parte difícil de construir por tu cuenta: ejecutar código no confiable de forma segura requiere
aislamiento, límites de recursos y nada de acceso a la red. POST /api/run-tests/ hace los pasos 3 a 5
en una sola solicitud: envías el código y las pruebas, y devuelve un veredicto por prueba, un veredicto general y
una calificación.
1. Guarda los casos de prueba
Guarda las pruebas de cada problema en tu servidor, nunca en el navegador. Cada prueba es un texto de entrada y la salida esperada, más los límites del problema. Un primer problema clásico lee dos números e imprime su suma:
import { judge } from "./judge.js";
const problem = {
timeLimit: 2, // segundos por prueba
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, ... }: el int se desborda. Con long long, da "accepted".La tercera prueba es a propósito: cuatro mil millones no caben en un int de 32 bits (el mayor es
2,147,483,647), así que una solución en C++ que usa int imprime un número negativo equivocado.
Los buenos conjuntos de pruebas incluyen casos límite como este. Cada entrada puede tener hasta 100 KB.
2. Envía el código y las pruebas
Una solicitud lleva el código, el lenguaje y todas las pruebas: un input, el expected_output y si la prueba es hidden (oculta). Cada prueba se ejecuta en su propio
sandbox nuevo, como máximo 2 a la vez en Free, en el orden en que las pongas, y cada una
cuenta como una ejecución. Una solicitud admite hasta 8 pruebas en Free y hasta
80 en Business, y tiene un presupuesto de 60 segundos; una solicitud
que lo supera recibe 400 con el mayor time_limit que cabría. Si el servidor está ocupado, responde
429; espera un poco y vuelve a intentar. Si sigue ocupado, las pruebas que no puedan empezar en
45 segundos vuelven como skipped y el veredicto general es incomplete; ejecuta la solicitud de nuevo. La función judge() de abajo maneja ambos
casos.
3. La salida se compara por ti
Comparar byte por byte reprueba a estudiantes por un espacio al final de la línea o por un salto de línea final
que falta, así que el checker predeterminado, trim, quita los espacios al final de cada línea y las
líneas en blanco del final, y luego compara exactamente. Para otros problemas, pon checker en exact, tokens (ignora todas las diferencias de espaciado) o float (números
dentro de tolerance, 1e-6 por defecto). Los problemas con varias respuestas
válidas necesitan un programa verificador, que la API no ejecuta: compáralos en tu propio código a partir del output de una ejecución visible.
Calificar preguntas de pandas y SQL
Las preguntas de datos tienen dos problemas que los checkers simples no resuelven: una tabla no tiene un orden
fijo de filas, y una columna de punto flotante rara vez coincide hasta el último dígito. El checker rows resuelve ambos. Compara las líneas de la salida como un conjunto sin orden (una línea que
aparece dos veces debe aparecer dos veces). Una línea con |, coma o tabulador se divide en campos en
cada uno de esos caracteres, conservando los campos vacíos para que cada valor quede en su columna; cualquier otra
línea se divide en los espacios. Los números coinciden dentro de tolerance y todos los demás campos
deben coincidir exactamente. Envía los datos una vez en files; el sandbox de cada prueba recibe su
propia copia en el directorio de trabajo (8 archivos y 1 MB como
máximo).
Para SQL, usa duckdb-1.5: la consulta del estudiante lee los archivos con read_csv, y
DuckDB imprime cada fila con | entre columnas y sin fila de encabezado, así que escribe la salida
esperada de la misma forma.
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, usa python-3.14 (3.0.6), o python-3.14-pandas2 (2.3.3) si tus preguntas se
escribieron para pandas 2. Haz que el programa imprima una fila por línea y dale a la pregunta un código inicial
que ya lo haga:
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 las bibliotecas toma un momento (unos 0.55 s para pandas y hasta
3 s para todo el conjunto, según nuestras mediciones), así que sube time_limit por encima del valor predeterminado en las preguntas de pandas. La lista de paquetes está
en la tabla de paquetes, y la solicitud completa, en
la referencia de run-tests (ambas en inglés).
4. Lee los veredictos
Cada prueba recibe un veredicto, y la respuesta tiene uno general: el de la primera prueba, en tu orden, que falló.
| Veredicto | Cuándo |
|---|---|
time_limit_exceeded | Código de salida 124, o time por encima del time_limit del problema más el margen de arranque de Java (1 s), Groovy (1.5 s) y F# (2.5 s) |
memory_limit_exceeded | Se detuvo al llegar a 512 MB (código de salida 137). Hay un solo límite de memoria; uno menor requiere revisar memory en una ejecución visible. |
output_limit_exceeded | La salida estándar (stdout) superó 64 KB |
compilation_error | La compilación falló; compile_error trae el mensaje cuando una prueba visible no compiló y, en las solicitudes de C y C++ que compilan una sola vez, siempre que alguna prueba sea visible |
runtime_error | Cualquier otro código de salida distinto de cero, o una señal; error trae el stack trace en una prueba visible |
wrong_answer | La ejecución terminó bien, pero el checker rechazó la salida |
accepted | La ejecución terminó bien y la salida coincide |
skipped | La prueba nunca empezó: una anterior falló con stop_on_failure activado (el valor predeterminado con la calificación todo o nada), el programa no compiló o llegó antes el corte de inicio de 45 segundos |
incomplete | Solo como veredicto general: el corte de inicio omitió pruebas y ninguna falló, por lo general porque el servidor o las demás ejecuciones de tu cuenta mantuvieron ocupados los sandboxes. Repite la solicitud; todas las pruebas se ejecutan y cuentan de nuevo. |
score va de 0 a 100: todo o nada por defecto, o, con percent y weighted, la
proporción de pruebas (o de su weight) que pasaron.
5. Juzga un envío completo
Con todo junto, el juez es una sola función:
// judge.js: ejecuta un envío contra las pruebas de un problema con una sola llamada a la API.
const RUN_TESTS_URL = "https://api.onlinecompiler.io/api/run-tests/";
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
// submit: true oculta todas las pruebas, así que la respuesta trae los veredictos y nada más.
// submit: false (el botón Ejecutar) también devuelve la salida y el error de cada prueba.
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 detiene cada prueba en el límite del problema, sin contar la compilación.
time_limit: problem.timeLimit,
tests: problem.tests.map((t) => ({
input: t.input,
expected_output: t.expected,
hidden: submit,
})),
// Detente en el primer fallo, como en el ICPC. "percent" sigue después de un fallo y califica todas.
scoring: problem.partialCredit ? "percent" : "all_or_nothing",
}),
});
// El endpoint responde 429 cuando está ocupado: espera y vuelve a intentar.
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": los sandboxes estaban ocupados y algunas pruebas no pudieron empezar a tiempo. Las pruebas
// omitidas no cuestan nada, pero un reintento ejecuta y cuenta todas las pruebas otra vez: reintenta pocas veces.
if (result.verdict === "incomplete" && attempt < 3) {
await wait(2000);
continue;
}
return result;
}
}# judge.py: ejecuta un envío contra las pruebas de un problema con una sola llamada a la 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 todas las pruebas, así que la respuesta trae los veredictos y nada más.
# submit=False (el botón Ejecutar) también devuelve la salida y el error de cada prueba.
body = {
"compiler": compiler,
"code": code,
# time_limit detiene cada prueba en el límite del problema, sin contar la compilación.
"time_limit": problem["time_limit"],
"tests": [
{"input": t["input"], "expected_output": t["expected"], "hidden": submit}
for t in problem["tests"]
],
# Detente en el primer fallo, como en el ICPC. "percent" sigue después de un fallo y califica todas.
"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,
)
# El endpoint responde 429 cuando está ocupado: espera y vuelve a intentar.
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": los sandboxes estaban ocupados y algunas pruebas no pudieron empezar a tiempo. Las pruebas
# omitidas no cuestan nada, pero un reintento ejecuta y cuenta todas las pruebas otra vez: reintenta pocas veces.
if result["verdict"] == "incomplete" and attempt < 3:
time.sleep(2)
continue
return resultPor defecto se detiene en la primera prueba que falla, como en los concursos estilo ICPC. Para dar calificación
parcial, estilo IOI, activa partialCredit: así una prueba que falla no detiene las demás y la
calificación es la proporción que pasó. Una prueba oculta vuelve solo con su verdict: sin salida,
error, código de salida, tiempo ni memoria, y sin la salida del compilador, porque un programa puede filtrar una
entrada oculta por cualquiera de ellos. Muéstrales a los estudiantes el veredicto y el número de la prueba que
falló, nunca la entrada oculta ni la salida esperada. Usa submit: false para el botón Ejecutar con
las pruebas de ejemplo, donde el estudiante ve la salida.
6. Escala con el endpoint asíncrono
/api/run-tests/ mantiene la solicitud abierta hasta que terminan todas las pruebas, y una solicitud
admite como máximo tantas pruebas como el límite de pendientes de tu plan. Cuando todo un grupo envía antes de la
fecha límite, pon las ejecuciones en cola: el endpoint asíncrono responde de inmediato, y cada resultado se envía
por POST a la URL de callback configurada en tu clave de API. Pon tus propios ids en extra_params y
vuelven junto con el resultado.
// Pon todas las pruebas en cola a la vez. Cada resultado llega por POST a la URL de callback de tu clave 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 },
}),
});
}
// En tu handler de callback, extra_params indica de qué envío y de qué prueba es 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);
});Dos notas sobre el costo: cada caso de prueba es una ejecución, y cada ejecución en cola vuelve a compilar el
programa, así que un envío en C++ con 20 pruebas compila 20 veces; /api/run-tests/ compila los
programas en C, C++ y Java una vez por solicitud cuando hay más pruebas de las que se ejecutan a la vez, así que
es preferible cuando un envío cabe en una solicitud. Usa esta ruta cuando un envío tenga más pruebas de las que
admite una solicitud a /api/run-tests/, o cuando prefieras no mantener una solicitud abierta. Para
elegir un plan según tus usuarios, envíos y pruebas, usa el estimador de planes.
7. Si vienes de Judge0: el endpoint de lotes (batch)
Si ya tienes un cliente de Judge0, la API compatible con Judge0 (en
inglés) también compara la salida, y puedes dejar tu cliente como está. Envía el expected_output de
cada prueba y la API compara la salida como lo hace el checker trim, ignorando los espacios al final
de cada línea y al final de la salida. Un lote envía todo un conjunto de pruebas en una solicitud:
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 los 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 hasta que ninguna prueba esté In Queue (1) ni 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" ]: otra vez el desbordamiento del int.Cada prueba vuelve con un estado de Judge0: 3 Accepted, 4 Wrong Answer, 5 Time Limit Exceeded (por encima de cpu_time_limit o del tope de 30 segundos), 6 Compilation Error y de 7 a 12 para
errores en tiempo de ejecución. memory_limit se acepta, pero no se aplica, así que revisa memory tú mismo si un problema necesita un límite menor que 512 MB. Un lote admite
hasta 20 pruebas y no más que el límite de pendientes de la cuenta (8 en Free), así que
divide los conjuntos de pruebas más grandes. Cada prueba sigue siendo una ejecución.
8. Mantenlo seguro
- Guarda la clave de API y las pruebas en tu servidor. El navegador solo envía el código y recibe un veredicto.
- Limita los envíos por usuario, para que un estudiante no agote tu cuota.
- Trata la salida como texto no confiable. Escápala antes de mostrarla en una página.
- La parte del sandbox ya está resuelta: cada ejecución recibe un sandbox gVisor nuevo, sin red, con 512 MB de memoria, 2 CPU y 16 procesos.
Organiza un concurso de programación en tu universidad
Aquí no hay un concurso alojado: ninguna página de concurso, registro ni tabla de posiciones que activar. La API es el juez, y el resto es lo bastante pequeño para construirlo para un solo evento. La versión más simple tiene tres partes. Primero, una lista de problemas, con las pruebas de cada uno guardadas en tu servidor:
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, una ruta de envío que juzga cada envío con /api/run-tests/, mediante la función judge() del paso 5, y guarda solo el veredicto:
# contest.py: la ruta de envío de un concurso, con judge() del paso 5 y 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 todas las pruebas, así que el resultado trae los veredictos y nada más.
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"] # muestra esto al concursante, nunca las pruebasTercero, la tabla de posiciones, ordenada por problemas resueltos y luego por tiempo de penalización, como en el ICPC:
-- Estilo ICPC: más problemas resueltos y, luego, la menor penalización: los minutos desde :start hasta
-- cada primera respuesta aceptada, más 20 por cada intento rechazado antes de ella.
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;Ejecuta la consulta con :start igual a la hora de inicio del concurso en UTC, por ejemplo 2026-11-14 09:00:00. Los errores de compilación y las ejecuciones incomplete no cuentan
como intentos rechazados, y un concursante aparece en cuanto resuelve un problema. Cada envío cuesta una ejecución
por prueba: en Free, un problema puede tener hasta 8 pruebas, y 30
ejecuciones por minuto son 3 envíos de
8 pruebas por minuto. Eso alcanza para la ronda de práctica de un club, no para una sala llena
de concursantes enviando a la vez; la tabla de abajo dimensiona cada plan.
Backend para una plataforma de exámenes de programación
Si estás construyendo un producto de exámenes o de prácticas de laboratorio evaluadas, la API puede ser su núcleo de calificación. Cubre la parte que es difícil de construir de forma segura y te deja el examen en sí:
| La API cubre | Tú construyes o traes |
|---|---|
| Ejecutar código no confiable: un sandbox nuevo para cada prueba, sin red | Supervisión del examen (proctoring): monitoreo de cámara web, pantalla y cambio de pestañas |
Pruebas visibles y ocultas, con puntos (weight) y calificación parcial | Un banco de preguntas: problemas, casos de prueba y versiones, guardados en tu servidor |
| Un tiempo límite por prueba, hasta 30 segundos, y 5 checkers de salida | Listas de estudiantes, inicios de sesión y el calendario de exámenes |
| 46 lenguajes | Detección de plagio, con una herramienta como MOSS o JPlag |
| El endpoint asíncrono, que pone las ejecuciones en cola y envía cada resultado a tu URL de callback | Exámenes sin conexión: cada ejecución va a nuestros servidores por internet, así que un laboratorio sin acceso a internet necesita su propio juez |
Cuántos estudiantes admite cada plan a la vez
Cada prueba es una ejecución, y cada plan limita las ejecuciones que tu cuenta inicia por minuto. Supón el peor momento de un examen práctico, cuando todos los estudiantes envían una vez en el mismo minuto. Al dividir el límite de cada plan entre las pruebas por envío, sale el tamaño del grupo que cabe:
| Plan | Ejecuciones por minuto | Estudiantes a la vez, 4 pruebas cada uno | Estudiantes a la vez, 8 pruebas cada uno |
|---|---|---|---|
| Free | 30 | 7 | 3 |
| Starter | 60 | 15 | 7 |
| Pro | 120 | 30 | 15 |
| Business (próximamente) | 300 | 75 | 37 |
Así, Pro permite 120 ejecuciones por minuto:
30 estudiantes que envían 4 pruebas cada uno quedan por
debajo del límite, y 200 estudiantes necesitan 7 minutos para la misma ronda. Dentro del
minuto, los envíos que llegan juntos también hacen cola: una cuenta ejecuta 4 pruebas a
la vez en Pro, con hasta 40 en cola o en ejecución, y por encima de eso la API
responde HTTP 429, que judge() reintenta. Estos son los límites de tu cuenta, no una promesa de
capacidad: los sandboxes son compartidos, así que un servidor ocupado todavía puede responder 429 o devolver incomplete. Para un examen con tiempo y un grupo grande, escríbenos antes.
Lo que la API no hace por ti
- No guarda problemas ni casos de prueba: tu servidor los guarda y los manda junto con cada envío.
/api/run-tests/juzga un programa contra tantas pruebas por solicitud como el límite de pendientes de tu plan. Los endpoints run-code admiten una prueba por solicitud y le dejan el veredicto a tu código. El endpoint compatible con Judge0 compara la salida y admite lotes de hasta 20 pruebas, como en el paso 7.- Los checkers son
trim,exact,tokens,floatyrows. No hay programa verificador para problemas con varias respuestas válidas: compáralos en tu propio código. - La salida se conserva hasta 64 KB por flujo de salida, así que los problemas con salidas más grandes necesitan un verificador dentro del propio programa u otro enfoque.
- Admite 46 lenguajes.
Si prefieres dejar los problemas y los casos de prueba en manos del proveedor, el módulo Problems de Sphere Engine los guarda. Mira cómo se compara o cómo se comparan todas las opciones (ambas páginas en inglés). ¿Vas a calificar tareas de un curso en lugar de montar un juez? La guía de calificación automática de código cubre puntos, calificación parcial y cómo calificar una carpeta entera de envíos.
Preguntas frecuentes
¿Cómo califico una pregunta de pandas o SQL?
Envía los datos como archivos con la solicitud, haz que el programa del estudiante imprima su respuesta con una fila por línea y pon checker en rows, que ignora el orden de las filas y acepta números dentro de una tolerancia. Usa python-3.14 para pandas 3.0.6 o python-3.14-pandas2 para pandas 2.3.3, y duckdb-1.5 para SQL. Todas las pruebas comparten los archivos, hasta 8 archivos y 1 MB en total.
¿Cómo hago un clon de LeetCode?
Necesitas cuatro partes: un editor de código en el navegador (Monaco o CodeMirror), los problemas y sus casos de prueba en tu propia base de datos, una ruta del backend que envía el código y las pruebas a una API de ejecución de código, y una página que muestra los veredictos. Con /api/run-tests/, la ruta del backend es una sola solicitud y nunca ejecutas un sandbox; esta guía construye esa ruta en Node.js y Python.
¿Cada prueba vuelve a compilar el programa?
En C, C++ y Java, no, cuando una solicitud tiene más pruebas de las que se ejecutan a la vez: un sandbox compila el programa y luego todas las pruebas ejecutan esa compilación. Los demás lenguajes compilados compilan en cada prueba, así que con compiladores lentos como Kotlin, Scala y Crystal caben menos pruebas en una solicitud.
¿Qué significan AC, WA, TLE, MLE, RE y CE?
Son los veredictos clásicos de los jueces en línea, con sus siglas en inglés. Accepted (AC, aceptado): la salida coincidió. Wrong Answer (WA, respuesta incorrecta): se ejecutó, pero imprimió otra cosa. Time Limit Exceeded (TLE, tiempo límite excedido): seguía ejecutándose al llegar al tiempo límite. Memory Limit Exceeded (MLE, límite de memoria excedido): se detuvo al llegar al límite de 512 MB. Runtime Error (RE, error en tiempo de ejecución): cualquier otro fallo o código de salida distinto de cero. Compilation Error (CE, error de compilación): no compiló. La API los devuelve como accepted, wrong_answer, time_limit_exceeded, memory_limit_exceeded, runtime_error y compilation_error.
¿Cómo comparan la salida los jueces en línea?
La mayoría ignora los espacios al final de cada línea y las líneas en blanco al final de la salida, y luego compara exactamente. Los problemas con varias respuestas válidas, o con respuestas de punto flotante, necesitan un verificador (checker): comparar tokens, permitir una pequeña tolerancia, como 1e-6, o ejecutar un programa verificador que valide la respuesta.
¿Cómo pongo un tiempo límite menor que 30 segundos?
Envía time_limit, en segundos, con /api/run-tests/ (se aplica a cada prueba; 2 si no lo envías) o con una ejecución individual. El programa se detiene en ese límite con el código de salida 124, y la compilación previa no cuenta; compilar y ejecutar juntos siguen limitados a 30 segundos. Java, Groovy y F# compilan al arrancar, así que tienen un margen de arranque además del límite: Java (1 s), Groovy (1.5 s) y F# (2.5 s). La memoria tiene un solo límite, 512 MB; para uno menor, compara el campo memory (en KB) del resultado con tu límite.
¿Cuántas llamadas a la API usa un envío?
Una por caso de prueba, ya sea que envíes las pruebas una por una, en una solicitud a /api/run-tests/ o en un lote (batch) de Judge0. Un problema con 20 pruebas usa 20 ejecuciones por envío, así que las 100,000 ejecuciones al mes del plan Free alcanzan para 5,000 envíos de ese tamaño.
¿Los estudiantes pueden ver los casos de prueba ocultos?
No, si la calificación se hace en tu servidor. Tu servidor guarda los casos de prueba y la clave de API, los envía a la API con hidden en true y solo le devuelve el veredicto al estudiante. Una prueba oculta vuelve con su veredicto y nada más.
¿Puedo organizar un concurso de programación de mi universidad en OnlineCompiler.io?
No como un concurso alojado: no hay páginas de concurso, registros ni tablas de posiciones que activar. La API juzga cada envío, y el resto es poco: una tabla de problemas, una ruta de envío que llama a /api/run-tests/ con pruebas ocultas y una consulta para la tabla de posiciones. Esta página trae las tres partes.
¿Puede ser el backend de una plataforma de exámenes de programación?
Para la calificación, sí: pruebas visibles y ocultas, puntos, un tiempo límite por prueba y 46 lenguajes. La supervisión del examen (proctoring), la detección de plagio, el banco de preguntas, las listas de estudiantes y los exámenes sin conexión te tocan a ti, ya sea que los construyas o los compres, y cada ejecución necesita conexión a internet hasta nuestros servidores.
¿Qué lenguajes acepta el juez?
Cualquiera de los 46 que admite la API: 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).