Le besoin de vitesse : une rétrospective Toptal JavaScript Coding Challenge

Publié: 2022-03-11

Toptal a lancé l'application Web du défi de codage JavaScript comme moyen d'attirer les gens vers nos cabines de conférence. Voyant le succès rencontré, nous avons décidé de faire un pilote sur le web, ouvert à tous dans notre communauté et leurs réseaux.

Une capture d'écran du JavaScript Speed ​​Coding Challenge de Toptal, montrant l'une des premières questions. Une fonction nommée "double" est fournie, et son corps consiste en un commentaire, "return x doublé".

Au moment du lancement, nous avions encouragé les développeurs motivés à trouver des moyens créatifs d'obtenir un score élevé dans le défi global du codage JavaScript. Certains des facteurs clés de succès des grands freelances indépendants sont la capacité de sortir des sentiers battus et de trouver des moyens créatifs de travailler dans le cadre d'un ensemble de contraintes.

Questions du défi de codage JavaScript

Le gant consistait en un certain nombre de questions JavaScript, semblables à celles qui pourraient être utilisées comme questions d'entretien, allant des questions de défi JavaScript vraiment basiques :

 box.double = function double (x) { //return x doubled };

…aux plus intermédiaires :

 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) };

Nous voulions que les développeurs débutants et avancés s'amusent et invitent leurs amis et collègues à rivaliser avec eux pour les meilleurs scores. Les questions étaient triées par points, de sorte que même les développeurs débutants pouvaient marquer des points. L'ordre des questions ayant le même nombre de points était aléatoire, de sorte que l'expérience était légèrement différente à chaque fois, tandis que l'ensemble des questions restait le même tout au long de la semaine.

L'objectif était d'accomplir autant de tâches que possible dans le délai de trois minutes. Au cas où quelqu'un trouverait un moyen de terminer toutes les tâches du défi de codage JavaScript, 10 points seraient attribués pour chaque seconde restante. Nous avons autorisé plusieurs tentatives pour relever le défi.

La première chose à laquelle nous nous attendions est que les gens prennent un peu de temps pour résoudre les questions à un rythme tranquille, puis copient et collent les réponses dans l'application Web.

Après une heure et 40 minutes de lancement du défi, la première personne a suivi cette approche et a obtenu les 1445 points maximum autorisés par l'application, plus quelques points supplémentaires que nous avons attribués pour chaque seconde restante.

Un tournant dans le défi du codage JavaScript

Avec l'approche copier-coller, les meilleurs candidats n'avaient plus rien à gagner à se concentrer sur le codage des réponses eux-mêmes. Au lieu de cela, ils se sont concentrés sur la rapidité de leurs compétences en automatisation.

L'approche la plus simple à ce stade était d'écrire du JavaScript qui résoudrait chaque tâche en attendant sur une boucle jusqu'à ce que les boutons soient prêts, et de le copier-coller dans la console du navigateur :

 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()

Cette partie pourrait également être automatisée à l'aide d'outils d'automatisation classiques tels que Selenium. Mais un moyen plus rapide serait d'automatiser l'utilisation de l'API, en envoyant les solutions aux tâches :

 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)

Une chose à noter est que, dans cette version du défi speedcoding, le code a été testé uniquement côté client. Pour cette raison, il était possible d'envoyer simplement les réponses aux cas de test au lieu du code. Cela a permis une optimisation et de gagner quelques millisecondes côté client.

Au bout de trois jours, la compétition a vraiment commencé à s'intensifier, le nombre de tentatives par seau de trois heures passant d'un coup de moins de 1 000 à près de 100 000.

Pendant trois jours, les scores sont restés les mêmes. Certaines personnes écrivaient des micro-optimisations pour leur code, et plusieurs personnes soumettaient leurs solutions en boucle, espérant que le serveur deviendrait moins encombré afin qu'ils puissent gagner quelques points d'avance. Nous devions avoir une grosse surprise.

Briser le score maximum du JavaScript Coding Challenge

Tout d'abord, faisons quelques calculs rapides : nous avons obtenu un total de 1 445 points pour l'achèvement de toutes les tâches, et une allocation de temps de 180 secondes. Si nous attribuons 10 points par seconde restante dans l'application, le score théorique maximum réalisable serait de 3245 - dans le cas de la soumission de toutes les réponses instantanément.

L'un de nos utilisateurs a obtenu un score de plus de 6000, qui n'a cessé de croître au fil du temps.

Comment quelqu'un a-t-il pu obtenir un score aussi élevé ?

Après un bref examen, nous avons découvert ce qui s'était passé. Notre meilleur concurrent, un développeur full-stack professionnel et Toptaler avec plus de 15 ans d'expérience en programmation compétitive, a trouvé une faille dans la configuration du défi de codage. Il a engendré plusieurs robots, ce qui a ralenti le serveur ; pendant ce temps, il pouvait accomplir la même tâche (celle qui attribuait le plus de points) autant de fois que possible et attribuer leurs scores à une seule entrée, en ajoutant continuellement au score de cette entrée.

Ce n'était pas contraire aux règles, car nous permettions des solutions créatives ; cependant, la méthode particulière qu'il utilisait rendait le serveur considérablement plus occupé, ralentissant les requêtes réseau pour tout le monde. La première chose que nous avons faite a été d'augmenter la puissance de notre serveur, ce qui l'a fait passer de 56 000 à 70 000 points et rester à la première place.

Bien que nous ne voulions pas intervenir dans la façon dont les gens interagissaient avec le défi, ces tentatives ont ralenti le serveur dans la mesure où le défi était difficile à utiliser pour les autres utilisateurs, nous avons donc décidé de corriger la faille.

Le correctif a rendu impossible pour d'autres personnes d'obtenir le même score au cours de la dernière journée du défi de codage JavaScript. Pour cette raison, nous avons décidé d'étendre le nombre de récompenses décernées aux meilleurs candidats. À l'origine, le premier prix - une paire d'AirPods - était censé revenir au seul meilleur candidat. Au final, des AirPods ont été donnés à ceux détenant les six premières places.

Des débuts humbles et des fins féroces : quelques statistiques sur le défi de codage JavaScript

Même nos meilleurs buteurs ont eu du mal à démarrer. En fait, de toutes les personnes qui ont fait cinq tentatives ou plus, le score le plus élevé pour la première tentative de quiconque était de 645, et le score médian pour les premières tentatives dans ce groupe n'était que de 25 points.

À la fin du concours, on pouvait deviner la stratégie tentée à partir du nombre total de tentatives. Alors que certains étaient plus efficaces que d'autres, le meilleur concurrent de loin avait le plus grand nombre de tentatives.

Un graphique à barres logarithmique combiné montrant les scores les plus élevés des 20 meilleurs concurrents et le nombre total de tentatives. Alors que le nombre de tentatives les plus élevées est allé au meilleur buteur, les deuxième, troisième et quatrième tentatives les plus élevées sont allées respectivement aux 13e, neuvième et deuxième places. Exactement la moitié des 20 premiers, y compris les concurrents aux quatrième et sixième places, ont effectué moins de 1 000 tentatives. Deux concurrents avaient moins de 100 tentatives, et un autre avait même moins de 10.

Avancer

Que réserve l'avenir?

Ce n'était que la première itération du défi de codage JS. Nous voulons relever de nombreux autres défis de programmation JavaScript à l'avenir, tirer les leçons des exécutions précédentes et les rendre encore plus excitantes. La première chose que nous voulons faire est de mettre en œuvre la limitation des tentatives pour limiter le nombre de soumissions. Nous souhaitons également aller au-delà des défis de codage en JavaScript, en les rendant disponibles dans un large éventail de langages de programmation.

Enfin, alors que nous prenons des mesures pour rendre le défi de codage JavaScript plus sécurisé, nous prévoyons de continuer à autoriser l'utilisation de bots et d'autres approches créatives pour les défis futurs.


Remerciements particuliers à Pavel Vydra, Anton Andriievskyi, Tiago Chilanti et Matei Copot pour leurs contributions au défi et à cet article, et à @Zirak pour le projet open source qui a constitué la base de l'application du concours. De même, merci à tous ceux qui ont participé et organisé le concours.