appscript.dev
Automation Beginner Sheets

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 name and weight. One row per entrant.
  • A whole number in every weight cell — 1 for a normal entry, higher for a better chance. Blank weights are treated as 1.

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

  1. pickWinners reads the raffle sheet and builds a col lookup so columns can be read by name rather than position.
  2. If there are no entry rows it logs a message and returns an empty list — no draw on an empty sheet.
  3. It expands the entries into a pool: flatMap repeats each name as many times as its weight, so a weight-3 entry occupies three slots. A blank or non-numeric weight falls back to 1.
  4. It draws winners in a loop. Each pass picks a random slot, removes it from the pool with splice, and adds the name to winners only if it is not already there.
  5. 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.
  6. announceWinners is 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:

nameweight
Priya3
Tomas1
Aoife3
Wesley1

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:

  1. In the Apps Script editor, select announceWinners and click Run.
  2. Approve the authorisation prompt the first time.
  3. 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.random is 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.5 is floored to 2 by parseInt, and a weight of 0 removes 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 count is 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