The Need for Speed: una retrospectiva del desafío de codificación JavaScript de Toptal
Publicado: 2022-03-11Toptal comenzó la aplicación web del desafío de codificación de JavaScript como una forma de atraer personas a nuestras cabinas de conferencias. Al ver el éxito que tuvo, decidimos hacer un piloto en la web, abierto para todos en nuestra comunidad y sus redes.
En el momento del lanzamiento, alentamos a los desarrolladores motivados a encontrar formas creativas de obtener una puntuación alta en el desafío general de codificación de JavaScript. Algunos de los factores clave de éxito de los grandes autónomos independientes son la capacidad de pensar fuera de la caja y encontrar formas creativas de trabajar dentro de un conjunto de limitaciones.
Preguntas de desafío de codificación de JavaScript
El desafío consistía en una serie de preguntas de JavaScript, similares a las que podrían usarse como preguntas de entrevista, que iban desde preguntas de desafío de JavaScript realmente básicas:
box.double = function double (x) { //return x doubled };
…a los más intermedios:
box.dateRank = function dateRank (x) { // x is a date in 2019 as string (example: "06/30/2019") // return the rank of the day in 2019 (ie, "09/01/2019" translates to 244) };
Queríamos que tanto los desarrolladores principiantes como los avanzados se divirtieran e invitaran a sus amigos y colegas a competir con ellos por los puntajes más altos. Las preguntas se ordenaron por puntos, de modo que incluso los desarrolladores junior pudieran obtener algunos puntos. El orden de las preguntas que tenían la misma cantidad de puntos fue aleatorio, por lo que la experiencia fue ligeramente diferente cada vez, mientras que el conjunto completo de preguntas permaneció igual durante toda la semana.
El objetivo era completar tantas tareas como fuera posible dentro del límite de tiempo de tres minutos. En caso de que alguien encontrara una manera de completar todas las tareas en el desafío de codificación de JavaScript, se otorgarían 10 puntos por cada segundo restante. Permitimos múltiples intentos para completar el desafío.
Lo primero que anticipamos que sucedería es que las personas se tomarían un tiempo para resolver las preguntas a un ritmo pausado y luego copiarían y pegarían las respuestas en la aplicación web.
Después de una hora y 40 minutos de lanzar el desafío, la primera persona siguió este enfoque y obtuvo los 1445 puntos máximos que permitía la aplicación, más algunos puntos extra que otorgamos por cada segundo restante.
Un punto de inflexión en el desafío de la codificación de JavaScript
Con el enfoque de copiar y pegar, los mejores concursantes no tenían nada más que ganar si se concentraban en codificar las respuestas ellos mismos. En cambio, dirigieron su atención a llevar la velocidad a sus habilidades de automatización.
El enfoque más fácil en este punto fue escribir algo de JavaScript que resolvería cada tarea mientras esperaba en un ciclo hasta que los botones estuvieran listos, y copiarlo y pegarlo en la consola del navegador:
const solutions = { 'double': 'return x*2', 'numberToString': '...', 'square': '...', 'floatToInt': '...', 'isEven': '...', 'squareroot': '...', 'removeFirstFive': '...', // ... 'dateRank': '...', // ... }; const get_submit_button = () => document.querySelector('.task-buttons > .col > .btn'); const solve = () => { const ace_editor = ace.edit(document.querySelector('.ace_editor')) const submission = ace_editor.getValue() for (const key in solutions) { if (submission.includes('box.' + key + ' ')) { ace_editor.insert(solutions[key]) get_submit_button().click() setTimeout(() => { get_submit_button().click() setTimeout(() => { solve() }, 400) }, 900) return } } } solve()
Esta parte también podría automatizarse usando herramientas de automatización regulares como Selenium. Pero una forma más rápida sería automatizar el uso de la API, enviando las soluciones a las tareas:
const request = require('request'); const runTask = (data, entryId, callback) => { const tests = data.nextTask.tests_json; const results = Object.fromEntries( Object.entries(tests).map(([key, value]) => [key, value.result]) ); request.post(`https://speedcoding.toptal.com/webappApi/entry/${entryId}/attemptTask`, { form: { attempt_id: data.attemptId, tests_json: JSON.stringify(results), }, }, (error, res, body) => { if (error) throw error; const next = JSON.parse(body).data if (next.isChallengeEntryFinished) { callback(next) return } runTask(next, entryId, callback) }); } const runEntry = (callback) => { request.post('https://speedcoding.toptal.com/webappApi/entry', { form: { challengeSlug: 'toptal-speedcoding', email: ..., leaderboardName: ..., isConfirmedToBeContacted: ..., dateStop: ... }, }, (error, res, body) => { if (error) throw error; const { data } = JSON.parse(body); const entryId = data.entry.id runTask(data, entryId, callback) }); } runEntry(console.log)
Una cosa a tener en cuenta es que, en esta versión del desafío de codificación rápida, el código se probó solo en el lado del cliente. Debido a esto, fue posible simplemente enviar las respuestas a los casos de prueba en lugar del código. Esto permitió una optimización y reducir algunos milisegundos en el lado del cliente.

Durante tres días, las puntuaciones se mantuvieron igual. Algunas personas estaban escribiendo optimizaciones micro para su código, y varias personas enviaban sus soluciones en un bucle, con la esperanza de que el servidor estuviera menos concurrido para poder obtener algunos puntos por delante. Nos esperaba una gran sorpresa.
Superando la puntuación máxima del desafío de codificación de JavaScript
Primero, hagamos algunos cálculos rápidos: obtuvimos un total de 1445 puntos otorgados por completar todas las tareas y un tiempo de 180 segundos. Si otorgamos 10 puntos por cada segundo restante en la aplicación, el puntaje teórico máximo alcanzable sería 3245, en el caso de enviar todas las respuestas al instante.
Uno de nuestros usuarios logró una puntuación de más de 6000, que siguió creciendo constantemente con el tiempo.
¿Cómo podría alguien obtener una puntuación tan alta?
Después de una breve revisión, encontramos lo que había estado sucediendo. Nuestro principal concursante, un desarrollador profesional de pila completa y Toptaler con más de 15 años de experiencia en programación competitiva, encontró una laguna en la configuración del desafío de codificación. Engendró múltiples bots, lo que ralentizó el servidor; mientras tanto, podía completar la misma tarea (la que otorgaba la mayor cantidad de puntos) tantas veces como fuera posible y asignar sus puntajes a una sola entrada, aumentando continuamente la puntuación de esa entrada.
Esto no estaba en contra de las reglas, ya que permitimos soluciones creativas; sin embargo, el método particular que estaba usando hizo que el servidor estuviera considerablemente más ocupado, lo que hizo que las solicitudes de red fueran más lentas para todos los demás. Lo primero que hicimos fue aumentar la potencia de nuestro servidor, lo que solo le hizo pasar de 56.000 a 70.000 puntos y quedarse en el primer puesto.
Si bien no queríamos intervenir en la forma en que las personas interactuaban con el desafío, estos intentos ralentizaron el servidor hasta el punto de que el desafío era difícil de usar para otros usuarios, por lo que decidimos solucionar el problema.
La solución hizo imposible que otras personas lograran el mismo puntaje durante el último día del desafío de codificación de JavaScript. Debido a esto, decidimos ampliar el número de premios otorgados a los mejores concursantes. Originalmente, se suponía que el premio mayor, un par de AirPods, era para el único concursante principal. Al final, se entregaron AirPods a quienes ocuparon los primeros seis lugares.
Comienzos humildes y finales feroces: algunas estadísticas del desafío de codificación de JavaScript
Incluso nuestros puntajes más altos tuvieron algunas dificultades para comenzar. De hecho, de todas las personas que hicieron cinco intentos o más, la puntuación más alta para el primer intento de cualquiera fue 645, y la puntuación media para los primeros intentos en ese grupo fue de solo 25 puntos.
Al final del concurso, uno podría adivinar la estrategia que se intentó a partir del número total de intentos. Si bien algunos fueron más eficientes que otros, el mejor concursante de lejos tuvo el mayor número de intentos.
Avanzando
¿Qué depara el futuro?
Esta fue solo la primera iteración del desafío de codificación JS. Queremos hacer muchos más desafíos de programación de JavaScript en el futuro, aprender lecciones de las ejecuciones anteriores y hacerlo aún más emocionante. Lo primero que queremos hacer es implementar la limitación de intentos para limitar el número de envíos. También queremos expandirnos más allá de los desafíos de codificación en JavaScript, haciéndolos disponibles en una amplia gama de lenguajes de programación.
Por último, mientras tomamos medidas para hacer que el desafío de codificación de JavaScript sea más seguro, planeamos seguir permitiendo el uso de bots y otros enfoques creativos para desafíos futuros.
Un agradecimiento especial a Pavel Vydra, Anton Andriievskyi, Tiago Chilanti y Matei Copot por sus contribuciones al desafío y este artículo, y a @Zirak por el proyecto de código abierto que formó la base de la aplicación del concurso. Del mismo modo, gracias a todos los que participaron y organizaron el concurso.