diff --git a/lib/Drupal/Core/Entity/ChunkedIterator.php b/lib/Drupal/Core/Entity/ChunkedIterator.php
new file mode 100644
index 0000000000..da4db612bd
--- /dev/null
+++ b/lib/Drupal/Core/Entity/ChunkedIterator.php
@@ -0,0 +1,76 @@
+<?php
+
+namespace Drupal\Core\Entity;
+
+use Drupal\Core\Cache\MemoryCache\MemoryCacheInterface;
+
+/**
+ * Provides an Iterator class for dealing with large amounts of entities
+ * but not loading them all into memory.
+ */
+class ChunkedIterator implements \IteratorAggregate, \Countable {
+
+  /**
+   * The entity storage controller to load entities.
+   *
+   * @var \Drupal\Core\Entity\EntityStorageInterface
+   */
+  protected $entityStorage;
+
+  /**
+   * An array of entity IDs to iterate over.
+   *
+   * @var array
+   */
+  protected $entityIds;
+
+  /**
+   * The size of each chunk of loaded entities.
+   *
+   * @var int
+   */
+  protected $chunkSize;
+
+  /**
+   * The memory cache to store but also reset loaded entities.
+   *
+   * @var \Drupal\Core\Cache\MemoryCache\MemoryCacheInterface
+   */
+  protected $memoryCache;
+
+  /**
+   * Constructs an entity iterator object.
+   *
+   * @param \Drupal\Core\Entity\EntityStorageInterface $entity_storage
+   * @param \Drupal\Core\Cache\MemoryCache\MemoryCacheInterface $memory_cache
+   * @param array $ids
+   * @param int $chunk_size
+   */
+  public function __construct(EntityStorageInterface $entity_storage, MemoryCacheInterface $memory_cache, array $ids, $chunk_size = 50) {
+    $this->entityStorage = $entity_storage;
+    $this->memoryCache = $memory_cache;
+    // Make sure we don't use a keyed array.
+    $this->entityIds = array_values($ids);
+    $this->chunkSize = (int) $chunk_size;
+  }
+
+  /**
+   * Implements \Countable::count().
+   */
+  public function count() {
+    return count($this->entityIds);
+  }
+
+  /**
+   * Implements \IteratorAggregate::GetIterator()
+   */
+  public function getIterator() {
+    foreach (array_chunk($this->entityIds, $this->chunkSize) as $ids_chunk) {
+      yield from $this->entityStorage->loadMultiple($ids_chunk);
+      // We clear all memory cache as we want to remove all referenced entites
+      // as well, like for example the owner of an entity.
+      $this->memoryCache->deleteAll();
+    }
+  }
+
+}
diff --git a/tests/Drupal/Tests/Core/Entity/ChunkedIteratorTest.php b/tests/Drupal/Tests/Core/Entity/ChunkedIteratorTest.php
new file mode 100644
index 0000000000..a2aa92f567
--- /dev/null
+++ b/tests/Drupal/Tests/Core/Entity/ChunkedIteratorTest.php
@@ -0,0 +1,190 @@
+<?php
+
+namespace Drupal\Tests\Core\Entity;
+
+use Drupal\Core\Cache\MemoryCache\MemoryCacheInterface;
+use Drupal\Core\Entity\ChunkedIterator;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityStorageInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @group Entity
+ * @coversDefaultClass \Drupal\Core\Entity\ChunkedIterator
+ */
+class ChunkedIteratorTest extends UnitTestCase {
+
+  /**
+   * The entity storage.
+   *
+   * @var \Drupal\Core\Entity\EntityStorageInterface|\Prophecy\Prophecy\ObjectProphecy
+   */
+  protected $entityStorage;
+
+  /**
+   * The entity.
+   *
+   * @var \Drupal\Core\Entity\EntityInterface|\Prophecy\Prophecy\ObjectProphecy
+   */
+  protected $entity;
+
+  /**
+   * The entity type.
+   *
+   * @var \Drupal\Core\Entity\EntityInterface|\Prophecy\Prophecy\ObjectProphecy
+   */
+  protected $entityType;
+
+  /**
+   * @var \Drupal\Core\Cache\MemoryCache\MemoryCacheInterface|\Prophecy\Prophecy\ObjectProphecy
+   */
+  protected $memoryCache;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() : void {
+    $this->entityStorage = $this->prophesize(EntityStorageInterface::class);
+    $this->memoryCache = $this->prophesize(MemoryCacheInterface::class);
+    $this->entity = $this->prophesize(EntityInterface::class);
+    $this->entityType = $this->prophesize(EntityTypeInterface::class);
+  }
+
+  /**
+   * @covers ::count
+   */
+  public function testCountWithNoItems() {
+    $this->entityStorage->loadMultiple()->shouldNotBeCalled();
+    $iterator = new ChunkedIterator($this->entityStorage->reveal(), $this->memoryCache->reveal(), []);
+    $this->assertSame(0, $iterator->count());
+  }
+
+  /**
+   * @covers ::getIterator
+   */
+  public function testIterationWithNoItems() {
+    $this->entityStorage->loadMultiple()->shouldNotBeCalled();
+
+    $iterator = new ChunkedIterator($this->entityStorage->reveal(), $this->memoryCache->reveal(), []);
+    iterator_to_array($iterator);
+  }
+
+  /**
+   * @covers ::getIterator
+   */
+  public function testIteratorWithNoValidItems() {
+    $this->memoryCache->deleteAll()
+      ->shouldBeCalledTimes(1);
+    $this->entityStorage->loadMultiple([1, 2, 3])
+      ->willReturn([])
+      ->shouldBeCalled();
+
+    $iterator = new ChunkedIterator($this->entityStorage->reveal(), $this->memoryCache->reveal(), [1, 2, 3]);
+    iterator_to_array($iterator);
+  }
+
+  /**
+   * @covers ::getIterator
+   */
+  public function testIteratorWithOneChunkValidItems() {
+    $return = [
+      1 => $this->entity->reveal(),
+      2 => $this->entity->reveal(),
+      3 => $this->entity->reveal(),
+    ];
+
+    $this->memoryCache->deleteAll()
+      ->shouldBeCalledTimes(1);
+    $this->entityStorage->loadMultiple([1, 2, 3])
+      ->willReturn($return)
+      ->shouldBeCalled();
+
+    $iterator = new ChunkedIterator($this->entityStorage->reveal(), $this->memoryCache->reveal(), [1, 2, 3]);
+
+    $this->assertSame($return, iterator_to_array($iterator));
+  }
+
+  /**
+   * @covers ::getIterator
+   */
+  public function testIteratorWithOneChunkInvalidItems() {
+    $return = [
+      2 => $this->entity->reveal(),
+      3 => $this->entity->reveal(),
+    ];
+
+    $this->memoryCache->deleteAll()
+      ->shouldBeCalledTimes(1);
+    $this->entityStorage->loadMultiple([1, 2, 3])
+      ->willReturn($return)
+      ->shouldBeCalled();
+
+    $iterator = new ChunkedIterator($this->entityStorage->reveal(), $this->memoryCache->reveal(), [1, 2, 3]);
+
+    $this->assertSame($return, iterator_to_array($iterator));
+  }
+
+  /**
+   * @covers ::getIterator
+   */
+  public function testIteratorWithMultipleChunkValidItems() {
+    $return_1 = [
+      1 => $this->entity->reveal(),
+      2 => $this->entity->reveal(),
+      3 => $this->entity->reveal(),
+    ];
+
+    $return_2 = [
+      4 => $this->entity->reveal(),
+      5 => $this->entity->reveal(),
+      6 => $this->entity->reveal(),
+    ];
+
+    $this->memoryCache->deleteAll()
+      ->shouldBeCalledTimes(2);
+    $this->entityStorage->loadMultiple([1, 2, 3])
+      ->willReturn($return_1)
+      ->shouldBeCalled();
+    $this->entityStorage->loadMultiple([4, 5, 6])
+      ->willReturn($return_2)
+      ->shouldBeCalled();
+
+    // Create a new iterator but set the cache limit to 3. Two chunks should be
+    // loaded.
+    $iterator = new ChunkedIterator($this->entityStorage->reveal(), $this->memoryCache->reveal(), [1, 2, 3, 4, 5, 6], 3);
+
+    $this->assertSame($return_1 + $return_2, iterator_to_array($iterator));
+  }
+
+  /**
+   * @covers ::getIterator
+   */
+  public function testIteratorWithMultipleChunkInvalidItems() {
+    $return_1 = [
+      2 => $this->entity->reveal(),
+      3 => $this->entity->reveal(),
+    ];
+
+    $return_2 = [
+      5 => $this->entity->reveal(),
+      6 => $this->entity->reveal(),
+    ];
+
+    $this->memoryCache->deleteAll()
+      ->shouldBeCalledTimes(2);
+    $this->entityStorage->loadMultiple([1, 2, 3])
+      ->willReturn($return_1)
+      ->shouldBeCalled();
+    $this->entityStorage->loadMultiple([4, 5, 6])
+      ->willReturn($return_2)
+      ->shouldBeCalled();
+
+    // Create a new iterator but set the cache limit to 3. Two chunks should be
+    // loaded.
+    $iterator = new ChunkedIterator($this->entityStorage->reveal(), $this->memoryCache->reveal(), [1, 2, 3, 4, 5, 6], 3);
+
+    $this->assertSame($return_1 + $return_2, iterator_to_array($iterator));
+  }
+
+}
