thread-safety
programming
concurrency
data structures
software development

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:

python
1import threading
2
3items = []
4lock = threading.Lock()
5
6
7def add_item(value):
8    with lock:
9        items.append(value)
10
11
12threads = [threading.Thread(target=add_item, args=(i,)) for i in range(100)]
13
14for thread in threads:
15    thread.start()
16
17for thread in threads:
18    thread.join()
19
20print(len(items))

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, ArrayList is 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:

java
1import java.util.List;
2import java.util.concurrent.CopyOnWriteArrayList;
3
4public class Main {
5    public static void main(String[] args) {
6        List<String> items = new CopyOnWriteArrayList<>();
7        items.add("a");
8        items.add("b");
9        System.out.println(items);
10    }
11}

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:

python
if len(items) > 0:
    first = items[0]

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.

Course illustration
Course illustration

All Rights Reserved.