// Sheets · Apps Script

Fix "There are too many scripts running simultaneously" in Google Sheets.

Custom functions in Google Sheets run one execution per cell, so filling a formula down 500 rows launches 500 concurrent scripts against a hard quota of ~30 slots. Rewrite the function to accept an entire range, return a 2-D array, and the whole column resolves in one call.

I filled my custom Apps Script function down a column and now every cell shows "Loading..." or throws "There are too many scripts running simultaneously for this Google user account."

The script

copy · paste · trigger
classifyRange.gs
Apps Script
// Range-aware custom function — single call resolves the whole column
// Usage in Sheets: =CLASSIFY_DOMAIN(A2:A500)
function CLASSIFY_DOMAIN(input) {
  const isRange = Array.isArray(input);
  const flat = isRange ? input.flat() : [input];

  const results = flat.map(function(val) {
    if (!val) return '';
    const domain = String(val).toLowerCase();
    if (domain.indexOf('gov') !== -1) return 'government';
    if (domain.indexOf('edu') !== -1) return 'education';
    return 'commercial';
  });

  // Sheets expects a 2-D array when the input was a range
  return isRange ? results.map(function(r) { return [r]; }) : results[0];
}

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

Walkthrough

Why one formula in 500 cells kills the quota

Google Apps Script enforces a hard concurrency cap of roughly 30 simultaneous script executions per Google account. When you write a custom function and fill it down a column, Sheets doesn't batch those calls — it queues each cell as a separate execution. Fill it down 500 rows during a spreadsheet open or recalc, and you will hit that cap almost immediately. The cells that can't get a slot show the 'Loading...' spinner indefinitely, then surface the 'too many scripts running simultaneously' error.

The quota is per-account, not per-spreadsheet. If you have two sheets open that both use the same custom function heavily, they share the same 30-slot ceiling. The error is not a temporary glitch you can wait out — the cells will keep retrying and keep colliding until the recalc cycle exhausts itself.

Making the function range-aware

The fix is to change what the function accepts. When Sheets passes a multi-cell range to a custom function, the argument arrives as a 2-D JavaScript array (rows × columns). The first thing your function should do is check Array.isArray(input) — if true, you're holding a range; if false, the function was called on a single cell.

Flatten that 2-D array, run your logic once across all values in a single execution, then return a 2-D array of the same shape. Sheets maps the output back to the correct cells automatically. The entire column resolves in one script execution instead of one per row. The first time I hit this on a sheet with 800 domain lookups, switching to the range pattern dropped execution count from ~800 attempts to 1 and cleared all the errors inside a single recalc.

Change the call site too: instead of putting =CLASSIFY_DOMAIN(A2) in B2 and filling down, put =CLASSIFY_DOMAIN(A2:A500) in B2 only. Sheets spills the returned 2-D array into B2:B500. Delete all the duplicated formulas below it — they are now the problem, not the solution.

Handling mixed single-cell and range calls

A range-aware function still needs to work when someone calls it on a single cell. The Array.isArray check handles this: if input is a scalar, wrap it in an array, process it, and return the scalar result directly (not wrapped in [[]]). Returning a 2-D array from a single-cell call will spill unexpectedly into adjacent cells, which is disorienting.

Watch for empty cells inside the range — Sheets passes them as empty strings, not null. A guard like if (!val) return '' handles both empty strings and actual falsy values cleanly. If your function hits an external API (UrlFetchApp, for instance), batch the requests inside the single execution rather than calling fetch per row. The 6-minute execution time limit per call applies, but for most column-level work that limit is not the constraint — concurrency was.

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
Can I just add a sleep or retry to fix the error without rewriting the function?
No. Utilities.sleep() inside a custom function delays that execution but doesn't free slots for the other concurrent ones. You'll hit the 30-second custom function timeout before the congestion clears. The rewrite is the fix.
Does the range pattern work if my column has gaps or mixed types?
Yes, as long as you guard for empty strings. Sheets sends empty cells as '' (empty string), so a check like if (!val) return '' before your logic handles gaps without throwing. The output array stays the same length as the input, so cell positions stay aligned.
I already have =MYFUNCTION(A2) filled down 1,000 rows. Do I need to delete them all manually?
Select column B, press Delete to clear all values, then type =MYFUNCTION(A2:A1000) in B2 only and press Enter. Sheets spills the result. The old 1,000 formulas are gone in one delete.
Will this fix the error when multiple people have the sheet open at the same time?
Partially. Range-aware formulas cut your per-user execution count from N to 1, which gives you much more headroom. But the 30-slot limit is per Google account (the owner's), not per viewer. Heavy simultaneous recalcs from many collaborators can still approach the limit. Caching results in PropertiesService or writing a time-triggered batch job is the next step if multi-user load is genuinely the constraint.