Skip to content
Skilled.dev

Free lesson walkthrough · C#

Implement a linked list in C#: head, tail, and node identity

Build a doubly linked list in C#. Trace prepend and append, verify reciprocal links and both endpoints, and explain ownership and retention.

4 min read · Worked code, edge cases, and complexity

This lesson implements linked-list operations from a head node, then compares them with a doubly linked wrapper that retains a tail and length. Those interfaces expose different information. An operation that is direct with a saved tail may require traversal when the function receives only a head.

Make head replacement part of the contract

Prepending creates a node whose next reference points to the old head, then returns the new node. The caller must retain that returned head. Appending from a head-only interface follows successors until it reaches the final node, then attaches the new node. Empty input needs a separate path because there is no final node to update.

For an insertion into the middle, first locate the predecessor. Set the new node's next reference to the predecessor's old successor before connecting the predecessor to the new node. This order preserves the remaining chain. Removing a node similarly reconnects its predecessor to its successor. Removing the head changes the returned head instead.

Maintain both directions in a doubly linked wrapper

A wrapper with head, tail, and length must keep them consistent after every mutation. In a nonempty doubly linked list, a next link and the corresponding previous link should agree. Prepending changes the old head's previous reference; appending changes the old tail's next reference. An empty-to-nonempty transition must establish both endpoints.

The constructor in the current example creates a first node, so its initial length is one. A zero initial count would make later increments disagree with the chain. Test the constructor state before testing any method. A correct append cannot repair a count that was already wrong.

Charge for the traversal you perform

Given the needed predecessor, changing a fixed number of links uses constant work. Finding a position from the head takes linear time. Retaining a tail makes append direct, but removing a singly linked tail still requires finding its predecessor. A recursive traversal also adds stack depth that a loop avoids.

Check an empty chain, one node, insertion at the head, and insertion at the end. Walk forward after every mutation and backward for the doubly linked version. Use the current implementation lesson for its exact index contracts, then apply the same reference discipline to Reverse a Linked List.

The complete example here is a doubly linked list with a nonempty constructor, prepend, and append. Starting at 5, prepend 2 and append 9. Forward traversal should read [2, 5, 9]; backward traversal should read [9, 5, 2]. Length should be three, the head's previous link absent, and the tail's next link absent.

On prepend, retain the old head before replacing it. Connect the new head forward to the old head, then connect the old head backward to the new one. Append performs the corresponding endpoint updates. Checking only forward traversal can miss a broken previous link.

Describe the API that is actually provided

The constructor creates one node. This excerpt does not implement an empty-list constructor, removal, or indexed insertion. The main lesson develops additional operations separately. Calling this a complete general-purpose list library would overstate what the shown class supports.

Each prepend and append changes a fixed number of links, for O(1) structural work and one new allocation. Traversal takes O(n), and nodes occupy O(n) storage. Shared forward and backward ownership can form a retention cycle; Swift uses weak previous links and Rust uses Weak for that reason. Check several alternating prepends/appends, repeated payloads, both traversal directions, and the length. Compare node identity when verifying reciprocal links, because equal values can hide references to the wrong node.

C# example and implementation choices

The list and node classes use reference semantics, with public fields exposing the representation directly for the lesson. Equal payloads do not imply equal nodes, so reciprocal-link checks should compare references. The constructor creates one entry and there is no empty-list removal path here. Garbage collection handles unreferenced cycles, while externally retained nodes can keep a larger portion of the structure reachable.

Complete implementation

public class DoublyNode {
    public int data;
    public DoublyNode next;
    public DoublyNode prev;

    public DoublyNode(int data) {
        this.data = data;
        this.next = null;
        this.prev = null;
    }
}

public class DoublyLinkedList {
    public int length;
    public DoublyNode head;
    public DoublyNode tail;

    public DoublyLinkedList(int data) {
        var initialNode = new DoublyNode(data);

        this.length = 1;
        this.head = initialNode;
        this.tail = initialNode;
    }

    // O(1)
    public DoublyNode Prepend(int data) {
        var prependedNode = new DoublyNode(data);
        DoublyNode previousHead = this.head;

        this.head = prependedNode;
        this.head.next = previousHead;

        previousHead.prev = this.head;

        this.length++;

        return this.head;
    }

    // O(1)
    public DoublyNode Append(int data) {
        var appendedNode = new DoublyNode(data);
        DoublyNode previousTail = this.tail;

        this.tail = appendedNode;
        this.tail.prev = previousTail;

        previousTail.next = this.tail;

        this.length++;

        return appendedNode;
    }
}

Continue in C#

Open Implement a Linked List for the complete course explanation. The course keeps your saved programming-language choice. Use this standalone page to inspect the C# example and run it in your own local environment. Follow the original course sequence when you want the surrounding lesson context and related exercises.

The C# interview preparation guide connects these language choices with further practice and the cross-language operation reference.

Continue with the lesson

Open the original lesson to explore its full explanation and examples. Your saved programming-language choice stays unchanged. This reading page belongs to that lesson and does not create a separate progress record.