AWS Builder Center

Building a Real-Time Typing Tournament Platform with AWS

Explore how a real-time typing tournament can be designed with AWS using Amazon Cognito, API Gateway, Lambda, DynamoDB, and WebSockets to manage players, matches, rankings, and tournament state.

Estudiante - Ing. Sistemas Computacionales

Building a Real-Time Typing Tournament Platform with AWS

Description: Explore how a real-time typing tournament can be designed with AWS using Amazon Cognito, API Gateway, Lambda, DynamoDB, and WebSockets to manage players, matches, rankings, and tournament state.

From Typing Test to Tournament

Real-time applications become significantly more interesting when multiple users need to interact with the same system at the same time.
While building Typerace, a web application for competitive typing tournaments, I encountered this problem directly.
The application was designed around a simple idea:
Players compete by typing English phrases as quickly and accurately as possible.
However, turning that idea into a tournament system requires much more than displaying a typing test.
The application needs to manage users, matches, rounds, rankings, player progress, and the current state of the tournament.
This article explores how a system like Typerace can be designed using AWS services to create a scalable and real-time architecture.

The Architecture

The original Typerace prototype was built using React, Vite, Tailwind CSS, Framer Motion, and Supabase.
For an AWS-oriented architecture, the system could be redesigned using:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Users
|
v
React Frontend
|
v
Amazon Cognito
|
v
Amazon CloudFront
|
v
Amazon API Gateway
/ \
/ \
v v
AWS Lambda WebSocket API
| |
+-------+-------+
|
v
DynamoDB
|
v
Tournament State
Each service has a specific responsibility.
Amazon Cognito handles authentication.
Amazon CloudFront can distribute the frontend.
Amazon API Gateway exposes HTTP APIs and WebSocket communication.
AWS Lambda contains the tournament logic.
Amazon DynamoDB stores players, matches, rounds, and tournament state.

Authentication with Amazon Cognito

A tournament application needs to know who each player is.
Amazon Cognito can provide user authentication without requiring the application to implement the complete authentication system itself.
A simplified flow would be:
1
2
3
4
5
6
7
8
9
10
11

Player
|
v
Amazon Cognito
|
v
Authentication Token
|
v
Typerace API
After authentication, the frontend can include the user's identity when communicating with the backend.
The backend can then associate actions with a specific player.
For example:
1
2
3
4
5
{
"userId": "player_102",
"matchId": "match_05",
"action": "finish"
}
This is particularly important for tournament systems because the backend should never blindly trust a player-provided identifier.

Managing Tournament State

One of the most important parts of Typerace is tournament state.
The backend needs to know whether a tournament is:
1
2
3
waiting
active
finished
Each match can also have its own state:
1
2
3
4
pending
ready
running
completed
A simplified DynamoDB item could look like:
1
2
3
4
5
6
7
8
9
10
11
{
"tournamentId": "tournament_001",
"round": 2,
"matchId": "match_05",
"status": "running",
"players": [
"player_101",
"player_102"
],
"startedAt": "2026-10-05T20:00:00Z"
}
DynamoDB is useful for this type of application because tournament entities can be stored as individual items and retrieved using keys designed around the application's access patterns.

Real-Time Communication

The most interesting challenge is real-time communication.
Imagine two players participating in the same match.
Player A finishes first.
The system needs to update the match and potentially notify the other participants that the result has changed.
A traditional request-response architecture might require the frontend to repeatedly ask:
"Did anything change?"
This is inefficient.
Instead, a WebSocket connection can allow the server to push updates to connected clients.
The flow becomes:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Player A
|
v
WebSocket
|
v
API Gateway
|
v
Lambda
|
v
Tournament State
|
v
WebSocket
|
+------> Player A
|
+------> Player B
|
+------> Admin
This is particularly useful for tournament events such as:
  • Match started
  • Player finished
  • Player eliminated
  • Round completed
  • Next round generated
  • Tournament completed

Validating Match Results

One important lesson from developing Typerace was that the backend cannot simply trust the data sent by the frontend.
For example, a player should not be able to send:
1
2
3
4
5
{
"playerId": "player_999",
"matchId": "match_05",
"status": "finished"
}
and automatically become the winner.
The backend needs to verify:
  1. The player belongs to the match.
  2. The match is currently active.
  3. The player has not already finished.
  4. The tournament is in the correct round.
  5. The submitted result is valid.
  6. The player is authorized to perform the action.
Conceptually:
1
2
3
4
5
6
7
function canFinishMatch(player, match) {
return (
match.status === "running" &&
match.players.includes(player.id) &&
!player.finished
);
}
The actual validation would live on the server rather than relying on frontend logic.
This is a fundamental principle for multiplayer and competitive applications:
The client can request an action, but the server decides whether that action is valid.

Tournament Progression

Once a match finishes, the backend can determine which player advances.
For example:
1
2
3
4
5
6
7
8
9
10
Round 1
|
+-- Player A vs Player B → Player A wins
|
+-- Player C vs Player D → Player D wins
|
v
Round 2
|
+-- Player A vs Player D
This logic can be handled by Lambda functions responsible for tournament state transitions.
The backend can update DynamoDB and notify connected clients through API Gateway WebSockets.
This creates a complete event flow:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Match Completed
|
v
Validate Result
|
v
Update Match
|
v
Determine Winner
|
v
Create / Update Next Round
|
v
Notify Players

Ranking and Results

A tournament also needs a way to present results.
The system could maintain information such as:
1
2
3
4
5
6
7
8
{
"playerId": "player_101",
"wins": 3,
"losses": 1,
"averageWPM": 74,
"accuracy": 96.8,
"eliminated": false
}
The frontend can use this information to display leaderboards and tournament results.
Because ranking calculations can become more complex as the application grows, keeping tournament state and player statistics in a structured backend makes it easier to extend the system later.

Serverless Architecture

One advantage of this architecture is that the main application logic can be implemented without managing traditional servers.
The request flow could be:
1
2
3
4
5
6
7
8
9
10
11
HTTP / WebSocket
|
v
API Gateway
|
v
AWS Lambda
|
+----> DynamoDB
|
+----> Tournament Logic
Lambda functions can handle operations such as:
1
2
3
4
5
6
7
createTournament()
joinTournament()
startTournament()
startMatch()
finishMatch()
advancePlayer()
getRanking()
This makes the architecture modular and allows individual operations to evolve independently.

Observability

Real-time applications are difficult to debug if we cannot see what is happening.
Amazon CloudWatch could be used to monitor:
  • Lambda errors
  • API latency
  • WebSocket failures
  • Tournament events
  • Invalid requests
  • Authentication failures
  • Database errors
For example, if players report that a match did not advance to the next round, logs can help determine whether the problem occurred during validation, database updates, or the WebSocket notification.
Observability is especially important in real-time systems because failures are not always visible from the frontend.

Security

Competitive applications also require careful security design.
AWS IAM can control permissions between services, while Amazon Cognito can manage user identity.
The backend should also validate every important action.
For example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Frontend
|
| "I finished the match"
v
API Gateway
|
v
Lambda
|
+-- Is the user authenticated?
|
+-- Is the user in this match?
|
+-- Is the match running?
|
+-- Has the player already finished?
|
v
Update DynamoDB
This prevents important tournament rules from being enforced only in the browser.

Challenges

Building Typerace made one thing particularly clear:
Real-time applications are state-management problems.
It is not enough to create a nice interface.
The backend needs to maintain a consistent state while different users perform actions simultaneously.
Other challenges include:
  • Preventing duplicate events
  • Handling users disconnecting
  • Keeping match state consistent
  • Validating player actions
  • Updating the next tournament round
  • Synchronizing the frontend with backend state
  • Making the interface responsive across different screen sizes
These problems become even more important as the number of concurrent players increases.

From Prototype to Cloud

The original Typerace project was built as a functional web application using React, Vite, Tailwind CSS, Framer Motion, and Supabase.
Designing an AWS architecture provides an opportunity to rethink the system around independently scalable cloud services.
The resulting architecture could look like:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
React Application
|
v
Amazon CloudFront
|
v
Amazon Cognito
|
v
Amazon API Gateway
/ \
/ \
v v
REST API WebSocket API
| |
v v
AWS Lambda <---------+
|
v
DynamoDB
|
v
Tournament State
This architecture separates presentation, authentication, communication, business logic, and persistence.

What I Learned

Typerace started as a typing game, but building tournament functionality changed the problem completely.
The application became a distributed system where multiple users interact with shared state.
That experience reinforced an important software engineering principle:
A real-time application is only as reliable as its state management and validation.
AWS provides services that can address different parts of this problem, but the architecture still needs to be designed around the application's actual requirements.
The important question is not:
"Which AWS services should I use?"
It is:
"What state does my application need to manage, and how should that state move through the system?"

Conclusion

A typing tournament may look simple from the user's perspective.
Behind the interface, however, it requires authentication, real-time communication, state management, validation, persistence, and synchronization between multiple clients.
Designing Typerace around AWS services such as Amazon Cognito, Amazon API Gateway, AWS Lambda, Amazon DynamoDB, Amazon CloudFront, and Amazon CloudWatch provides a path toward a scalable cloud architecture.
More importantly, the project demonstrates how a relatively simple idea can become an interesting distributed application once multiple users need to interact with the same system in real time.
For me, Typerace was not only a project about typing speed.
It was an opportunity to learn more about real-time systems, backend architecture, cloud computing, and the importance of keeping application state under control.
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article