thread-safety
programming
concurrency
data structures
software development

Are lists thread-safe?

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

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.

Related reading
Course
Intermediate
27 lessons
15 hours
DSA Fundamentals

Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

All Rights Reserved.