C# tutorials > Core C# Fundamentals > Data Structures and Collections > What is the difference between `List<T>`, `ArrayList`, and `LinkedList<T>`?

What is the difference between `List<T>`, `ArrayList`, and `LinkedList<T>`?

This tutorial explores the key differences between `List `, `ArrayList`, and `LinkedList ` in C#. Understanding these differences is crucial for choosing the right data structure for optimal performance in your applications. We'll cover their underlying implementations, performance characteristics, and appropriate use cases.

Overview of `List`, `ArrayList`, and `LinkedList`

  • `List`: A generic, strongly-typed collection that represents an ordered list of objects. It is implemented as a dynamically-sized array. `List` is generally the most performant option for most common scenarios.
  • `ArrayList`: A non-generic collection that can store objects of any type. It also uses a dynamically-sized array internally. However, due to boxing and unboxing of value types, it's less efficient than `List`. `ArrayList` is mostly obsolete in modern C# code due to the availability of generics.
  • `LinkedList`: A generic collection that represents a doubly-linked list. Each element in the list stores a pointer to the next and previous elements. This makes it efficient for insertions and deletions at arbitrary positions in the list, but less efficient for random access.

Key Differences Summarized

The main differences between these collections can be summarized in the following table:

Feature`List``ArrayList``LinkedList`
Type SafetyGeneric (strongly-typed)Non-generic (object)Generic (strongly-typed)
Underlying ImplementationDynamically-sized arrayDynamically-sized arrayDoubly-linked list
Insertion/Deletion (Middle)O(n) - relatively slowO(n) - relatively slowO(1) - very fast
Random Access (by index)O(1) - very fastO(1) - very fastO(n) - slow
Memory OverheadLower (contiguous memory)Lower (contiguous memory)Higher (pointers for each node)
Boxing/UnboxingNo boxing/unboxingBoxing/unboxing for value typesNo boxing/unboxing

Concepts Behind the Snippet

The core concepts behind these data structures are:

  • Arrays: Contiguous blocks of memory that allow for fast access to elements by index. Resizing an array involves creating a new, larger array and copying the elements over, which can be a relatively expensive operation.
  • Linked Lists: A collection of nodes, where each node contains a value and a pointer to the next (and sometimes previous) node. Linked lists offer flexible memory allocation and efficient insertion/deletion operations, but random access is slow.
  • Generics: A feature that allows you to write code that can work with any data type without sacrificing type safety or performance. `List` and `LinkedList` are generic, while `ArrayList` is not.

Code Example: Demonstrating Usage

This example demonstrates basic operations on each type of collection. Notice how `ArrayList` can store different data types, while `List` and `LinkedList` are type-safe. Also, `LinkedList` uses a different API for adding elements (e.g., `AddFirst`, `AddLast`, `AddAfter`).

csharp
using System; using System.Collections; using System.Collections.Generic;

public class CollectionComparison
{
    public static void Main(string[] args)
    {
        // List<T>
        List<int> intList = new List<int>();
        intList.Add(10); intList.Add(20); intList.Insert(1, 15);
        Console.WriteLine($"List<T> count: {intList.Count}, Element at index 0: {intList[0]}");

        // ArrayList
        ArrayList arrayList = new ArrayList();
        arrayList.Add(100); arrayList.Add("Hello"); arrayList.Insert(1, 150);
        Console.WriteLine($"ArrayList count: {arrayList.Count}, Element at index 0: {arrayList[0]}");

        // LinkedList<T>
        LinkedList<string> linkedList = new LinkedList<string>();
        linkedList.AddFirst("First"); linkedList.AddLast("Last"); linkedList.AddAfter(linkedList.First, "Middle");
        Console.WriteLine($"LinkedList<T> count: {linkedList.Count}, First element: {linkedList.First.Value}");
    }
}

Real-Life Use Case Section

  • `List`: Suitable for scenarios where you need to store a collection of elements and frequently access them by index. Examples include storing a list of products in an e-commerce application, managing a list of students in a class, or storing data read from a database.
  • `ArrayList`: Rarely used in modern C# development due to its lack of type safety. Legacy codebases may still use it. Avoid using it in new projects.
  • `LinkedList`: Useful when you need to perform frequent insertions and deletions in the middle of the collection. Examples include implementing a text editor (where characters are frequently inserted and deleted), managing a history of commands, or implementing a cache.

When to Use Them

  • Use `List` when:
    • You need a type-safe, ordered collection.
    • You frequently access elements by index.
    • The number of elements is not known in advance, but you don't expect frequent insertions/deletions in the middle of the list.
  • Avoid `ArrayList` unless:
    • You are working with legacy code that uses it.
    • You need to store objects of different types in the same collection (which is generally bad practice).
  • Use `LinkedList` when:
    • You need to perform frequent insertions and deletions in the middle of the collection.
    • Random access is not a frequent requirement.

Memory Footprint

  • `List`: Generally has a lower memory footprint than `LinkedList` because it stores elements in contiguous memory. However, resizing the underlying array can lead to temporary increases in memory usage.
  • `ArrayList`: Similar memory footprint to `List` when storing value types, but boxing/unboxing can add overhead.
  • `LinkedList`: Has a higher memory footprint because each element requires additional memory to store pointers to the next and previous elements.

Pros and Cons

`List`:

  • Pros: Type-safe, efficient random access, generally good performance for most operations.
  • Cons: Slower insertion/deletion in the middle of the list compared to `LinkedList`.

`ArrayList`
  • Pros: Can store objects of any type (but this is generally a disadvantage).
  • Cons: Not type-safe, performance overhead due to boxing/unboxing, generally obsolete.

`LinkedList`
  • Pros: Efficient insertion/deletion in the middle of the list.
  • Cons: Slower random access, higher memory footprint.

Best Practices

  • Always prefer `List` over `ArrayList` in new C# code.
  • Choose the data structure that best matches the usage pattern of your application.
  • Consider the trade-offs between memory usage, performance, and complexity when selecting a data structure.
  • Use profiling tools to measure the performance of your code and identify potential bottlenecks.

Interview Tip

When discussing these data structures in an interview, emphasize your understanding of their underlying implementations, performance characteristics, and appropriate use cases. Be prepared to discuss the trade-offs involved in choosing one data structure over another. Also, be sure to state that you would almost always prefer `List` to `ArrayList` in modern C# development.

Alternatives

  • `HashSet`: Useful when you need to store a collection of unique elements and quickly check if an element is present.
  • `Dictionary`: Useful for storing key-value pairs and quickly looking up values by key.
  • `Queue`: Represents a first-in, first-out collection.
  • `Stack`: Represents a last-in, first-out collection.

FAQ

  • When would I ever use `ArrayList`?

    In modern C# development, `ArrayList` is rarely the best choice. It might be encountered in older codebases or when interacting with COM objects that require it. However, for new development, generics like `List` provide better type safety and performance.
  • Is `List` always the best choice for a list-like structure?

    Not always. If you require frequent insertions and deletions in the middle of the list and don't need fast random access, `LinkedList` may be a better option, despite its higher memory overhead.
  • How does `List` resize its internal array?

    When a `List` reaches its capacity, it typically doubles its size by allocating a new array and copying the elements from the old array to the new one. This operation can be relatively expensive, so it's good practice to initialize the `List` with an appropriate initial capacity if you know the approximate number of elements it will hold.