Understand
30-second explanation
Build the interview-ready mental model before comparing implementations.
To delete index k, walk to its predecessor (index k − 1) and relink past the target: prev.next = prev.next.next. Position 0 is the special case — there's no predecessor, so return head.next as the new head. O(n) time, O(1) space.
Interview contextWhat is testedWhere candidates slipHow to stand outExplore interview contextHide interview context
What's being tested
Whether you reach the predecessor (index − 1), relink past the target without losing the rest of the list, and treat deleting the head as a separate case that changes the returned head.
Where candidates slip
Walking to the target node itself instead of its predecessor, or not handling position 0 and dereferencing a null predecessor.
How to stand out
Name the two invariants: stop one node early, and relink prev.next = prev.next.next. Call out that position 0 returns head.next, which is why the function returns a head.
Choose
Compare the available approaches
Use constraints and trade-offs to select the route you can defend in an interview.
Interactive decision treeInteractiveChoose the right approachFirst decision previewDelete node at 0-based positionposition == 0?YesNoExplore the full treeCollapse the tree
Learn & trace
Build each solution step by step
Move from the core idea to a visual trace, implementation, proof, and edge cases.
Approach 1 of 2 · Optimal
Iterative — Walk to Predecessor, Then Relink (Optimal)
1Hint 1Explore hintHide hint
To unlink the node at index k, which node's next pointer do you actually have to change — the target's, or the one before it?
2Hint 2Explore hintHide hint
Reaching index k − 1 takes k − 1 steps. What do you do when k is 0 and there's no node before the target?
3Hint 3Explore hintHide hint
Once you're on the predecessor, how do you make the target disappear from the chain with a single pointer assignment?
A · Core idea
We reach the node just before the target and rewire its next to jump over the target.
Why the predecessor. In a singly linked list you remove a node by editing the next of the node before it. So to delete index k, get a handle on index k − 1 and set its next to the target's next. That's why we walk position − 1 steps, not position — landing on the target itself gives you no way to bypass it.
The relink, in one line. Standing on prev, do prev.next = prev.next.next. prev.next is the target; prev.next.next is the node after it. After the assignment, nothing points to the target, so it's unreachable and gets garbage-collected. The rest of the list is untouched.
Why deleting the head is special. Index 0 has no predecessor — there's no prev.next to edit. Instead the head advances: return head.next. This is exactly why the function returns a head; only the head-delete changes it, and the caller must learn the new head.
Why the loop is counted. We stop at a fixed index, so a for loop of position − 1 iterations lands precisely on the predecessor. A condition-driven walk to the tail would overshoot.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
list = [1, 2, 3, 4], position = 2Array state
start
prev on head (index 0)
[1, 2, 4]C · Code with teaching comments
D · Why it works and complexityO(n) timeO(1) spaceOpen the invariant and proofExplore the proofHide the proof
Deletion is insertion run backward: instead of saving the tail and splicing in, you read past the target and relink over it. The named trap is the head-delete case — forget it and you dereference a null predecessor or fail to advance the head.
ComplexityO(n) time, O(1) space — At most position − 1 ≤ n − 1 steps to reach the predecessor, then O(1) relink. No extra structures → O(1) space.
E · Edge cases and pitfalls7list = [5], position = 0 — Head delete: return head.next = null → empty list.Explore edge casesHide edge cases
Edge cases handled
list = [5], position = 0Head delete: return head.next = null → empty list.list = [1, 2, 3], position = 2Walk 1 step to node 2; prev.next.next is null; prev.next = null removes the tail → [1, 2].list = [1, 2], position = 0Return head.next = node 2 → [2].
Pitfalls
- Walking position steps instead of position − 1 — you land on the target and can't bypass it.
- Not handling position 0 — dereferences a null predecessor or fails to advance the head.
- Forgetting to return the head — a head delete never reaches the caller.
- Assuming position may be out of range — here it's guaranteed valid, but in general validate it.
Approach 2 of 2
Dummy Head — Uniform Delete Without the Special Case
1Hint 1Explore hintHide hint
What if a fake node sat in front of the real head so that even the head has a 'predecessor'?
2Hint 2Explore hintHide hint
Anchor prev on the dummy, walk position steps from there, then relink the same way.
3Hint 3Explore hintHide hint
Return dummy.next at the end — it tracks the real head whether or not you deleted index 0.
A · Core idea
A dummy node placed before the head gives every real node a predecessor, including the original head — so the position-0 case stops being special.
Why the dummy removes the branch. With dummy.next = head, deleting index 0 is just relinking dummy.next = dummy.next.next, identical to deleting any other index. There's no longer a 'the head has no predecessor' exception, so one code path handles all positions.
Walk count shifts by one. Because prev starts on the dummy (one slot before the real head), we walk position steps (not position − 1) to reach the predecessor of index position. Then the relink prev.next = prev.next.next is the same.
Return dummy.next. The real head is always dummy.next after the operation — it equals the original head when we deleted elsewhere, or the second node when we deleted index 0. Returning it covers both without a branch.
Trade-off. Same O(n)/O(1) cost as the direct version, slightly more setup but no special case — a common pattern that pays off in problems with many edge cases (remove duplicates, remove by value).
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
list = [1, 2, 3], position = 0Array state
setup
prev on dummy D
[2, 3]C · Code with teaching comments
Reference implementation previewpublic ListNode deleteNode(ListNode head, int position) { ListNode dummy = new ListNode(0, head); ListNode prev = dummy; for (int i = 0; i < position; i++) { prev = prev.next;Explore the full codeHide the full code
D · Why it works and complexityO(n) timeO(1) spaceOpen the invariant and proofExplore the proofHide the proof
The dummy head turns 'the head is special' into 'the head is just another node'. It's the same sentinel trick used to build lists in Merge Two Sorted Lists, applied here to deletion.
ComplexityO(n) time, O(1) space — Same single walk of up to position ≤ n steps and an O(1) relink → O(n) time. One dummy node and a pointer → O(1) space.
E · Edge cases and pitfalls3Walking position − 1 steps from the dummy — off by one; from the dummy it's position steps.Explore edge casesHide edge cases
Pitfalls
- Walking position − 1 steps from the dummy — off by one; from the dummy it's position steps.
- Returning head instead of dummy.next — misses the new head when index 0 was deleted.
- Forgetting to set dummy.next = head, leaving the dummy detached from the list.
Practice
Try the solution yourself
Edit the optimal implementation, test another input, and observe the result.
Editable code previewpublic class ListNode { int val; ListNode next; ListNode() {} ListNode(int val) { this.val = val; }Explore the playgroundClose the playground
Avoid mistakes
Recognize the interview traps
Understand why common attempts fail before reviewing the corrected implementation.
1Stopping on the target instead of the predecessor
Walking position steps lands you on the node to delete. From there you can't change the predecessor's next, so you can't unlink it cleanly. Stop at position − 1.
Explore the code comparisonHide the code comparison
Stopping on the target instead of the predecessor
Walking position steps lands you on the node to delete. From there you can't change the predecessor's next, so you can't unlink it cleanly. Stop at position − 1.
Don't write this
ListNode cur = head;
for (int i = 0; i < position; i++) {
cur = cur.next; // now ON the target — can't bypass it
}Write this instead
ListNode prev = head;
for (int i = 0; i < position - 1; i++) {
prev = prev.next; // stop on the predecessor
}
prev.next = prev.next.next;2Ignoring the head-delete case
Index 0 has no predecessor. Without the branch, walking position − 1 = −1 steps leaves prev on the head and deletes index 1, or a dummy-less relink dereferences null.
Explore the code comparisonHide the code comparison
Ignoring the head-delete case
Index 0 has no predecessor. Without the branch, walking position − 1 = −1 steps leaves prev on the head and deletes index 1, or a dummy-less relink dereferences null.
Don't write this
prev = head
for _ in range(position - 1):
prev = prev.next # misbehaves when position == 0
prev.next = prev.next.nextWrite this instead
if position == 0:
return head.next
prev = head
for _ in range(position - 1):
prev = prev.next
prev.next = prev.next.nextRevise
Carry the pattern into the interview
Pattern to remember
Walk to predecessor, relink past the target (prev.next = prev.next.next)
Code pattern to memorize
if position == 0: return head.next
prev = head
repeat position - 1 times: prev = prev.next
prev.next = prev.next.next
return headRules to remember
- 1To delete index
k, stop on indexk − 1— walkposition − 1steps to reach the predecessor. - 2Relink with
prev.next = prev.next.next— this skips the target so it's no longer reachable. - 3Position
0has no predecessor: return `head.next` as the new head (don't try to walk). - 4The head can change (deleting index 0), so the function must return the head.
- 5Read
prev.nextbefore relinking; deleting the head and an empty list are the two boundary cases to name.
Use this pattern when
- Remove the node at a known 0-based index from a singly linked list
- Drop an element while keeping the rest of the chain intact
- Any 'unlink one node' subproblem (remove duplicates, remove by value)
- Cleanup steps inside larger list rewrites
Choose another pattern when
- You only have a reference to the node to delete, not its predecessor, and it isn't the tail → copy the next node's value and skip it instead
- You delete from the front repeatedly → that's an O(1) pop-head, no walk needed
- Random index deletes happen constantly → an array or balanced structure may fit better
- It's a doubly linked list with the node in hand → unlink in O(1) using prev/next directly
Interview takeaway
Deletion is the mirror of insertion: reach the predecessor, then rewire one link to bypass the target. Get the walk-to-position-minus-one and the head-delete case right and remove-by-value and remove-duplicates fall out of the same skeleton.
Continue
Transfer the pattern to another problem
Use a direct variation or pattern sibling to prove the idea is reusable.
Transferable patternWalk-to-predecessor then relink-past-the-target — the deletion mirror of the insert splice. The dummy-head variant erases the head special case and is the same sentinel idiom used to build lists.
Practice queueDirect variations and pattern siblings
Direct follow-ups
- Insert at a Specific PositionThe inverse: walk to position − 1, then splice in instead of relinking past.
- Remove Nth Node From End of ListFind the predecessor relative to the tail with a two-pointer gap, then do the same relink.
- Remove Linked List ElementsComing soonDelete every node with a given value — same relink, driven by value instead of index, dummy head shines.