Scala Lists – Why Appending Has O(n) Time Complexity

immutabilitylanguage-designlistscala

I just read that the execution time of the append operation for a List (:+) grows linearly with the size of the List.

Appending to a List seems like a pretty common operation. Why should the idiomatic way to do this be prepending the components and then reversing the list? It can't also be a design failure as implementation could be changed at any point.

From my point of view, both prepending and appending should be O(1).

Is there any legitimate reason for this?

Best Answer

I'll expand my comment a bit. The List[T] data structure, from scala.collection.immutable is optimized to work the way an immutable list in a more purely functional programming language works. It has very fast prepend times, and it is assumed that you will be working on the head for almost all of your access.

Immutable lists get to have very fast prepend times due to the fact that they model their linked lists as a series of "cons cells". The cell defines a single value, and a pointer to the next cell (classic singly-linked-list style):

Cell [Value| -> Nil]

When you prepend to a list, you're really just making a single new cell, with the rest of the existing list being pointed to:

Cell [NewValue| -> [Cell[Value| -> Nil]]

Because the list is immutable, you're safe to do this without any actual copying. There's no danger of the old list changing and causing all the values in your new list to become invalid. However, you lose the ability to have a mutable pointer to the end of your list as a compromise.

This lends itself very well to recursively working on lists. Let's say you defined your own version of filter:

def deleteIf[T](list : List[T])(f : T => Boolean): List[T] = list match {
  case Nil => Nil
  case (x::xs) => f(x) match {
    case true => deleteIf(xs)(f)
    case false => x :: deleteIf(xs)(f)
  }
}

That's a recursive function that works from the head of the list exclusively, and takes advantage of pattern matching via the :: extractor. This is something you see a lot of in languages like Haskell.

If you really want fast appends, Scala provides a lot of mutable and immutable data structures to choose from. On the mutable side, you might look into ListBuffer. Alternatively, Vector from scala.collection.immutable has a fast append time.