TensorFlow
Keras
Thread Safety
Sequence Class
Concurrency

Is the class generator inheriting Sequence thread safe in Keras/Tensorflow?

ML System Design practice on Codemia

Design recommenders, ranking systems and training pipelines the way ML interviews actually ask for them, with worked solutions.

Practice ML system design

Introduction

In Keras, subclassing Sequence is safer than using a plain Python generator when data loading happens with workers, but it is not a magic thread-safety shield for every custom implementation. Sequence gives Keras predictable indexing and epoch semantics, while your own code still has to avoid unsafe shared mutable state.

What Sequence Actually Guarantees

keras.utils.Sequence is designed for indexed batch access. Keras can ask for batch 0, batch 1, and so on, and it knows how many batches exist because the class provides both __len__ and __getitem__.

Minimal example:

python
1import math
2import numpy as np
3from tensorflow.keras.utils import Sequence
4
5
6class NumberSequence(Sequence):
7    def __init__(self, x, batch_size=4):
8        self.x = np.asarray(x)
9        self.batch_size = batch_size
10
11    def __len__(self):
12        return math.ceil(len(self.x) / self.batch_size)
13
14    def __getitem__(self, index):
15        start = index * self.batch_size
16        end = start + self.batch_size
17        batch = self.x[start:end]
18        return batch, batch
19
20
21seq = NumberSequence(np.arange(10), batch_size=3)
22print(seq[0])

That indexed design is why Sequence works better with multiprocessing and avoids some duplication issues seen with naive generators.

Safer Than a Plain Generator Does Not Mean Fully Safe

The important distinction is:

  • Keras can schedule Sequence batches more safely than a plain generator
  • your __getitem__ implementation can still be unsafe if it mutates shared state badly

For example, this is risky:

python
1class BadSequence(Sequence):
2    def __init__(self):
3        self.counter = 0
4
5    def __len__(self):
6        return 100
7
8    def __getitem__(self, index):
9        self.counter += 1
10        return np.array([self.counter]), np.array([self.counter])

If multiple workers touch shared mutable state like counter, the behavior becomes hard to reason about.

Keep __getitem__ Stateless or Predictable

The safest pattern is for __getitem__ to compute a batch entirely from the requested index and immutable data sources.

Good traits:

  • batch contents depend on index
  • no global counters
  • no random mutation of shared lists
  • no side effects that another worker can race with

If you need shuffling, update the index mapping in on_epoch_end rather than inside __getitem__.

python
1class SafeSequence(Sequence):
2    def __init__(self, x, y, batch_size=4):
3        self.x = np.asarray(x)
4        self.y = np.asarray(y)
5        self.batch_size = batch_size
6        self.indices = np.arange(len(self.x))
7
8    def __len__(self):
9        return math.ceil(len(self.indices) / self.batch_size)
10
11    def __getitem__(self, index):
12        idx = self.indices[index * self.batch_size:(index + 1) * self.batch_size]
13        return self.x[idx], self.y[idx]
14
15    def on_epoch_end(self):
16        np.random.shuffle(self.indices)

This is much easier to keep safe than a batcher that mutates state during every fetch.

Watch External Libraries Too

Even if your Sequence logic is clean, the code it calls may not be thread-safe. Common examples include:

  • image decoders with shared caches
  • global random state
  • file handles reused across workers
  • database clients not intended for concurrent access

Sequence does not protect you from those issues. It only gives Keras a safer contract for requesting batches.

Sequence Versus Newer Input Pipelines

For new TensorFlow-heavy projects, tf.data is often a better long-term input pipeline choice because it gives clearer control over parallelism, prefetching, and graph-friendly transformations. Sequence still makes sense when you need Python-side logic, existing NumPy data, or a quick integration with model.fit.

Use Sequence when:

  • your loader is naturally batch-indexed
  • Python-side preprocessing is acceptable
  • you want something safer than a bare generator

Use tf.data when:

  • the input pipeline is performance critical
  • TensorFlow-native transformations are possible
  • you need more control over pipeline execution

Common Pitfalls

The biggest mistake is assuming Sequence makes non-thread-safe code safe automatically. It does not.

Another issue is mutating shared counters, file pointers, or caches inside __getitem__. Indexed fetching works best when the method is close to pure.

A third problem is debugging duplicate or inconsistent batches without checking custom shuffling logic in on_epoch_end.

Summary

  • 'Sequence is safer than a plain generator for Keras worker-based loading.'
  • Its main advantage is predictable indexed batch retrieval.
  • Your own __getitem__ logic still needs to avoid unsafe shared mutable state.
  • Keep batch generation index-driven and side-effect-light.
  • Consider tf.data when you need a more scalable or TensorFlow-native pipeline.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

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

ML System Design practice on Codemia

Design recommenders, ranking systems and training pipelines the way ML interviews actually ask for them, with worked solutions.

Practice ML system design

All Rights Reserved.