Use LockService to Prevent Race Conditions in Apps Script
If multiple users can trigger your script at the same time — or if you have overlapping time-based triggers — you can end up with race conditions: two executions reading and writing data simultaneously, causing duplicates or corruption.
Apps Script's LockService solves this by letting you acquire a lock before critical sections of code.
The problem: duplicate writes without a lock
Imagine a script that assigns sequential ticket numbers:
// ❌ UNSAFE — two simultaneous executions could assign the same numberfunctionassignTicketNumber(){const sheet =SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();const lastRow = sheet.getLastRow();const lastNumber = sheet.getRange(lastRow,1).getValue();const newNumber =(parseInt(lastNumber)||0)+1; sheet.appendRow([newNumber,newDate(),Session.getActiveUser().getEmail()]);}
If two users run this at the same time, both could read the same lastNumber and write the same newNumber.
The fix: acquire a lock first
// ✅ SAFE — LockService prevents concurrent executionfunctionassignTicketNumberSafe(){const lock =LockService.getScriptLock();try{ lock.waitLock(10000);// Wait up to 10 seconds to acquire the lockconst sheet =SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();const lastRow = sheet.getLastRow();const lastNumber = sheet.getRange(lastRow,1).getValue();const newNumber =(parseInt(lastNumber)||0)+1; sheet.appendRow([newNumber,newDate(),Session.getActiveUser().getEmail()]);Logger.log('Assigned ticket number: '+ newNumber);}catch(e){Logger.log('Could not acquire lock: '+ e.message);thrownewError('Another process is running. Please try again in a moment.');}finally{ lock.releaseLock();}}
Types of locks
Apps Script provides three lock types:
// Blocks ALL users and executions of this scriptconst scriptLock =LockService.getScriptLock();// Blocks only the CURRENT USER's concurrent executionsconst userLock =LockService.getUserLock();// Blocks all executions on the CURRENT DOCUMENTconst documentLock =LockService.getDocumentLock();
Use getScriptLock() when protecting shared resources like a spreadsheet. Use getUserLock() when protecting per-user state.
Check if a lock is available without waiting
functiontryLockExample(){const lock =LockService.getScriptLock();if(lock.tryLock(5000)){// Try for up to 5 secondstry{// Do your critical work hereLogger.log('Lock acquired, doing work...');}finally{ lock.releaseLock();}}else{Logger.log('Could not acquire lock — another process is running.');}}
Protect a form submit handler
Form submissions can come in simultaneously. Protect your onFormSubmit handler: