// Apps Script

Pause a script with Utilities.sleep (and when not to).

Utilities.sleep pauses Apps Script execution for milliseconds — useful for tight rate-limit gaps, but a long sleep burns your 6-minute execution budget. Learn when to use it and when to reach for a trigger instead.

I need to add a delay between API calls in my Apps Script so I stop hitting rate-limit errors, and I want to know how much sleep I can afford before it costs me my whole execution window.

The script

copy · paste · trigger
rate-limit-backoff.gs
Apps Script
// Throttle Sheets API writes to stay under the 60-req/min quota
function exportRowsWithBackoff(rows) {
  var BATCH = 10;
  var PAUSE_MS = 1100; // just over 1 s keeps us under 60 req/min

  for (var i = 0; i < rows.length; i++) {
    writeRow(rows[i]);

    var endOfBatch = (i + 1) % BATCH === 0;
    var notLastRow = i < rows.length - 1;

    if (endOfBatch && notLastRow) {
      Utilities.sleep(PAUSE_MS);
    }
  }
}

function writeRow(row) {
  // placeholder: your Sheets or external API call goes here
  Logger.log('writing row: ' + row);
}

Need a variant? Gnaw writes a custom version from one sentence — fields, triggers, edge cases handled.

Walkthrough

What Utilities.sleep actually does to your clock

Utilities.sleep(milliseconds) blocks the current execution thread for the duration you specify. That sounds obvious, but the implication is easy to miss: Apps Script gives every run a hard 6-minute wall-clock limit, and sleep counts toward that limit. A Utilities.sleep(60000) call — one minute — eats one-sixth of your entire budget without doing a single unit of work.

The upside is precision. For small delays in the 200ms–2000ms range, sleep is the right tool. The Sheets API allows roughly 60 write requests per minute per project. If you fire all 60 in the first three seconds, the next call returns a 429 and your script either crashes or you waste time in error-handling. Sleeping 1,100ms between batches of ten keeps you safely under the quota with no retry logic needed.

The first time I hit a Sheets quota wall, I added a 5-second sleep between every row. On a 200-row sheet that was 1,000 seconds of sleep — the script timed out at 360 seconds every single run. Batching the sleep to once per 10 rows brought the total pause time down to ~22 seconds.

Structuring the delay so it does not compound

The pattern in the snippet above sleeps only at the boundary of each batch, not on every iteration. That matters because the cost of the sleep multiplies by how many times you call it. With 200 rows and a 10-row batch, you sleep 19 times (not on the last batch since there is nothing left to rate-limit against). 19 × 1,100ms = roughly 21 seconds total. That is well within the 6-minute window and leaves ample time for the actual API calls.

One thing worth checking: if your rows array can be very large — say, 500 or 1,000 rows — do the arithmetic before assuming sleep will hold. 99 pauses × 1,100ms = 108 seconds just in sleep, plus API call latency. At that scale you need a different pattern entirely: chunk the work into multiple time-triggered runs, each processing a safe-sized slice and persisting its position in PropertiesService.

Utilities.sleep accepts an integer number of milliseconds. It does not accept sub-millisecond values, and values above 300,000 (5 minutes) will consume most of your execution budget in a single call — Apps Script will not warn you.

When to use a trigger instead of sleep

If the work you are waiting on is external — a webhook to arrive, a third-party export to finish, a human to respond — sleep is the wrong tool unconditionally. You cannot sleep for 10 minutes because the execution limit is 6. You cannot sleep-poll every second for 5 minutes because that is 300,000ms of wasted quota.

The right pattern is a time-driven trigger. Write your current state into PropertiesService (or a Sheets cell), schedule a trigger to fire in 1–2 minutes via ScriptApp.newTrigger(...).timeBased().after(90000).create(), and let the execution end. The next trigger picks up where you left off. This keeps each individual run fast and within budget, and the total wall-clock time can be hours.

Sleep belongs in one category: smoothing the cadence of calls you are making right now within a single run. Everything else — waiting on external state, polling, backoff after repeated failures — belongs in the trigger-and-resume pattern. The distinction is not academic; conflating them leads to scripts that time out reliably on anything but toy datasets.

Want a custom version?

Describe your sheet and the rule you want. Gnaw writes the Apps Script — fields, triggers, edge cases — in one shot.

FAQ

4 questions
What is the maximum value I can pass to Utilities.sleep?
There is no documented hard cap, but anything above 300,000ms (5 minutes) will consume the majority of your 6-minute execution budget in a single call. In practice, keep individual sleep calls under 5,000ms and restructure larger waits as trigger-and-resume.
Does Utilities.sleep work inside a custom function called from a cell formula?
No. Custom functions run in a read-only context with a 30-second limit. Utilities.sleep will throw an error or cause the cell to return a timeout error. It is only usable in bound scripts, standalone scripts, and add-on server-side functions triggered by menu or event.
I am hitting a 429 from the Gmail API even with sleep. Why?
Gmail quotas are per-user per day, not per-minute. A 429 from Gmail usually means you have hit the 100 emails/day limit (free) or the per-second send rate. Sleeping between calls within a single run will not help if you have already exhausted the daily bucket. Check the Quotas page in the Apps Script dashboard — it shows remaining quota in real time.
Will Utilities.sleep(0) yield execution or do anything useful?
No. It is a no-op in practice. Apps Script is single-threaded and there is no event loop to yield to. If you are trying to avoid a race condition, the answer is to restructure the logic, not to sleep for zero milliseconds.
// one good script a week

Get a working Apps Script snippet in your inbox, weekly.