Back to Blog
Guide
8

Google Apps Script Lock Service Guide: Prevent Duplicate Submissions and Race Conditions

GSheetLab Expert

Author

2026-06-26

Published

Learn how to use Google Apps Script Lock Service to prevent duplicate submissions, avoid race conditions, and ensure data integrity in multi-user environments. This guide covers script locks, document locks, user locks, and practical examples for building reliable Apps Script automations.

As your Google Apps Script projects become more advanced, you may encounter situations where multiple users, triggers, or processes attempt to access the same resource at the same time. While this might seem harmless, it can lead to serious issues such as duplicate entries, overwritten data, inconsistent records, and failed automations.

Imagine an employee attendance system where two employees submit forms simultaneously, or a CRM automation that processes multiple leads at the same moment. Without proper controls, both executions may attempt to update the same spreadsheet row, resulting in corrupted data. This problem is known as a race condition.

Fortunately, Google Apps Script provides a built-in solution called the Lock Service. Lock Service allows scripts to temporarily reserve access to resources, ensuring that only one execution can perform critical operations at a time.

By the end of this article, you'll understand how to build safer and more reliable Apps Script automations.

What Is Google Apps Script Lock Service?

Lock Service is a Google Apps Script feature that prevents multiple script executions from modifying shared resources simultaneously. Think of it like a meeting room reservation system: without reservations, multiple people enter at once and chaos occurs; with reservations, one person enters, finishes work, and leaves before the next person enters.

Lock Service works similarly. When a script acquires a lock, other executions must wait, shared data remains protected, and conflicts are prevented.

Why Lock Service Is Important

Many Apps Script projects involve shared spreadsheets, form submissions, API integrations, database updates, and automated workflows. Without locking, concurrent executions can create serious problems.

Understanding Race Conditions

A race condition occurs when multiple script executions attempt to access or modify the same data simultaneously. For example, consider an inventory count of 100:

  • User A reads 100, then subtracts 10 → expects a result of 90
  • User B also reads 100 (before A's update), then subtracts 20 → expects a result of 80

The expected final inventory should be 70, but the actual result becomes 80 — data becomes inaccurate because both executions used outdated information.

Common Problems Without Lock Service

  • Duplicate Form Entries: Multiple submissions may create duplicate records.
  • Overwritten Spreadsheet Data: One execution may overwrite another.
  • Incorrect Calculations: Counters and totals become inaccurate.
  • Duplicate Emails: Triggers may send multiple emails.
  • Failed Workflows: Automation processes become unreliable.

How Lock Service Works

The process is straightforward: a script requests a lock → the lock becomes available → the script performs critical work → the script releases the lock → the next execution proceeds. Only one execution can hold the lock at a time.

Types of Locks in Google Apps Script

Script Lock

Protects the entire script. Best for shared spreadsheets, global counters, and application-wide operations.

const lock = LockService.getScriptLock();

User Lock

Protects resources for a specific user. Best for user preferences, personal settings, and individual workflows.

const lock = LockService.getUserLock();

Document Lock

Protects a specific document. Best for spreadsheet updates, document add-ons, and shared files.

const lock = LockService.getDocumentLock();

Acquiring and Releasing a Lock

Before modifying shared data, acquire the lock and wait up to a specified timeout for it to become available:

function updateData() {
  const lock = LockService.getScriptLock();
  lock.waitLock(30000); // wait up to 30 seconds
}

Always release the lock after work is completed so other executions can proceed:

lock.releaseLock();

Complete Lock Service Example

function addRecord() {
  const lock = LockService.getScriptLock();

  try {
    lock.waitLock(30000);

    const sheet = SpreadsheetApp
      .getActiveSpreadsheet()
      .getSheetByName("Data");

    sheet.appendRow([
      new Date(),
      "New Record"
    ]);

  } finally {
    lock.releaseLock();
  }
}

This ensures only one execution writes data at a time.

waitLock() vs tryLock()

waitLock(timeout) waits for the specified number of milliseconds for access. If the lock is unavailable within that time, the script throws an error:

lock.waitLock(10000); // wait up to 10 seconds

tryLock(timeout) attempts to acquire the lock and returns false if unsuccessful, rather than throwing an error — useful for non-critical operations:

if (lock.tryLock(5000)) {
  // proceed
}

Preventing Duplicate Form Submissions

One of the most common use cases: when two users submit a form simultaneously, both executions may attempt sheet.appendRow(), potentially resulting in missing or duplicate records. Lock Service solves this:

function onFormSubmit() {
  const lock = LockService.getScriptLock();
  lock.waitLock(30000);

  try {
    // process form
  } finally {
    lock.releaseLock();
  }
}

Now submissions are processed one at a time.

Preventing Duplicate Emails

When multiple triggers execute simultaneously, a customer may receive multiple emails. Acquiring a lock before sending solves this:

lock.waitLock(30000);

GmailApp.sendEmail(email, subject, body);

lock.releaseLock();

Using Lock Service with Counters

Suppose you're generating invoice numbers and the current number is 1001. Without locks, two executions may both generate 1002, creating duplicate invoice IDs. Lock Service prevents this issue.

Invoice Generator Example

function generateInvoice() {
  const lock = LockService.getScriptLock();
  lock.waitLock(30000);

  try {
    const props = PropertiesService.getScriptProperties();
    let invoice = Number(props.getProperty("INVOICE_ID"));
    invoice++;
    props.setProperty("INVOICE_ID", invoice);

  } finally {
    lock.releaseLock();
  }
}

This guarantees unique invoice numbers. Lock Service and Properties Service often work together this way: store counters, acquire the lock before updating, and release the lock after the update to ensure consistency.

Using Lock Service with Triggers

Time-driven triggers can overlap — for example, a report that runs every 5 minutes may still be running when the next trigger fires. Lock Service prevents this overlap:

function generateReport() {
  const lock = LockService.getScriptLock();

  if (!lock.tryLock(1000)) {
    return;
  }

  try {
    // report logic
  } finally {
    lock.releaseLock();
  }
}

Only one report runs at a time.

Real-World Use Cases

  • Employee Attendance Systems: Prevent duplicate attendance entries.
  • CRM Automations: Avoid duplicate lead creation.
  • Inventory Management: Maintain accurate stock counts.
  • Invoice Systems: Generate unique invoice numbers.
  • Financial Reporting: Prevent overlapping calculations.
  • Email Automation: Avoid duplicate notifications.

Best Practices

Keep Lock Duration Short

Acquire the lock only when necessary. Avoid locking around slow operations like API calls:

// Bad — lock held during slow operations
lock.waitLock();
fetchAPI();
processLargeData();
lock.releaseLock();

// Good — lock held only for the critical section
fetchAPI();
processData();
lock.waitLock();
updateSheet();
lock.releaseLock();

Always Release Locks

Use finally blocks to guarantee the lock is released even if an error occurs, preventing deadlocks:

finally {
  lock.releaseLock();
}

Other Best Practices

  • Use the appropriate lock type (Script Lock, User Lock, or Document Lock) based on your needs
  • Minimize critical sections — lock only the code that accesses shared resources
  • Always anticipate lock acquisition failures and handle them gracefully

Common Mistakes

  • Forgetting releaseLock(): Causes unnecessary waiting for other executions.
  • Locking Entire Scripts: Reduces performance unnecessarily.
  • Using the Wrong Lock Type: Can leave resources unprotected.
  • Ignoring Exceptions: May leave locks active if errors aren't handled properly.

Lock Service Limitations

Although useful, Lock Service is not a database transaction system. Limitations include temporary locking only, execution time restrictions, and waiting limits. It should be used strategically.

Lock Service vs No Lock Service

Feature Without Lock With Lock
Duplicate Entries Possible Prevented
Data Consistency Lower Higher
Shared Resources Vulnerable Protected
Reliability Moderate High
Workflow Stability Lower Higher

Advanced Workflow Example

A lead processing system might follow this sequence: form submission → acquire lock → check lead database → create lead → send email → release lock. This prevents duplicate leads from ever being created.

Why Businesses Should Use Lock Service

As automation grows, concurrency problems become more common. Lock Service helps organizations protect data integrity, prevent duplicates, improve reliability, reduce errors, and build scalable workflows. For production Apps Script projects, Lock Service is often considered essential.

Frequently Asked Questions

Lock Service is a feature that prevents multiple script executions from accessing shared resources simultaneously.
It prevents race conditions, duplicate submissions, overwritten data, and workflow conflicts.
A race condition occurs when multiple executions try to modify the same data at the same time, causing unpredictable results.
Google Apps Script provides Script Lock, User Lock, and Document Lock.
It waits for a specified amount of time until a lock becomes available.
It attempts to acquire a lock and returns immediately if unsuccessful.
Yes. Lock Service is commonly used to ensure form submissions are processed one at a time.
Yes. Always release locks using a finally block to avoid unnecessary delays and resource contention.
Yes. They are frequently used together to safely update counters, settings, and workflow states.
No. It is primarily needed when multiple executions may access the same resource concurrently.

Did you find this helpful? Share it with your team.