Dart - Copy/Clone List (Shallow & Deep) Examples

In this tutorial, I'm going to show you how to copy or clone a List in Dart, both shallow copy and deep copy.

Dart supports List data type. Copying a List in Dart is quite simple and can be done in various ways. While a List is copied to a new one each element on the new List may or may not refer to the same memory location. It's essential to understand the difference to avoid unexpected results. One of the examples is if you modify an element of the new List, it may also modify the same element on the source List. This tutorial is divided into two parts, shallow copy and deep copy.

Shallow Copy/Clone

Shallow copy means the main object (the List) is copied, but the inner objects (the elements) are not. If the element type of the List is immutable such as String, bool, int, or bool, shallow copy should be enough. The reason is an immutable value cannot be modified, so Dart will create a new object at a different memory location if the value is changed. Below are the examples of how to perform a shallow copy.

Using List.of

One of the methods you can use is List.of. It accepts an Iterable as the first argument where you pass the List to be copied. It also has another optional named argument growable which defaults to true. If the growable value is set to false, the number of elements cannot be changed, which means you cannot add or remove any element.

  factory List.of(Iterable<E> elements, {bool growable = true})

Example:

  var originalList = <int>[1, 2, 3, 4];
  var newList = List.of(originalList); // return List<int>
  var newNumList = List.of(originalList); // return List<num>

Using List.from

List.from is similar to List.of. The difference is the resulting List element type can be a subtype of the source List element type.

  factory List.from(Iterable elements, {bool growable = true})

Example:

  var originalList = <num>[1, 2, 3, 4];
  var newList = List.from(originalList); // return List<dynamic>
  var newIntList = List<int>.from(originalList); // return List<int>

Using List.unmodifiable

If you want to create a new List that cannot be modified, you can use List.umodifiable.

  factory List.unmodifiable(Iterable elements)

Example:

  var values = List.unmodifiable(['one', 'two', 'three']);

Using List.copyRange

List.copyRange can be used to copy a List from a source to a target List. You need to specify at which index the copied elements should be started to put in the target. Optionally, you can set the start (inclusive) and the end (exclusive) indexes of the source List elements to be copied. To avoid error, you have to make sure that the target has enough capacity to put the elements.

  static void copyRange<T>(
      List<T> target,
      int at,
      List<T> source,
      [int? start, int? end]
  )

Example:

  var source = <int>[11, 12, 13, 14];
  var target = [1, 2, 3, 4, 5];
  List.copyRange(target, 1, source, 1, 3);
  print(target); // [1, 12, 13, 4, 5]

Using List.toList

Dart's Iterable has toList method that can also be used to create a new List from an existing one.

  List<E> toList({bool growable = true})

Example:

  var numbers = [1, 2, 3];
  var numbersCopy = numbers.toList();

Using Spread Operator

Dart's spread operator can also be used to copy a List.

  var originalList = <int>[1, 2, 3, 4];
  var newList = [
    ...originalList,
  ];
  print(newList); // [1, 2, 3, 4]

Deep Copy/Clone

The examples above only use immutable elements. If you modify an element of the source, it will not affect the target or the new List, and the other way around. However, if the List contains mutable elements, you need to be careful. If you use one of the methods above, Dart will perform a shallow copy by default. That's because with shallow copy, Dart only copies the reference to the object. If what you want to do is a deep copy, you have to clone each element by creating a new one. Dart doesn't have deep copy implementation because it depends on the data structure of the elements. Therefore, you have to make your own implementation for performing a deep copy.

For example, we have a List whose element type is Item. Since we have to clone the Item object, it would be easier to create a factory method for cloning. The clone method below is responsible for creating a new Item object from a source Item object.

  class Item {
    String name;
  
    Item({
      required this.name,
    });
  
    factory Item.clone(Item source) {
      return Item(
        name: source.name,
      );
    }
  }

Having created the clone method, use Iterable's map method to map each element by calling the clone method.

  List<Item> source = [Item(name: 'One'), Item(name: 'Two')];
  List<Item> clone = source.map((o) => Item.clone(o)).toList();
  source[0].name = 'Three';
  print(source[0].name); // Three
  print(clone[0].name); // One

Summary

That's how to copy a List in Dart. If the List only contains immutable elements, you can simply use one of the methods provided by Dart. For a List with mutable elements, you may need to clone each element if necessary.