Next Round Robin

nextRoundRobin(key, people, opts) returns the next person in a rotation and remembers that choice in Script Properties. A script lock wraps the read-and-write so two form submits in the same second cannot both get the same person. Pass {out: [...]} to skip people who are away.

When to use

  • Assigning new leads, tickets, or reviews evenly
  • An onFormSubmit or append handler that must not skip or double-assign
  • A rotation that changes when someone is on vacation

How to use this snippet

/** * Return the next person in a named rotation. * Stores the last assignee in Script Properties under roundRobin:<key>. * Uses LockService.getScriptLock() around the read-check-write. * opts.out is a list of people to skip (case-insensitive). * * @param {string} key rotation id, e.g. 'leads' * @param {string[]} people full roster, in order * @param {{out?: string[], lockMs?: number}} [opts] * @return {string} */ function nextRoundRobin(key, people, opts) { opts = opts || {}; var outSet = {}; (opts.out || []).forEach(function (p) { outSet[String(p).toLowerCase()] = true; }); var lock = LockService.getScriptLock(); if (!lock.tryLock(opts.lockMs || 30000)) { throw new Error('nextRoundRobin: could not acquire script lock'); } try { var props = PropertiesService.getScriptProperties(); var propKey = 'roundRobin:' + key; var last = props.getProperty(propKey); var start = 0; if (last) { for (var i = 0; i < people.length; i++) { if (String(people[i]) === last) { start = i + 1; break; } } } for (var n = 0; n < people.length; n++) { var cand = people[(start + n) % people.length]; if (cand && !outSet[String(cand).toLowerCase()]) { props.setProperty(propKey, String(cand)); return cand; } } throw new Error('nextRoundRobin: no one available'); } finally { lock.releaseLock(); } }

Example

function assignLead(e) { var team = ['Ann', 'Bob', 'Cam']; var out = ['Cam']; // vacation var owner = nextRoundRobin('leads', team, { out: out }); // write owner onto the new row... }

Notes

  • The lock is a script lock, because the cursor lives in Script Properties (project-wide), not in one spreadsheet. If the lock times out, the function throws — it does not silently assign.
  • Walking starts just after the last stored person, wraps around, and skips anyone in opts.out. If the last person left the roster, it starts from the top.
  • If everyone is out, it throws nextRoundRobin: no one available.
  • Script Properties are per project. Two spreadsheets bound to the same project share the cursor; copies with their own project do not.

Happy Coding!

NitroGAS Chrome Extension

Want this snippet handy inside the Apps Script editor? NitroGAS gives you free snippets — plus optional Co-Pilot when you want a boost (Day Pass — $3, or 1-week, 1-month, or yearly passes — no subscription). Happy Coding!

Get the Extension