LogIn
I don't have account.

Mastering LinkedList in C#

DevSniper

197 Views

When working with collections in C#, developers often reach for List<T> or HashSet<T> because they’re simple and efficient for many use cases. However, there are some situation when you need a collection that supports fast insertions and deletions at any position and allows bidirectional traversal ,that’s exactly when LinkedList<T> is the best choice.

LinkedList<T> is a doubly linked list, meaning each node maintains references to both its previous and next nodes. This design makes operations like AddFirst, AddLast, AddBefore and AddAfter highly efficient , all of these can run in O(1) time if you already have a reference to the target node. According to the official documentation:

LinkedList<T> is a general-purpose linked list… insertion and removal are O(1) operations.

This makes LinkedList<T> ideal for scenarios such as

  • Implementing queues, deques or LRU caches.
  • Managing ordered sequences where frequent insertions and removals occur in the middle.
  • Maintaining direct references to nodes so they can be moved efficiently without shifting entire arrays (unlike List<T>).

What is LinkedList<T> in C#?

LinkedList<T> is a generic collection class available in the System.Collections.Generic namespace. It represents a doubly linked list, where each element (or node) holds a value and pointers to both its previous and next nodes.

Unlike List<T>, which stores items in a contiguous memory block, a LinkedList<T> allocates each node independently in dynamic memory, connecting them through links (pointers). This structure makes it highly flexible for frequent insertions and deletions anywhere in the list.

Key Characteristics of LinkedList<T>

  • Doubly Linked Structure : Each node keeps track of its previous and next nodes, enabling traversal in both directions , forward and backward.
  • Fast Insertions and Removals (O(1)) : When you already have a reference to a node, inserting before or after it or removing it takes constant time because only the pointers are updated.
  • Sequential Access (No Indexing) : You cannot directly access elements by index (like myList[3]). Traversal is linear (O(n)), meaning you must iterate through nodes to reach a specific element.
  • Allows Duplicates and Nulls : LinkedList<T> allows duplicate values and for reference types, null values are also valid.
  • Dynamic Memory Allocation : Unlike List<T>, LinkedList<T> doesn’t rely on a continuous internal array. Each node exists independently in memory, making it flexible but with slightly more overhead.
  • Heap Allocation : All LinkedList<T> objects and their nodes are allocated on the managed heap.
  • Supports Duplicate Elements : Unlike HashSet<T> or SortedSet<T>, a LinkedList<T> allows duplicate values. If you add the same element multiple times, each entry is stored as a separate node in the list.
  • Empty List Behavior : When the list is empty, the First and Last properties return null. This helps developers easily check whether a linked list currently holds any nodes.
  • Not Thread-Safe : LinkedList<T> is not synchronized by default. If multiple threads need to access or modify the same list concurrently, you must handle synchronization manually (e.g. using locks or concurrent collections).
Namespace :- System.Collections.Generic
Assembly :- System.Collections.dll
Signature :-
public class LinkedList<T> : 
   System.Collections.Generic.ICollection<T>,
   System.Collections.Generic.IEnumerable<T>,
   System.Collections.Generic.IReadOnlyCollection<T>,
   System.Collections.ICollection,
   System.Runtime.Serialization.IDeserializationCallback,
   System.Runtime.Serialization.ISerializable

LinkedList<T> Constructors

1. LinkedList<T>()

Initializes a new, empty instance of the LinkedList<T> class.

Copy
var list = new LinkedList<string>();

Usage : Use this constructor when you want to start with an empty linked list and add elements later using AddFirst(), AddLast() or AddBefore() / AddAfter() methods.

2. LinkedList<T>(IEnumerable<T> collection)

Initializes a new instance of the LinkedList<T> class that contains elements copied from the specified collection, maintaining their order.

Copy
var numbers = new List<int> { 10, 20, 30 };
var linkedList = new LinkedList<int>(numbers);

Usage : Use when you want to create a linked list from an existing collection (like a List<T> or an array) and preserve the sequence of elements as they appear in the original collection.

LinkedList<T> Properties

1. Count

Copy
public int Count { get; }

Gets the number of elements that are currently contained in the LinkedList<T>.

Example
Copy
var list = new LinkedList<int>();
list.AddLast(10);
list.AddLast(20);
Console.WriteLine(list.Count); // Output: 2

Usage : This is use to check how many nodes are present in the linked list.

2. First

Copy
public LinkedListNode<T>? First { get; }

Gets the first node (LinkedListNode<T>) of the LinkedList<T>. Returns null if the list is empty.

Copy
var list = new LinkedList<string>();
list.AddLast("C#");
list.AddLast("Java");
Console.WriteLine(list.First.Value); // Output: C#

Usage : Use when you need direct access to the first node of the linked list to perform operations such as adding before or after it.

3. Last

Copy
public LinkedListNode<T>? Last { get; }

Gets the last node (LinkedListNode<T>) of the LinkedList<T>. Returns null if the list is empty.

Copy
var list = new LinkedList<string>();
list.AddLast("C#");
list.AddLast("Python");
Console.WriteLine(list.Last.Value); // Output: Python

Usage : Use when you want to access or modify the last node or when appending new elements efficiently using AddAfter() or AddLast().

Creating and Adding Elements in LinkedList<T>

1. Creating an Empty LinkedList

Creates an empty LinkedList<string> with no nodes. At this point, both First and Last are null.

Example
Copy
using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        var linkedlist = new LinkedList<string>();
        linkedlist.AddLast("Python");
        linkedlist.AddLast("Java");
        linkedlist.AddLast("C#");
        linkedlist.AddFirst("React");
        linkedlist.AddFirst("C++");
        linkedlist.AddLast("Go");
        foreach (var lst in linkedlist) {
            Console.WriteLine(lst);
        }
    }
}
C++
React
Python
Java
C#
Go

2. Creating and Initializing with a Collection

Initializes a new LinkedList<int> with the elements from an existing collection (List<int> in this case). The elements are added in the same order as they appear in the source collection.

Usage : This is mostly used when you already have data in another collection and want to convert it into a LinkedList<T>.

Example
Copy
using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        // Source collection
        var languages = new List<string> { "C++", "React", "Python", "Java", "C#", "Go" };
        // Initialize LinkedList from existing collection
        var linkedLanguages = new LinkedList<string>(languages);
        foreach (var lang in linkedLanguages)
        {
            Console.WriteLine(lang);
        }
    }
}
C++
React
Python
Java
C#
Go

When to Use LinkedList<T>

  • Fast Insertions or Removals are Needed : You need quick insertions or deletions at known positions , especially at the beginning or end of the list or in the middle when you already have a reference to a specific node.
  • Frequent Mid-Collection Updates : You often add or remove elements in the middle of a large collection, where shifting elements (as in List<T>) would be inefficient.
  • Node Reusability : You need to move nodes between lists efficiently without creating new objects or copying data, as each node (LinkedListNode<T>) can belong to only one list at a time.

When to avoid LinkedList<T>

  • Random Access is Needed : You require fast, index-based access to elements (e.g. list[5]), which LinkedList<T> doesn’t support. Use List<T> instead.
  • Mostly Static or Large Collections : The collection size is stable and lookups or iterations are more common than insertions or deletions. List<T> performs better here.
  • Memory Efficiency Matters : Each node in LinkedList<T> carries additional overhead (two references: Previous and Next), making it less memory-efficient than List<T>.
  • High-Performance Iteration is Crucial : Sequential traversal in a LinkedList<T> is typically slower due to non-contiguous memory allocation. For CPU cache efficiency, List<T> is superior.

Real-World Use Cases of LinkedList<T>

1. Music Playlist or Media Queue

When building a music player, video playlist or slideshow viewer, songs or items often need to move forward/backward dynamically. LinkedList<T> allows easy insertion, deletion and navigation between previous and next tracks.

Copy
var playlist = new LinkedList<string>();
playlist.AddLast("Song A");
playlist.AddLast("Song B");
playlist.AddLast("Song C");

var current = playlist.First;
Console.WriteLine("Now Playing: " + current.Value);  // Song A
current = current.Next;
Console.WriteLine("Next: " + current.Value);          // Song B

Why LinkedList? : No need to reindex or shift elements. you can directly move to Next or Previous songs efficiently.

2. LRU (Least Recently Used) Cache

An LRU cache keeps frequently used data easily accessible and removes the least recently used ones. LinkedList<T> is perfect because it allows O(1) insertion and removal from both ends.

Copy
class LRUCache
{
    private readonly int capacity;
    private readonly LinkedList<int> list = new();
    private readonly HashSet<int> set = new();
    public LRUCache(int capacity) => this.capacity = capacity;
    public void Access(int item)
    {
        if (set.Contains(item))
            list.Remove(item);
        else if (list.Count == capacity)
            set.Remove(list.Last.Value), list.RemoveLast();
        list.AddFirst(item);
        set.Add(item);
    }
}

Why LinkedList? : Quickly move frequently used items to the front and remove old ones from the end , both in constant time.

3. Navigation Systems (Browser History / Back-Forward Stack)

Web browsers or apps that maintain history navigation (e.g. back and forward) can use LinkedList<T> to maintain order and allow bidirectional traversal.

Copy
var history = new LinkedList<string>();
history.AddLast("Home");
history.AddLast("About");
history.AddLast("Contact");

var current = history.Last;
Console.WriteLine("Current Page: " + current.Value); // Contact
Console.WriteLine("Back to: " + current.Previous.Value); // About

Why LinkedList? : You can easily navigate both directions and remove or add new pages dynamically.