Lucky Draw
Overview
A Lucky Draw is an elimination-style draw that supports any participant count, not just powers of 2. Unlike standard elimination draws that use byes to pad to a power-of-2 draw size, lucky draws create rounds with non-power-of-2 matchUp counts and use a "lucky loser" mechanism to balance rounds when an odd number of winners is produced.
In the factory, this draw type is represented by the constant LUCKY_DRAW.
When a lucky draw is generated with a power-of-2 participant count, it falls back to a standard elimination tree since no lucky loser mechanism is needed.
Structure
A lucky draw for 11 participants produces rounds with the following matchUp counts:
Round 1: 6 matchUps (12 participants, but only 11 available -- 1 gets a bye equivalent)
Round 2: 3 matchUps
Round 3: 2 matchUps
Round 4: 1 matchUp (Final)
Key structural differences from standard elimination:
- No connecting lines between rounds. Unlike elimination draws where specific matchUp winners feed into specific next-round matchUps, lucky draws do not have fixed advancement paths between rounds.
- Non-power-of-2 matchUp counts. Rounds can have odd numbers of matchUps (e.g., 3, 5, 7).
- Pre-feed rounds. When a round has an odd number of matchUps, it produces an odd number of winners. This is one too many for a standard halving to work, so one loser from that round is selected to advance -- the "lucky loser."
Example: drawSize 25
The schematic below shows a 25-participant lucky draw structure. Pre-feed rounds (where a lucky loser is selected) are highlighted — these are rounds with an odd number of matchUps where the tournament director must select one loser to advance.
| Round | Participants | MatchUps | Pre-feed? | Lucky loser advances? |
|---|---|---|---|---|
| Round 1 | 26 (25 + 1 BYE) | 13 | Yes | Yes -- 1 of 13 losers |
| Round 2 | 14 | 7 | Yes | Yes -- 1 of 7 losers |
| Round 3 | 8 | 4 | No | No |
| Round 4 | 4 | 2 | No | No |
| Final | 2 | 1 | -- | -- |
Pre-Feed Rounds and Lucky Loser Selection
A pre-feed round is a round with an odd number of matchUps (and thus an odd number of winners). After a pre-feed round completes, the next round needs one additional participant to fill its draw positions. That participant is selected from the losers of the pre-feed round.
Selection Criteria
The system presents the tournament director with margin-of-defeat data at multiple levels of granularity. The tournament director uses whichever level provides sufficient differentiation:
- Set ratio -- often enough to distinguish losers (e.g., a 3-set loss vs a straight-set loss).
- Game ratio -- useful when set ratios are tied. Computed from
side1Score/side2Score. - Point ratio -- the most granular level, from
side1PointScore/side2PointScore. Only needed when higher-level ratios don't differentiate.
When no statistical differentiation exists at any level, the tournament director makes the selection at their discretion (e.g., by coin flip).
The system ranks eligible losers by margin but does not auto-select; the tournament director always makes the final decision. Participants who lost by walkover or default have no meaningful margin and are excluded from the ranked list.
Per-Round vs Cumulative Margin
By default, margin is calculated based only on the matchUp in which the participant lost. The cumulativeMargin option considers all prior rounds' matchUps for each participant, which can be useful in formats where consistency across rounds should factor into the selection.
The getLuckyDrawRoundStatus method accepts a cumulativeMargin boolean parameter to toggle between these modes.
API
Generating a Lucky Draw
const { drawDefinition } = engine.generateDrawDefinition({
drawSize: 11,
drawType: 'LUCKY_DRAW',
});
Checking Round Status
Use getLuckyDrawRoundStatus to determine which rounds need a lucky loser selection and to get the ranked list of eligible losers:
const { rounds } = engine.getLuckyDrawRoundStatus({ drawId });
const preFeedRound = rounds.find((r) => r.needsLuckySelection);
// preFeedRound.eligibleLosers is sorted by margin (narrowest loss first)
The returned rounds array contains objects with the following properties:
| Property | Type | Description |
|---|---|---|
roundNumber | number | The round number |
matchUpsCount | number | Total matchUps in this round |
completedCount | number | Number of completed matchUps |
isComplete | boolean | Whether all matchUps in the round are complete |
isPreFeedRound | boolean | Whether this round has an odd number of matchUps (not final round) |
needsLuckySelection | boolean | Whether this round is complete, is a pre-feed round, and the next round has an open position |
nextRoundHasOpenPosition | boolean | Whether the subsequent round has an unfilled draw position |
eligibleLosers | array | Ranked list of losers (only present when needsLuckySelection is true) |
Each entry in eligibleLosers contains:
| Property | Type | Description |
|---|---|---|
participantId | string | The losing participant's ID |
participantName | string | The losing participant's name |
matchUpId | string | The matchUp in which they lost |
scoreString | string | The score of the matchUp |
margin | number | Margin of defeat (0-1, higher = closer match; excluded for walkovers/defaults) |
gameDifferential | number | Difference in games won between winner and loser |
setsWonByLoser | number | Number of sets won by the losing participant |
Advancing a Lucky Loser
After reviewing the eligible losers, advance the selected participant:
engine.luckyDrawAdvancement({
drawId,
participantId: preFeedRound.eligibleLosers[0].participantId,
roundNumber: preFeedRound.roundNumber,
});
The luckyDrawAdvancement method:
- Validates that the draw is a lucky draw.
- Verifies the specified round is a pre-feed round that needs selection.
- Confirms the participant is an eligible loser from that round.
- Places the participant into the open draw position in the next round.
An optional selectionBasis parameter ('MARGIN', 'RANDOM', or 'MANUAL') can be provided for telemetry purposes.
Calculating Match Margin
The calculateMatchUpMargin method returns detailed margin data for any matchUp:
const {
margin, // Combined 0-1 value (undefined for walkovers/defaults)
setRatio, // Loser sets / total sets decided
gameRatio, // Loser games / total games (undefined if no game data)
pointRatio, // Loser points / total points (undefined if no point data)
gameDifferential, // Winner games - loser games
setsWonByLoser,
setsWonByWinner,
} = engine.calculateMatchUpMargin({ matchUpId });
Complete Workflow Example
// 1. Generate a lucky draw for 11 participants
engine.generateDrawDefinition({
drawSize: 11,
drawType: 'LUCKY_DRAW',
eventId,
drawName: 'Main Draw',
});
// 2. After Round 1 completes (6 matchUps, odd count = pre-feed round),
// check which round needs a lucky selection
const { rounds } = engine.getLuckyDrawRoundStatus({ drawId });
const preFeedRound = rounds.find((r) => r.needsLuckySelection);
if (preFeedRound) {
console.log('Eligible losers (ranked by margin):');
for (const loser of preFeedRound.eligibleLosers) {
console.log(` ${loser.participantName}: margin=${loser.margin}, score=${loser.scoreString}`);
}
// 3. Tournament director selects a lucky loser (here, the one with narrowest margin)
const selected = preFeedRound.eligibleLosers[0];
engine.luckyDrawAdvancement({
drawId,
participantId: selected.participantId,
roundNumber: preFeedRound.roundNumber,
});
}
Related
- Adaptive Draw -- Multi-structure variant using lucky draw logic for any participant count
- Draw Types Overview -- List of all pre-defined draw types
- Single Elimination -- Standard power-of-2 knockout draw
- Draw Links -- How structures connect in multi-structure draws
- Generation Governor -- API reference for
generateDrawDefinition