Are lists thread-safe?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
The safe default answer is no: ordinary list types are usually not designed for concurrent mutation from multiple threads without coordination. Some runtimes make a few individual operations appear atomic, but that is not the same as saying the list is fully thread-safe for iteration, updates, and compound read-modify-write logic.
What Thread-Safe Really Means
A collection is thread-safe when it can be used from multiple threads without corrupting data or producing undefined behavior, according to the guarantees of that runtime and API.
That is a stronger claim than "this one method seems to work most of the time." A list may allow one operation safely while still failing under:
- concurrent iteration and modification,
- multiple writers,
- read-then-write sequences,
- size checks combined with updates.
So the real question is not just whether a list exists in memory safely. It is whether the pattern of access is protected correctly.
A Common Unsafe Pattern
Consider multiple threads appending and reading without coordination. Even if a runtime protects some internals, the overall logic is still unsafe once the code depends on multiple steps being consistent.
In Python, a lock-based approach is the straightforward fix:
The important point is not Python specifically. It is that the list itself is not being trusted as the synchronization mechanism.
Language Guarantees Differ
Different languages give different partial guarantees:
- in Java,
ArrayListis not thread-safe, - in C#,
List<T>is not thread-safe for concurrent write access, - in Python, some single operations may appear atomic in CPython, but that is not a general thread-safety promise.
That means answers copied from one language ecosystem do not automatically transfer to another. Always check the contract of the specific collection type and runtime.
Use a Concurrent Collection When the Problem Calls for One
Sometimes the right fix is not "wrap a list in a lock." Sometimes the right fix is "use a different data structure."
In Java, for example:
CopyOnWriteArrayList is useful when reads are frequent and writes are rare. That is not the right answer for every workload, but it shows the principle: pick a collection with concurrency semantics that match your access pattern.
Other times, a queue is a better choice than a list if producers and consumers are communicating across threads.
Compound Operations Are the Real Trap
Even when a single operation is safe, multi-step logic often is not.
This pattern is risky without synchronization:
Another thread could change the list between the length check and the indexed read. That is why thread safety must be evaluated at the operation-sequence level, not just the individual method level.
If your code needs invariants such as "check then act," protect the whole critical section with the same lock or use a purpose-built concurrent structure.
Common Pitfalls
- Assuming that one atomic operation means the entire list API is thread-safe.
- Reading language-specific advice and applying it to a different runtime.
- Using a plain list where a queue, concurrent collection, or message channel would be a better abstraction.
- Protecting writes with a lock but leaving iteration or compound reads unprotected.
- Focusing on data corruption only and ignoring race conditions in higher-level business logic.
Summary
- Ordinary list types are generally not safe for uncontrolled concurrent mutation.
- Thread safety depends on the runtime, the exact collection type, and the access pattern.
- A few safe-looking individual operations do not make compound logic safe.
- Use locks when a shared list truly needs coordinated access.
- Prefer concurrent collections or queues when the workload naturally matches those abstractions better than a plain list.

