Under Time Budget

Apps Script kills a execution around the 6-minute wall. If your loop is still mid-UrlFetch when that happens, you lose the chance to write a checkpoint, clear a lock, or stamp “partial”. underTimeBudget(startedAt, budgetMs?) returns true while you’re still under the budget (default 5.5 minutes / 330000 ms), so the loop can stop cleanly, save progress, and let the next trigger finish the job.

When to use

  • Row / file / page loops inside a time-driven or menu job that might overrun
  • Checkpointed imports and “process the queue until we should stop” patterns
  • Any job that needs 15–30 seconds at the end to flush, unlock, and stamp status

How to use this snippet

/** * True while wall time since startedAt is still under the budget. * Default budget: 330000 ms (5.5 minutes) — leaves headroom under the ~6 min limit. * * @param {Date|number} startedAt Date or epoch ms when the run began * @param {number=} budgetMs override budget in milliseconds * @return {boolean} */ function underTimeBudget(startedAt, budgetMs) { var budget = budgetMs == null ? 330000 : Number(budgetMs); var start = startedAt instanceof Date ? startedAt.getTime() : Number(startedAt); if (!isFinite(start) || !isFinite(budget) || budget <= 0) return false; return (Date.now() - start) < budget; }

Example

function processQueueUntilBudget() { var started = new Date(); var lock = LockService.getScriptLock(); if (!lock.tryLock(5000)) return; // someone else is running try { var sheet = SpreadsheetApp.getActive().getSheetByName('Queue'); var rows = claimPendingRows_(sheet); // your claim helper var i = 0; while (i < rows.length && underTimeBudget(started)) { // Optional: also honor a Cancel flag from a sidebar // if (isCancelRequested('queueJob')) break; processOne_(rows[i]); markDone_(sheet, rows[i]); i++; } if (i < rows.length) { // Stopped early on purpose — next trigger continues stampStatus_('Partial: ' + i + ' of ' + rows.length + ' this run'); } else { stampStatus_('OK: ' + i + ' rows'); } } finally { lock.releaseLock(); } }

Tips:

  • Capture startedAt once at the top of the run, not inside the loop.
  • Shorter budget (e.g. 4 minutes) if each iteration can be slow or you need a long teardown.
  • This is wall-clock time, not CPU. A single hung UrlFetch can still blow the limit — keep per-call timeouts tight.
  • Pair with a checkpoint (Script Property or a cursor cell) so the next trigger resumes where you left off.
  • Pair with a cancel flag when a human might hit Cancel mid-run.

Tip: NitroGAS Co-Pilot can wrap an existing for loop with var started = new Date() and an underTimeBudget(started) check, plus a one-line checkpoint write when the loop breaks early.

Happy Coding!

NitroGAS Chrome Extension

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

Get the Extension