Planning a large table tennis tournament is a classic optimization problem. You have a limited set of resources (tables), a large number of tasks (matches), and a complex web of dependencies (winners move on, losers drop out).
In TourneyPilot, we solve this using a custom implementation of Discrete-Time List Scheduling over a Dependency Graph (DAG). Here’s a look at the architecture.
1. The Challenge: It’s Not Just About Time
Simple calendar scheduling is easy (“Event A at 9:00”). Tournament scheduling is hard because:
- Dependencies: You can’t schedule the Semi-Final before the Quarter-Final.
- Resource Contention: 500 matches all want to “start as early as possible”, but you only have 20 tables.
- Hybrid Constraints: A user might say “Start Men’s Singles at 9:00 AM” (Manual Constraint) while leaving “Women’s Doubles” completely floating (Auto).
2. The Model: Scheduling as a Graph
First, we transform the hierarchical tournament structure (Tournament -> Competition -> Stage -> Round -> Match) into a flat Directed Acyclic Graph (DAG).
We define ScheduleNode and ScheduleEdge types effectively allowing us to abstract away the “sport” and focus on the “schedule”.

This transformation happens in GraphBuilder. It walks the domain entities and builds edges:
- Structural Edges:
Stage 1must finish beforeStage 2. - Logical Edges:
Round 1must finish beforeRound 2.
3. The Constraint Engine
Before we schedule a single match, we need to know the earliest possible start time for every node. This is where the Hybrid part comes in.
If a user manually pins the “Finals” to 4:00 PM, that constraint must propagate backwards. But more commonly, they pin a “Start Time” for a competition.

This allows mixed-mode planning. You can fix the “skeleton” of the day manually, and let the algorithm fill in the muscle.
4. The Algorithm: Discrete-Time List Scheduling
We generally use a List Scheduling approach. It’s a heuristic method that is near-optimal for this class of problem and, importantly, is very fast (O(N)).
Step 1: Time Discretization
We slice the day into “slots” (e.g., 5 minutes or 10 minutes).
Step 2: The Loop
The core loop in AutoScheduler simulates the passage of time:

Why Workload Priority?
We prioritize tasks with the highest “workload” (total match minutes required) because they are most likely to be the bottleneck. By scheduling the “heavy” items first, we fit the smaller, lighter items (like small groups) into the gaps left behind, maximizing table utilization.
Conclusion
By treating a tournament as a Graph problem, we can support:
- Correctness: Guarantees no match is scheduled before its players are ready.
- Efficiency: Maximizes table usage density.
- Flexibility: The “Hybrid” mix of manual control and automation gives organizers the best of both worlds.
This architecture is defined in packages/domain and runs entirely client-side, giving users instant feedback as they plan their event.