Build a weighted raffle and winner picker
Draw winners with configurable odds — entries can have weights, e.g. for early-bird bonus.
Published Dec 20, 2025
Northwind runs a giveaway at its open house, and not every entry should have the same odds — early-bird sign-ups were promised a better chance, and a flat draw would throw that promise away. A plain random pick treats everyone equally; what is needed is a weighted draw, where a heavier entry is more likely to come up.
This script does exactly that. Each entry on the raffle sheet carries a weight, and the script gives a weight-3 entry three times the chance of a weight-1 one by putting it into the draw three times. It then pulls distinct winners from that pool, so no one can win twice in the same draw.
What you’ll need
- A raffle sheet with a header row and columns named
nameandweight. One row per entrant. - A whole number in every
weightcell —1for a normal entry, higher for a better chance. Blank weights are treated as1.
The script
// The spreadsheet holding the raffle entries.
const RAFFLE_SHEET_ID = '1abcRaffleId';
// How many winners to draw by default.
const DEFAULT_WINNER_COUNT = 3;
/**
* Draws `count` distinct winners from the raffle sheet, with each
* entry's chance proportional to its weight.
*
* @param {number} count How many winners to pick.
* @return {string[]} The winning names.
*/
function pickWinners(count = DEFAULT_WINNER_COUNT) {
const [header, ...rows] = SpreadsheetApp.openById(RAFFLE_SHEET_ID)
.getSheets()[0]
.getDataRange()
.getValues();
// Stop early if there are no entries.
if (!rows.length) {
Logger.log('No raffle entries — nothing to draw.');
return [];
}
// Map column names to their index so we can read by name.
const col = Object.fromEntries(header.map((key, i) => [key, i]));
// 1. Expand the pool: a weight-N entry appears N times.
const pool = rows.flatMap((r) => {
const weight = parseInt(r[col.weight], 10) || 1;
return Array(weight).fill(r[col.name]);
});
// 2. Draw distinct winners by removing each pick from the pool.
const winners = [];
while (winners.length < count && pool.length) {
const index = Math.floor(Math.random() * pool.length);
const pick = pool.splice(index, 1)[0];
if (!winners.includes(pick)) winners.push(pick);
}
return winners;
}
/**
* Runs a draw and logs the winners. Run this one by hand on the day.
*/
function announceWinners() {
const winners = pickWinners(DEFAULT_WINNER_COUNT);
Logger.log('Winners: ' + winners.join(', '));
}
How it works
pickWinnersreads the raffle sheet and builds acollookup so columns can be read by name rather than position.- If there are no entry rows it logs a message and returns an empty list — no draw on an empty sheet.
- It expands the entries into a pool:
flatMaprepeats each name as many times as its weight, so a weight-3 entry occupies three slots. A blank or non-numeric weight falls back to1. - It draws winners in a loop. Each pass picks a random slot, removes it from
the pool with
splice, and adds the name towinnersonly if it is not already there. - Removing the slot and the duplicate check together guarantee distinct winners — a heavily weighted entrant has more chances to be drawn but still wins at most once.
announceWinnersis the convenience wrapper: it runs a draw and logs the result, so a draw on the day is one click.
Example run
Say the raffle sheet holds four entrants, with early-bird sign-ups on weight 3:
| name | weight |
|---|---|
| Priya | 3 |
| Tomas | 1 |
| Aoife | 3 |
| Wesley | 1 |
The pool holds eight slots — three each for Priya and Aoife, one each for Tomas
and Wesley. Drawing three winners with pickWinners(3) might log:
Winners: Aoife, Priya, Tomas
Across many draws Priya and Aoife each come up three times as often as Tomas or Wesley, but no name appears twice in a single draw.
Run it
This is an on-demand job — you run it once when it is time to draw:
- In the Apps Script editor, select
announceWinnersand click Run. - Approve the authorisation prompt the first time.
- Open Executions (or the log) to read the winning names.
To let non-editors draw without opening the script editor, add a custom menu so it appears in the spreadsheet:
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('Raffle')
.addItem('Pick winners', 'announceWinners')
.addToUi();
}
Watch out for
Math.randomis fine for a fun open-house draw, but it is not cryptographically secure. Do not use this where the result has to stand up to a formal audit.- Weights must be whole numbers. A weight of
2.5is floored to2byparseInt, and a weight of0removes that entrant from the draw entirely. - A very large total weight makes a very large pool. Thousands of entries each weighted in the hundreds will build a pool in the hundreds of thousands of items — keep weights modest.
- If
countis larger than the number of distinct entrants, the loop ends when the pool empties and simply returns fewer winners. - The draw is not recorded anywhere. If you need a paper trail, write the winners back to a sheet rather than only logging them.
Related
Build an OKR tracker with progress rollups
Aggregate Northwind's key-result progress into objective-level scores automatically.
Updated Jan 14, 2026
Build a recurring-task generator
Spawn new Northwind task rows on a daily or weekly cadence from a Recurring sheet.
Updated Jan 10, 2026
Build a multi-sheet search-and-jump tool
Find a value across every tab of a workbook and click through to the cell that contains it.
Updated Jan 7, 2026
Auto-rebuild grouped summaries on edit
Refresh pivot-style rollups the instant data changes — no manual recompute.
Updated Jan 4, 2026
Build a data-quality scorecard
Grade any sheet on completeness, validity, and freshness — surface gaps as a single score.
Updated Dec 31, 2025