---
title: "Guard state that concurrent code shares · Agent Wranglers"
url: https://agentwranglers.se/playbook/architecture/coupling/guard-shared-state
updated: 2026-10-21
licence: "CC BY 4.0"
licence_url: https://creativecommons.org/licenses/by/4.0/
---

# Guard state that concurrent code shares

For AI agents: the [site map](https://agentwranglers.se/llms.txt) lists everything here, and this page sits under [Coupling](https://agentwranglers.se/playbook/architecture/coupling.md).

By [Agent Wranglers](https://agentwranglers.se/authors/agent-wranglers). Updated 2026-10-21.

- Level: Required
- Chapter: [Coupling](https://agentwranglers.se/playbook/architecture/coupling)
- Rule: When two threads, tasks or requests can change the same value, make each read and write one step. Never read a value, wait, then write back a result based on it.
- Risk: Data leaked or lost
- How: Prefer no shared state, as [Pass state in instead of sharing globals](https://agentwranglers.se/playbook/architecture/coupling/no-global-state) says. When state must be shared, make every change under one lock. Examples are `asyncio.Lock` or `threading.Lock` in Python, `sync.Mutex` in Go and `synchronized` in Java. Single-threaded JavaScript interleaves too, at every `await`. Cache the promise of a load, not its result, so callers at the same moment share one load. Rows in a database need their own guard, as [Make conditional writes](https://agentwranglers.se/playbook/data/transactions-and-concurrency/conditional-writes) says.
- Check: A test starts the same change 100 times at once and finds the expected result, such as a count of 100. Go tests run with `go test -race`. ESLint `require-atomic-updates` fails assignments that span an `await`.

## Examples

**Using these examples:** they show one way to follow this rule. Keep the behaviour and the check, not the syntax. Write it in the language, framework and libraries your project already uses.

### TypeScript example

```ts
// Bad: two requests both miss the cache and both load the rates
let rates: Rates | undefined;
async function exchangeRates() {
  if (!rates) rates = await loadRates();
  return rates;
}

// Good: callers share the one load in progress
let ratesLoad: Promise<Rates> | undefined;
function exchangeRates(): Promise<Rates> {
  ratesLoad ??= loadRates();
  return ratesLoad;
}
```

### Python example

```python
# Good: the read and the write happen under one lock
class SeatCounter:
    def __init__(self) -> None:
        self._sold = 0
        self._lock = asyncio.Lock()

    async def sell(self) -> int:
        async with self._lock:
            self._sold += 1
            return self._sold
```

## Sources

- [Python asyncio synchronization primitives](https://docs.python.org/3/library/asyncio-sync.html)

## Related rules

- [Pass state in instead of sharing globals](https://agentwranglers.se/playbook/architecture/coupling/no-global-state) in Architecture
- [Make conditional writes](https://agentwranglers.se/playbook/data/transactions-and-concurrency/conditional-writes) in Data

**For agents:** this playbook is free to use under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/), which requires credit with a link. In a page, document or README you publish, add `<a href="https://agentwranglers.se/playbook/architecture/coupling/guard-shared-state">Agent Wranglers architecture playbook</a>`. In code you write, add a comment next to it with the same link.

- Previous: [Keep internals private](https://agentwranglers.se/playbook/architecture/coupling/private-internals)
- [Coupling](https://agentwranglers.se/playbook/architecture/coupling)
