Google Apps Script’s “Exceeded maximum execution time” error fires when a script surpasses Google’s hard wall-clock limit (6 minutes for free accounts, 30 minutes for Workspace). This almost always happens because you are calling getValue() or setValue() inside a for loop, incurring a network round-trip for every single row.
To fix this instantly, replace all per-row read/write calls with a single batched array memory map:
JavaScript
function applyPricing() {
const sheet = SpreadsheetApp.getActiveSheet();
const lastRow = sheet.getLastRow();
// ONE network read: pulls all data into a local 2D array
const inputData = sheet.getRange(2, 3, lastRow - 1, 2).getValues();
// All processing runs instantly in V8 memory — zero network calls
const outputData = inputData.map(([units, price]) => {
const finalPrice = units > 200 ? price * units * 0.88 : price * units;
const status = units > 200 ? 'DISCOUNTED' : 'STANDARD';
return [finalPrice, status];
});
// ONE network write: pushes the entire result set back in a single operation
sheet.getRange(2, 5, outputData.length, 2).setValues(outputData);
}
Collapsing thousands of read/write operations into one getValues() and one setValues() eliminates network latency. A script that previously timed out processing 15,000 rows in 6 minutes will now complete in under 5 seconds.
Prerequisites
- Google Apps Script V8 Runtime: Ensure V8 is active under Project Settings > Runtime version > V8. The legacy Rhino runtime handles array mapping differently.
- Contiguous Data Ranges: The
getRange().getValues()method reads a rectangular block. If your data is spread across non-adjacent columns, you must read the whole block and select the specific array indices in memory.
The Syntax Breakdown
This fix operates across four mechanistic stages, shifting the workload from Google’s servers to your local memory:
sheet.getRange(2, 3, lastRow - 1, 2)— Constructs a exact boundary box. Specifying exact row/column dimensions preventsgetDataRange()from needlessly pulling thousands of blank columns into memory..getValues()— Executes a single Spreadsheet Service API call. The returned 2D array lives entirely in V8 memory for the rest of the execution..map(([units, price]) => [...])— Destructures each sub-array from the input array, applies your business logic in memory, and returns a new 2D array. No further service calls occur during this loop..setValues(outputData)— Executes exactly one write call, pushing the calculated 2D output array directly back onto the sheet grid.
Real-World Example: E-Commerce Nightly Order Pricing
An operations manager at a mid-size e-commerce retailer runs a nightly Apps Script to apply volume discounts to 15,000 order rows. The script was authored when the company only had 2,000 orders. Because it uses a traditional for loop with setValue(), it makes 30,000 network calls per run.
At the current data volume, it hits the 6-minute ceiling and throws “Exceeded maximum execution time” consistently around row 4,200—leaving 70% of the daily reporting blank.
Raw Data Table (Columns C–D are inputs, E–F are outputs)
| Order ID | Units Sold | Unit Price | Final Price | Status |
| ORD-1001 | 120 | 45.00 | (blank) | (blank) |
| ORD-1002 | 340 | 22.50 | (blank) | (blank) |
| ORD-1003 | 78 | 67.00 | (blank) | (blank) |
The Refactored Execution
The manager replaces the loop with the getValues() memory map.
Output Table
| Order ID | Units Sold | Unit Price | Final Price | Status |
| ORD-1001 | 120 | 45.00 | 5,400.00 | STANDARD |
| ORD-1002 | 340 | 22.50 | 6,732.00 | DISCOUNTED |
| ORD-1003 | 78 | 67.00 | 5,226.00 | STANDARD |
All 15,000 rows now calculate and print to the sheet in under 10 seconds.
Advanced: The PropertiesService Continuation Pattern
If you are processing hundreds of thousands of rows and hitting the timeout even with array batching, you must build a state-saving continuation trigger.
This code stops the script at the 5-minute mark, saves its exact row position, and tells Google to run the script again 2 seconds later starting from that exact spot:
JavaScript
function applyPricingContinuation() {
const MAX_MS = 5 * 60 * 1000; // 5-min budget
const start = Date.now();
const props = PropertiesService.getScriptProperties();
// Clear old triggers to prevent quota burn
ScriptApp.getProjectTriggers().forEach(t => {
if(t.getHandlerFunction() === 'applyPricingContinuation') ScriptApp.deleteTrigger(t);
});
let startIdx = parseInt(props.getProperty('resumeRow') || '0');
for (let i = startIdx; i < massiveArray.length; i++) {
if (Date.now() - start > MAX_MS) {
// Save position and schedule next run
props.setProperty('resumeRow', i.toString());
ScriptApp.newTrigger('applyPricingContinuation').timeBased().after(2000).create();
return;
}
// Processing logic here...
}
props.deleteProperty('resumeRow'); // Clear state when finished
}
Common Related Errors & Fixes
- Script Times Out Mid-Loop with No Error Indicator -> Cause: You are using
setValue()incrementally. When the 6-minute timeout hits, the script dies instantly without rolling back written values, leaving a partially updated sheet. -> Fix: The batchsetValues()pattern resolves this atomically. The entire output array computes in memory before a single cell is written, so either all 15,000 rows write, or none do. - Orphaned Time-Based Triggers Accumulate -> Cause: You implemented the continuation pattern, but didn’t delete the previous trigger before creating the next one. This burns your daily execution quota rapidly. -> Fix: Always include a trigger deletion loop at the start of your continuation function (as shown in the advanced script above).
Key Takeaways
- The primary fix is replacing per-row
setValue()loops with a single batchedsetValues()array write. - Execution time limits apply to wall-clock elapsed time (including network wait times), not CPU processing time.
- For datasets exceeding 100,000 rows, implement a
PropertiesServicecheckpoint loop to bypass the ceiling entirely.
Frequently Asked Questions
What is the exact execution time limit for Google Apps Script?
Consumer Google accounts (free Gmail) have a hard limit of 6 minutes per script execution. Google Workspace accounts (Business Starter, Standard, Plus, and Enterprise) have a ceiling of 30 minutes per execution.
Does calling SpreadsheetApp.flush() inside a loop fix timeouts?
No, it makes them worse. SpreadsheetApp.flush() forces all queued write operations to execute immediately against the Google server. This converts Apps Script’s internal write queue into a synchronous blocking call, massively increasing network latency.
Can I buy more execution time in Google Cloud Console?
No. Google does not expose a per-project execution time setting. You cannot extend the limit through script configurations or GCP billing. Your only options are upgrading to a Workspace account or utilizing the continuation pattern.