Knowledge / Computer Science and Engineering / Linked lists

Linked lists

Computer Science and Engineering (B.E./B.Tech) · Data structures · free study notes for exams, placements and interviews

In short

A linked list stores a sequence as separate nodes, each holding data and the address of the next node, so the elements need not be adjacent in memory. It trades the array's constant-time random access for constant-time insertion and deletion once you hold a pointer to the place, and for growth without reallocation. It is the first data structure examined after arrays in every Indian university syllabus (AICTE/CBCS "Data Structures" paper) and in GATE CS, where pointer-manipulation code-reading questions appear regularly. In placement rounds — TCS NQT, Infosys HackWithInfy, Wipro NTH, Cognizant, and product interviews at Amazon, Flipkart, Microsoft IDC, Zoho, Adobe and Oracle — reversal, cycle detection, middle-node and merge problems are among the most asked coding questions.

Explained

Node, representation and the basic vocabulary

Types of linked lists

  1. Singly linked list (SLL): one next link; traversal in one direction only. Deleting a node needs its predecessor, so deletion by value is O(n).
  2. Doubly linked list (DLL): prev and next; traversal both ways, and deletion of a node whose pointer you already hold is O(1). Costs one extra pointer per node and four pointer writes per insertion.
  3. Circular singly linked list (CSLL): the last node points back to the first; there is no NULL terminator, so every traversal loop must stop on returning to the start. Keeping a tail pointer makes insertion at both front and rear O(1) — the standard queue implementation.
  4. Circular doubly linked list (CDLL): head->prev = tail, tail->next = head. Any node can reach any other; this is the structure behind std::list and the Linux kernel's list_head (Java's LinkedList is doubly linked but not circular).
  5. Multiply linked / multi-list: several independent link chains through the same nodes (for example a student record linked both by roll number and by branch) — used for sparse matrices and in multilist file organisation.
  6. Unrolled linked list: each node holds a small array of elements, cutting pointer overhead and improving cache behaviour.
  7. XOR (memory-efficient doubly) linked list: stores prev XOR next in one field — examinable as a trick, unsafe in practice (breaks with address translation and garbage collection).
  8. Skip list: a layered, randomised set of linked lists giving expected O(log n) search — a probabilistic alternative to balanced trees, used in Redis sorted sets.

Core operations, step by step

Traversal: for (p = head; p != NULL; p = p->next) visit(p); — O(n), one pass; a circular list uses do { … } while (p != head);

Insertion (SLL)

Deletion (SLL)

Search: sequential only — O(n); binary search is not applicable, because reaching the midpoint itself costs O(n).

Sorting: merge sort is the method of choice — O(n log n), no element movement (only links change), and no extra array; split with the slow/fast pointer. Quicksort on lists suffers from the lack of random access; insertion sort on a list is O(n²) but does no shifting.

Standard problems and techniques (the interview core)

Complexity, memory and when to prefer a list

Library and system implementations

Key points

Formulas and examples

Memory of a list, M = n × (d + p × s) + n × h
n = number of nodes; d = size of the data field; p = number of pointers (1 singly, 2 doubly); s = pointer size (8 B on 64-bit, 4 B on 32-bit); h = allocator header + padding per node (bytes)
Array element address = base + i × (element size); linked list has no such formula — the link must be stored and followed
Average comparisons, sequential search: successful = (n + 1)/2; unsuccessful = n
Floyd's cycle detection: slow covers s steps, fast covers 2s; they meet when (2s − s) = s ≡ 0 (mod λ) and s ≥ μ, i.e. after s = λ⌈μ/λ⌉ steps — the smallest multiple of λ that is at least μ
μ = number of nodes before the cycle starts; λ = cycle length; total nodes = μ + λ
Josephus (every k-th eliminated, 0-indexed): J(1) = 0, J(n) = (J(n−1) + k) mod n; survivor's 1-based position = J(n) + 1
Pointer writes per operation: SLL insert = 2, SLL delete = 1, DLL insert = 4, DLL delete = 2

**Example 1 — memory overhead (64-bit Linux, int data). Singly: 4 B data + 8 B pointer = 12 B, padded to a multiple of 8 → 16 B. Doubly: 4 + 8 + 8 = 20 B → padded to 24 B. For n = 10,000 nodes: array = 10,000 × 4 = 40,000 B ≈ 39.1 KB; SLL = 10,000 × 16 = 160,000 B ≈ 156.3 KB (4.0×); DLL = 10,000 × 24 = 240,000 B ≈ 234.4 KB** (6.0×). Adding a 16 B malloc header per node, the SLL becomes 10,000 × 32 = 320,000 B ≈ 312.5 KB, i.e. 8× the array.

Example 2 — insertion at the front, array vs list. An array holds 1,00,000 integers. Each front insertion shifts all 1,00,000 elements. For 1,000 such insertions: 1,000 × 1,00,000 = 10⁸ element moves (seconds of work). A linked list does 2 pointer writes each → 1,000 × 2 = 2,000 writes (microseconds). But to *read* the 50,000th element, the array needs 1 operation and the list needs ~50,000 link hops.

Example 3 — Floyd's cycle detection, traced. Ten nodes N1…N10; N10 points back to N7, so μ = 6 (N1…N6 lie before the cycle) and λ = 4 (N7, N8, N9, N10). Formula: s = λ⌈μ/λ⌉ = 4 × ⌈6/4⌉ = 4 × 2 = 8 steps to meet. Trace (position index, 0-based, N1 = 0):

Stepslowfast
00 (N1)0 (N1)
112
224
336
448
556
668
776
88 (N9)8 (N9)

They meet at N9 after 8 steps, as predicted. Now reset one pointer to N1 and move both one step: after 6 steps the first is at N7 and the second has gone N9→N10→N7→N8→N9→N10→N7 — both at N7, the start of the cycle. Total nodes = μ + λ = 6 + 4 = 10. ✓

Example 4 — Josephus with a circular list, n = 7, k = 3. Recurrence: J(1)=0; J(2)=(0+3) mod 2=1; J(3)=(1+3) mod 3=1; J(4)=(1+3) mod 4=0; J(5)=(0+3) mod 5=3; J(6)=(3+3) mod 6=0; J(7)=(0+3) mod 7=3 → survivor = 3 + 1 = position 4. Simulation on a circular list confirms the elimination order 3, 6, 2, 7, 5, 1 → survivor 4. Each elimination is one O(1) unlink after walking k−1 nodes, so the whole run is O(nk).

Example 5 — polynomial addition with sorted lists. P1 = 5x⁴ + 3x² + 7 (3 nodes), P2 = 4x³ + 3x² + 2x (3 nodes). Walk both in decreasing exponent, copying the larger exponent and adding on a tie: x⁴: 5 → x³: 4 → x²: 3+3 = 6 → x¹: 2 → x⁰: 7. Sum = 5x⁴ + 4x³ + 6x² + 2x + 7, 5 nodes, at most m + n = 6 comparisons — O(m+n), with no wasted storage for the missing powers (an array of coefficients would need 5 slots here and 101 for degree 100 with three terms).

OperationArraySingly list (head only)Doubly list (head+tail)
Access i-thO(1)O(n)O(n)
Search (unsorted)O(n)O(n)O(n)
Binary searchO(log n)not possiblenot possible
Insert at frontO(n)O(1)O(1)
Insert at rearO(1) amortisedO(n)O(1)
Delete a given nodeO(n) (shift)O(n) (needs predecessor)O(1)
Extra memorynone1 pointer/node2 pointers/node
Resizingreallocate + copynot needednot needed
Cache localityexcellentpoorpoor
struct Node { int data; struct Node *next; };

void push(struct Node **head, int x) {            /* insert at front, O(1) */
    struct Node *p = malloc(sizeof *p);
    p->data = x; p->next = *head; *head = p;
}

void insertAfter(struct Node *q, int x) {         /* O(1); order of the 2 lines matters */
    struct Node *p = malloc(sizeof *p);
    p->data = x; p->next = q->next; q->next = p;
}

void delKey(struct Node **head, int key) {        /* O(n), handles all 3 cases */
    struct Node *cur = *head, *prev = NULL;
    while (cur && cur->data != key) { prev = cur; cur = cur->next; }
    if (!cur) return;                             /* key absent */
    if (!prev) *head = cur->next;                 /* first node */
    else       prev->next = cur->next;
    free(cur);
}

struct Node *reverse(struct Node *head) {         /* O(n) time, O(1) space */
    struct Node *prev = NULL, *cur = head, *nxt;
    while (cur) { nxt = cur->next; cur->next = prev; prev = cur; cur = nxt; }
    return prev;                                  /* new head */
}

struct Node *loopStart(struct Node *head) {       /* Floyd: detect + locate */
    struct Node *s = head, *f = head;
    while (f && f->next) {
        s = s->next; f = f->next->next;
        if (s == f) { s = head;
            while (s != f) { s = s->next; f = f->next; }
            return s; }
    }
    return NULL;                                  /* no cycle */
}

Reversal trace on 1→2→3→4: after iteration 1, prev=1 (1→NULL), cur=2; after 2, prev=2 (2→1→NULL); after 3, prev=3; after 4, prev=4 and the list is 4→3→2→1→NULL — 4 iterations, 4 pointer writes.

Where it is used

Interview questions

Common mistakes

Related topics

Open in the Knowledge reader →Find jobs →