Blog · programming

The spreadsheet lock – why your Apps Script app starts freezing

2026-09-14

The symptom: an Apps Script app lagging with multiple users

An app written in Google Apps Script runs perfectly for one person. You add a second, a third, a fifth – and something strange starts happening. Clicking save takes three seconds. Then ten. Someone tries to upload a bigger batch of files and the whole app freezes for a minute. The form doesn’t submit, the button spins, and the user has no idea whether the operation went through.

The code isn’t bad. You haven’t changed anything in months. The company just grew and more people are working on the app at the same time. And suddenly what used to fly starts crawling.

What’s happening under the hood

Google Sheets as a database has one assumption that isn’t a bug but a consequence of what it actually is: it’s a spreadsheet, not a database. So that two people editing the same file at once see consistent data, the engine holds a global lock on the entire sheet during writes.

In practice this means: when one script writes to the sheet, all other scripts trying to write to that same sheet wait in line. They don’t error out, they don’t run in parallel – they just stand still until the previous one finishes.

With one person and five writes a day this is invisible. With twenty people doing twenty operations an hour, the math becomes ruthless. If one write takes 2 seconds, ten writes queued up mean 20 seconds of waiting for the last one. And to the user the app just looks frozen.

Why “speeding up the code” doesn’t help

The natural reflex is optimization: batching writes, caching, cutting down getRange calls. All good and worth doing – but it doesn’t remove the cause. You shorten each write from 2 seconds to 0.8 – the lock is still there, the queue still builds up, just more slowly.

This is a qualitative difference, not a quantitative one. A one-lane bridge doesn’t become two because the cars drive a bit faster.

What you can actually do

Three honest options, ordered from cheapest to most expensive:

1. Push writes off in time (queue + trigger). Instead of writing to the sheet synchronously on click, push the event into a queue (e.g. PropertiesService) and let a background trigger process it calmly. The user doesn’t wait because they immediately see “saved”. Downside: doesn’t work for operations where the user must get a result right away (like a generated order number).

2. Move the hottest tables to a real database. If 80% of writes hit one or two tables (like order statuses), you can leave the rest in the sheet and move those two to, say, Postgres. A real database has no global lock – two people can write to different rows at the same time. Complicated, because the app has to read from both places, but usually the best return on effort.

3. Move the whole data layer out. The sheet stays only as a viewing and manual-correction tool for the owner, and the app lives in a real database. Biggest job, but also the only way if the app is still growing.

How to choose between 1, 2 and 3?

- up to 10 active users                → option 1 usually enough
- 10-30 users, a few hot tables        → option 2 has the best
                                          effort-to-effect ratio
- above 30, or the app is growing      → option 3, and the sooner
                                          the less it hurts

If you want to see, step by step, how to migrate from a spreadsheet to Postgres without stopping the business, I covered that pattern separately in Dual-write – how to move an app to a new database without shutting down (available from Sep 22), with code for Apps Script + Supabase.

What not to do

Don’t jump to rewriting everything in Node/Django/whatever. That’s a trap. An app on a sheet has one huge advantage – the client can poke around in it themselves. Undo a change, check the data, export to Excel for a meeting. Take the sheet away and you take that comfort away too. Migration makes sense only once the cost of “lag” has exceeded the cost of “losing the sheet as a tool”.

And don’t try to work around the lock with LockService.getScriptLock(). That’s a different tool for a different problem (protects your code from itself), and it won’t touch the global sheet lock.

What I learned

Google Apps Script is a great tool for building something that quickly needs to work for a small team. But it has a ceiling, and it’s better to know where that ceiling is before you hit it. “Lag” isn’t a bug to be fixed – it’s a signal that the app has outgrown the platform. Spotting that signal and offering the client a sensible way out before they start losing money on it is half of the value you bring as a developer.

Back to all posts

Got a project to talk about?

Just tell me what you need - it does not have to be technical, that is my job. I will get back to you and tell you straight whether and how I can help.