Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?

Backend Coding Challenge

Design an API that save game speedruns and serves a leaderboard.

Background

In several games, finishing the game is deemed just too easy for some gamers, so they decide to see how fast they can finish the entire game. In fact, speedruns became so popular that many players compete with other players to see if they can break each other records, even if that means beating the time by just a couple of seconds. We want to create an API that can registers those records and also serve a leaderboard.

Requirements

We need an api that receives POST HTTP requests to register a new speedrun attempt to the database. We also want to provide a list of all speedruns in a way which is possible to filter and sort so it can serve as a leaderboard. For privacy compliance a user should be able to delete its speedruns. Updating a speedrun should not be allowed, as it probably would be considered cheating. Last but not least, it should be possible also to register new speedrun attempts using message queue events (like Kafka, RabbitMQ or Google Pub/Sub).

Tasks

You have to create a Backend using Node.js that implements the following tasks. They are ordered in order of importance. You don't have to do all tasks, but the less you do, the more perfectly implemented your code must be. Unit tests for the core business logic of each task is mandatory.

Register Speedruns (HTTP Endpoint)

You should be able to receive a speedrun data via POST endpoints and save them on the database. Now there is a catch: for better comparison purposes you should also save to this speedrun data what is the average (arithmetic mean) finishing time for that game, the amount of speedruns already saved for that game and also what was the fastest finishing for that game. The goal with those attributes is to compare how this speedrun performed in comparison to the rest at the time it was saved, so you do not and should not update those values after saving them. Also, after saving, we should generate a UUID for that speedrun and return this on the response payload, as this will be the ID that the user can use to delete the speedrun. Here is an example payload of request and the expected response. These does not need to be followed to the letter, but all data of the examples must be present in your solution.

Request POST /api/speedruns

{
  "game": "Bloodborne",
  "player": "A very fast player",
  "finishing_time": 2056 // in seconds
}

Response

{
  "id": "73985501-25cc-49dd-ba82-3a67297dc6b8",
  "game": "Bloodborne",
  "player": "A very fast player",
  "finishing_time": 2056,
  "average_finishing_time": 2459,
  "speedruns_count": 130,
  "record_finishing_time": 1956
}

Register Speedruns (Message Queue)

We also want to be able to receive data through message queues so we can support high volume of data income. You should listen for new-speedrun events / topics (depending on the lib chosen), process the data just as you would with the http request (calculating average, count and fastest finishing time) and then publish on the queue the response on the event / topic speedrun-created. The data expected to be received should be the same one used on the HTTP request payload, and the response published should be exactly the same as the one sent as a response to the HTTP request.

Serve Leaderboard

It should be possible to retrieve the list of speedruns. The endpoint should at least support filtering by game and sorting by finishing_time. Any other filter or sorting you think that can be helpful is also welcome. You should omit the ID field as it is the key to delete the result and should not be public. Here is an example request and its expected response. These again do not need to be followed to the letter, and you can architect the filter and sort params the way you think it works better. It just needs to have the same functionality as the one bellow.

Request GET /api/speedruns?game=Bloodborne&$sort[finishing_time]=ASC Response

[
  {
    "game": "Bloodborne",
    "player": "Fastest player",
    "finishing_time": 1956,
    "average_finishing_time": 2567,
    "record_finishing_time": 1956,
    "speedruns_count": 110
  },
  {
    "game": "Bloodborne",
    "player": "A very fast player",
    "finishing_time": 2056,
    "average_finishing_time": 2459,
    "record_finishing_time": 1956,
    "speedruns_count": 130
  },
  {
    "game": "Bloodborne",
    "player": "Master Chief",
    "finishing_time": 2057,
    "average_finishing_time": 2490,
    "record_finishing_time": 2000,
    "speedruns_count": 97
  }
]

Delete speedrun

The user should be in control of his data, otherwise we could get a very big fine operating on Europe. For that, the user can delete its speedruns by using the ID provided on creation endpoint. This is far from a good solution but it is the one enough for now. Here are some example request and response.

Request DELETE /api/speedruns/73985501-25cc-49dd-ba82-3a67297dc6b8 Response

{
    "id": "73985501-25cc-49dd-ba82-3a67297dc6b8",
    "game": "Bloodborne",
    "player": "Fastest player",
    "finishing_time": 1956,
    "average_finishing_time": 2567,
    "record_finishing_time": 1956,
    "speedruns_count": 110
  }

Implementation

  • Should be based on Node.js. Typescript is allowed.
  • Use message queues for the event driven feature. Kafka, Google Pub/Sub and RabbitMQ are some of the options available.
  • Use SQL database for persistence. MySQL and PostgreSQL are some of the options.
  • The API should communicate in JSON
  • Isolated and testeable business logic
  • At least unit tests

Plus points

  • Validate the data coming from the HTTP request
  • Documentation
  • Usage of design patterns like Dependency Injection
  • Docker setup and instructions
  • Good application logging

Hints / Tips

  • This is about the features you deliver, but also on how you architect your backend. It should use best practices and design patterns to facilitate maintenance.
  • It is ok if you do not manage to do everything. If you find yourself with less time than necessary to finish everything, maybe it is better to spend the remaining time perfecting the features you already have than creating incomplete features.
  • Try to not repeat yourself. If you think a piece of code could be used in more than one place, maybe you could move it to a proper service file and just use it there.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.