Bird
Raised Fist0
Interview Preplinked-list-advancedmediumAmazonGoogleMicrosoft

Design Linked List (Full Implementation)

Choose your preparation mode4 modes available

Start learning this pattern below

Jump into concepts and practice - no test required

or
Recommended
Test this pattern10 questions across easy, medium, and hard to know if this pattern is strong
📋
Problem

Imagine building a custom playlist manager where songs can be added, removed, or retrieved at any position efficiently. Designing a linked list from scratch is the foundation for such dynamic data structures.

Design a singly linked list with the following functionalities: - get(index): Get the value of the index-th node in the linked list. If the index is invalid, return -1. - addAtHead(val): Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list. - addAtTail(val): Append a node of value val to the last element of the linked list. - addAtIndex(index, val): Add a node of value val before the index-th node in the linked list. If index equals the length of the linked list, the node will be appended to the end. If index is greater than the length, the node will not be inserted. - deleteAtIndex(index): Delete the index-th node in the linked list, if the index is valid.

0 ≤ index ≤ 10^5All values will be in the range [1, 1000]At most 10^4 calls will be made to get, addAtHead, addAtTail, addAtIndex and deleteAtIndex.
Edge cases: get from empty list → returns -1addAtIndex with index greater than length → no insertiondeleteAtIndex with invalid index → no deletion
</>
IDE
class MyLinkedList:class MyLinkedList {class MyLinkedList {class MyLinkedList {
class MyLinkedList:
    def __init__(self):
        # Initialize your data structure here.
        pass
    def get(self, index: int) -> int:
        pass
    def addAtHead(self, val: int) -> None:
        pass
    def addAtTail(self, val: int) -> None:
        pass
    def addAtIndex(self, index: int, val: int) -> None:
        pass
    def deleteAtIndex(self, index: int) -> None:
        pass
class MyLinkedList {
    public MyLinkedList() {
        // Initialize your data structure here.
    }
    public int get(int index) {
        return -1;
    }
    public void addAtHead(int val) {
    }
    public void addAtTail(int val) {
    }
    public void addAtIndex(int index, int val) {
    }
    public void deleteAtIndex(int index) {
    }
}
#include <iostream>
using namespace std;
class MyLinkedList {
public:
    MyLinkedList() {
        // Initialize your data structure here.
    }
    int get(int index) {
        return -1;
    }
    void addAtHead(int val) {
    }
    void addAtTail(int val) {
    }
    void addAtIndex(int index, int val) {
    }
    void deleteAtIndex(int index) {
    }
};
class MyLinkedList {
    constructor() {
        // Initialize your data structure here.
    }
    get(index) {
        return -1;
    }
    addAtHead(val) {
    }
    addAtTail(val) {
    }
    addAtIndex(index, val) {
    }
    deleteAtIndex(index) {
    }
}
0/9
Common Bugs to Avoid
Wrong: get returns -1 even for valid indicesIncorrect index validation or traversal logic stopping early.Ensure traversal continues until index-th node is reached and check for None only after traversal.
Wrong: addAtIndex inserts node even when index > lengthMissing or incorrect boundary check for index in addAtIndex.Add condition: if index > length, do not insert node.
Wrong: deleteAtIndex deletes node at invalid indexNo validation of index before deletion.Check if index < length before deleting node.
Wrong: addAtTail causes TLE on large inputsTraversing entire list to add at tail causing O(n^2) complexity.Maintain a tail pointer to add at tail in O(1) time.
Wrong: get returns wrong values after deleteAtIndexPointer updates after deletion are incorrect causing list corruption.Update next pointers correctly and maintain list integrity after deletion.
Test Cases
t1_01basic
Input["addAtHead(1)","addAtTail(3)","addAtIndex(1, 2)","get(1)","deleteAtIndex(1)","get(1)"]
Expected[null,null,null,2,null,3]

After adding 1 at head and 3 at tail, list is [1,3]. Adding 2 at index 1 makes list [1,2,3]. get(1) returns 2. Deleting index 1 removes 2, list becomes [1,3]. get(1) now returns 3.

t1_02basic
Input["addAtHead(5)","addAtHead(10)","addAtTail(15)","get(0)","get(2)","deleteAtIndex(1)","get(1)"]
Expected[null,null,null,10,15,null,15]

Start with empty list. addAtHead(5) -> [5], addAtHead(10) -> [10,5], addAtTail(15) -> [10,5,15]. get(0) returns 10, get(2) returns 15. deleteAtIndex(1) removes 5, list becomes [10,15]. get(1) returns 15.

t2_01edge
Input["get(0)"]
Expected[-1]

Getting from an empty list returns -1 as no nodes exist.

t2_02edge
Input["addAtIndex(1, 10)","get(0)"]
Expected[null,-1]

addAtIndex with index greater than length (0) does not insert. get(0) returns -1 since list is still empty.

t2_03edge
Input["addAtHead(1)","deleteAtIndex(1)","get(0)"]
Expected[null,null,1]

deleteAtIndex with invalid index (1) does nothing. List remains [1]. get(0) returns 1.

t3_01corner
Input["addAtHead(1)","addAtTail(2)","addAtIndex(1, 3)","get(1)","deleteAtIndex(0)","get(0)"]
Expected[null,null,null,3,null,1]

Test off-by-one errors: addAtIndex(1,3) inserts 3 between 1 and 2. get(1) returns 3. deleteAtIndex(0) removes 1, list becomes [3,2]. get(0) returns 1.

t3_02corner
Input["addAtTail(1)","addAtTail(2)","addAtTail(3)","addAtIndex(3, 4)","get(3)","deleteAtIndex(3)","get(3)"]
Expected[null,null,null,null,4,null,-1]

Test adding at index equal to length (append). addAtIndex(3,4) appends 4. get(3) returns 4. deleteAtIndex(3) removes 4. get(3) returns -1 (invalid).

t3_03corner
Input["addAtHead(1)","addAtHead(1)","addAtHead(1)","get(0)","get(1)","get(2)","deleteAtIndex(1)","get(1)"]
Expected[null,null,null,1,1,1,null,1]

All elements identical. After three insertions at head, list is [1,1,1]. get returns 1 for all valid indices. deleteAtIndex(1) removes middle node. get(1) returns 1 (last node).

t4_01performance
Input["addAtHead(1)","addAtTail(2)","addAtTail(3)","addAtTail(4)","addAtTail(5)","addAtTail(6)","addAtTail(7)","addAtTail(8)","addAtTail(9)","addAtTail(10)","addAtTail(11)","addAtTail(12)","addAtTail(13)","addAtTail(14)","addAtTail(15)","addAtTail(16)","addAtTail(17)","addAtTail(18)","addAtTail(19)","addAtTail(20)","addAtTail(21)","addAtTail(22)","addAtTail(23)","addAtTail(24)","addAtTail(25)","addAtTail(26)","addAtTail(27)","addAtTail(28)","addAtTail(29)","addAtTail(30)","addAtTail(31)","addAtTail(32)","addAtTail(33)","addAtTail(34)","addAtTail(35)","addAtTail(36)","addAtTail(37)","addAtTail(38)","addAtTail(39)","addAtTail(40)","addAtTail(41)","addAtTail(42)","addAtTail(43)","addAtTail(44)","addAtTail(45)","addAtTail(46)","addAtTail(47)","addAtTail(48)","addAtTail(49)","addAtTail(50)","addAtTail(51)","addAtTail(52)","addAtTail(53)","addAtTail(54)","addAtTail(55)","addAtTail(56)","addAtTail(57)","addAtTail(58)","addAtTail(59)","addAtTail(60)","addAtTail(61)","addAtTail(62)","addAtTail(63)","addAtTail(64)","addAtTail(65)","addAtTail(66)","addAtTail(67)","addAtTail(68)","addAtTail(69)","addAtTail(70)","addAtTail(71)","addAtTail(72)","addAtTail(73)","addAtTail(74)","addAtTail(75)","addAtTail(76)","addAtTail(77)","addAtTail(78)","addAtTail(79)","addAtTail(80)","addAtTail(81)","addAtTail(82)","addAtTail(83)","addAtTail(84)","addAtTail(85)","addAtTail(86)","addAtTail(87)","addAtTail(88)","addAtTail(89)","addAtTail(90)","addAtTail(91)","addAtTail(92)","addAtTail(93)","addAtTail(94)","addAtTail(95)","addAtTail(96)","addAtTail(97)","addAtTail(98)","addAtTail(99)","addAtTail(100)"]
⏱ Performance - must finish in 2000ms

Test with n=100 addAtTail operations to check performance. The solution must run in O(n) time per operation and complete within 2 seconds.

Practice

(1/5)
1. You are given a singly linked list where each node contains an additional pointer that can point to any node in the list or null. The task is to create a deep copy of this list, preserving both the next and random pointer structure. Which approach guarantees an optimal solution with O(n) time and O(1) extra space?
easy
A. Interleave copied nodes within the original list, assign random pointers using the interleaved structure, then separate the lists.
B. Use a hash map to store original-to-copy node mappings and create the copy in two passes.
C. Use a recursive function with memoization to copy nodes and their random pointers.
D. Traverse the list multiple times, copying nodes and random pointers separately without extra data structures.

Solution

  1. Step 1: Understand the problem constraints

    The problem requires copying a complex linked list with random pointers efficiently.
  2. Step 2: Identify the optimal approach

    Interleaving copied nodes within the original list allows assigning random pointers without extra space, then separating the lists restores the original and creates the copy.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Interleaving nodes achieves O(n) time and O(1) space [OK]
Hint: Interleaving nodes avoids extra space for mapping [OK]
Common Mistakes:
  • Assuming recursion is always optimal
  • Using extra hash maps increases space complexity
  • Trying multiple traversals without mapping fails random pointer assignment
2. You need to split a singly linked list into k parts such that the sizes of the parts differ by at most one, and the order of nodes is preserved. Which approach guarantees an optimal solution with minimal passes over the list and clean code structure?
easy
A. Use a greedy approach that assigns nodes to parts until the next part would be larger, then move on.
B. Split the list by repeatedly removing the head node and appending it to parts until all nodes are distributed.
C. Precompute the total length, then split the list in-place using dummy heads and careful pointer manipulation.
D. Apply dynamic programming to decide the best split points to minimize size differences.

Solution

  1. Step 1: Understand the problem constraints

    The parts must differ in size by at most one, and the original order must be preserved.
  2. Step 2: Evaluate approaches

    Greedy and naive removal approaches do not guarantee minimal passes or clean code. Dynamic programming is unnecessary overhead. Precomputing length and using dummy heads allows a single pass split with clear structure.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    Precomputing length and dummy heads -> minimal passes and clean code [OK]
Hint: Precompute length, then split in-place with dummy heads [OK]
Common Mistakes:
  • Assuming greedy splitting always works
  • Using DP unnecessarily
  • Splitting without precomputing length
3. Consider the following buggy recursive code to convert a binary number in a linked list to an integer. Which line contains the subtle bug that causes incorrect output for single-node lists?
medium
A. Line X: using addition instead of bitwise OR to accumulate bits
B. Line 7: base case check for None node
C. Line 3: __init__ method of ListNode
D. Line 9: recursive call with node.next and updated acc

Solution

  1. Step 1: Identify the accumulation operation

    The code uses bitwise OR instead of addition to combine bits: acc = (acc << 1) | node.val.
  2. Step 2: Understand impact on single-node lists

    Bitwise OR correctly sets bits without carry, ensuring accurate accumulation for all list lengths.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Bitwise OR correctly sets bits without carry, addition may overflow [OK]
Hint: Use bitwise OR, not addition, to accumulate bits [OK]
Common Mistakes:
  • Using + instead of | causes wrong bit accumulation
  • Misunderstanding bitwise operations vs arithmetic
  • Assuming addition and OR are interchangeable for bits
4. Consider the following buggy code snippet for reversing nodes in k-groups. Which line contains the subtle bug that can cause incorrect output when the list length is not a multiple of k?
medium
A. Line missing the check 'if count < k: break' after counting nodes
B. Line with 'group_next = kth.next' assignment
C. Line with 'while kth.next and count < k:' loop
D. Line with 'tmp.next = group_next' pointer update

Solution

  1. Step 1: Identify missing boundary check

    The code does not check if the remaining nodes are fewer than k before reversal.
  2. Step 2: Consequence of missing check

    Without 'if count < k: break', partial groups get reversed incorrectly, breaking problem constraints.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Missing break causes partial group reversal [OK]
Hint: Always check group size before reversal [OK]
Common Mistakes:
  • Forgetting to break on partial group
  • Misplacing pointer updates
  • Assuming kth always valid
5. Suppose the linked list nodes can be reused multiple times (i.e., the list is cyclic or nodes appear multiple times). Which modification to the reorder list algorithm is necessary to handle this scenario correctly?
hard
A. Use a hash set to track visited nodes and avoid infinite loops during reordering.
B. No modification needed; the recursive approach handles cycles naturally.
C. Convert the list to an array first to handle duplicates, then reorder using indices.
D. Break the list into halves without reversing the second half to prevent cycles.

Solution

  1. Step 1: Understand node reuse implications

    Reusing nodes or cycles cause infinite loops if not detected during traversal.
  2. Step 2: Modify algorithm to track visited nodes

    Using a hash set to track visited nodes prevents revisiting and infinite loops.
  3. Step 3: Evaluate other options

    Recursive approach alone cannot detect cycles; array conversion adds overhead; skipping reversal breaks reorder logic.
  4. Final Answer:

    Option A -> Option A
  5. Quick Check:

    Tracking visited nodes prevents infinite loops in cyclic lists [OK]
Hint: Cycle detection requires visited node tracking [OK]
Common Mistakes:
  • Assuming no cycles exist
  • Relying on recursion without cycle checks
  • Ignoring node reuse effects