Toptal comenzó la aplicación web de desafío de codificación JavaScript como una forma de atraer personas a nuestras cabinas de conferencias. Al ver cuán exitoso fue, 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 sobre el desafío de codificación de JavaScript
El guante consistía en una serie de preguntas de JavaScript , similares a las que podrían usarse como preguntas de entrevista, que van 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 (i.e., "09/01/2019" translates to 244)
};
Queríamos que tanto los principiantes como los desarrolladores 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 podrían obtener algunos puntos. El orden de las preguntas con 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 encuentre una manera de completar todas las tareas en el desafío de codificación de JavaScript, se otorgarán 10 puntos por cada segundo que quede. Permitimos múltiples intentos para completar el desafío.
Lo primero que anticipamos que sucedería es que la gente se tomaría un tiempo para resolver las preguntas a un ritmo pausado y luego copiaría y pegaría 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 permitidos por la aplicación, además de algunos puntos adicionales que otorgamos por cada segundo restante.
Un punto de inflexión en el desafío de codificación de JavaScript
Con el enfoque de copiar y pegar, los principales concursantes no tenían nada más que ganar al enfocarse en codificar las respuestas ellos mismos. En cambio, centraron su atención en llevar la velocidad a sus habilidades de automatización.
El enfoque más fácil en este punto era escribir algo de JavaScript que resolviera cada tarea mientras esperaba en un bucle 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 utilizando 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 cortar algunos milisegundos en el lado del cliente.
Durante tres días, los puntajes permanecieron igual. Algunas personas escribieron micro optimizaciones para su código, y varias personas enviaron sus soluciones en un bucle, con la esperanza de que el servidor se llenara menos para poder adelantar algunos puntos. Nos esperaba una gran sorpresa.
Romper la puntuación máxima del desafío de codificación de JavaScript
Primero, hagamos algunos cálculos rápidos: tuvimos un total de 1445 puntos otorgados por completar todas las tareas, y un tiempo de 180 segundos. Si otorgamos 10 puntos por segundo restantes en la solicitud, el puntaje máximo teórico alcanzable sería 3245, en el caso de enviar todas las respuestas al instante.
Uno de nuestros usuarios logró un puntaje 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 full-stack y Toptaler con más de 15 años de experiencia en programación competitiva, encontró un vacío en la configuración del desafío de codificación. Engendró múltiples bots, lo que ralentizó el servidor; mientras tanto, él podría completar la misma tarea (la que otorgó la mayor cantidad de puntos) tantas veces como sea posible y asignar sus puntajes a una sola entrada, agregando continuamente al puntaje 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, haciendo 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 lo hizo pasar de 56,000 a 70,000 puntos y quedarse en primer lugar.
Una visualización de datos Flourish
Si bien no queríamos intervenir en la forma en que las personas interactuaban con el desafío, estos intentos ralentizaron el servidor en la medida en que el desafío era difícil de usar para otros usuarios, por lo que decidimos solucionar el vacío.
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 extender el número de premios otorgados a los mejores concursantes. Originalmente, se suponía que el primer premio, un par de AirPods, iría al único concursante superior. Al final, se les dieron AirPods a los que tenían los primeros seis lugares.
Principios humildes y finales feroces: algunas estadísticas del desafío de codificación de JavaScript
Incluso nuestros mejores anotadores tuvieron dificultades para comenzar. De hecho, de todas las personas que hicieron cinco intentos o más, el puntaje más alto para el primer intento de cualquiera fue 645, y el puntaje promedio para los primeros intentos en ese grupo fue de solo 25 puntos.
Al final del concurso, uno podría adivinar la estrategia que se intenta a partir del número total de intentos. Mientras que algunos fueron más eficientes que otros, el concursante superior de lejos tuvo el mayor número de intentos.
Avanzando
¿Qué depara el futuro?
Esta fue solo la primera iteración de 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 un intento de limitación 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 estamos tomando 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 futuros desafíos.
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 participaron en el concurso.