Delete Node in a Linked List — The Copy-and-Skip Trick Explained
Advertisement
Problem Statement
There is a singly-linked list and you are given access to a node to be deleted. You will not be given access to the first node of the list. Delete the given node. Note that by deleting the node, we do not mean removing it from memory — we mean:
- The value of the given node should not exist in the linked list.
- The number of nodes in the linked list should decrease by one.
- All the values before the given node should be in the same order.
- All the values after the given node should be in the same order.
Constraints:
- The number of nodes in the list is in the range
[2, 1000] -1000 <= Node.val <= 1000- The value of each node in the list is unique
- The node to be deleted is in the list and is not the tail node
Example 1:
Input: head = [4, 5, 1, 9], node = 5
Output: [4, 1, 9]
Explanation: The node with value 5 is not in the result.Example 2:
Input: head = [4, 5, 1, 9], node = 1
Output: [4, 5, 9]Why This Problem Matters
Delete Node in a Linked List (LeetCode 237) is deceptively simple — but it trips up candidates who haven't seen the trick before. Adobe and Microsoft use it as a quick filter in phone screens because it tests a specific kind of creative thinking: what do you do when the standard approach is impossible?
In the standard linked list deletion, you have access to the node's predecessor. You find prev.next == target, then set prev.next = target.next. That's impossible here. You're handed only the node to delete, with no way to traverse backwards or find the predecessor.
The insight required is lateral: instead of deleting the node itself, transform it into its successor. Copy the successor's value into the current node, then skip the successor. From the outside, the effect is identical to deletion — but the mechanism is completely different.
Beyond the interview setting, this pattern appears in real-world scenarios: embedded systems where you only have a reference to an element (not the container), doubly linked list deletion without the list header, and certain concurrent data structure designs where backward traversal is prohibited.
The Core Insight
You cannot delete yourself from a singly linked list — you don't know who points to you. But you can impersonate your successor.
Copy node.next.val into node.val. Now the current node looks like its successor. Then skip node.next by setting node.next = node.next.next. The successor node is now unreachable — effectively deleted.
The net effect: the value that was at node disappears, replaced by the value that was at node.next. The structure of the list is unchanged except that node.next is gone.
Why this works: The problem guarantees the node is not the tail (so node.next always exists). The values are unique, so there's no ambiguity about which value got "deleted."
Visual Dry Run
Input: 4 -> 5 -> 1 -> 9, delete the node with value 5
| Step | Action | List State |
|---|---|---|
| Before | Given node = [5, next->1] | 4 -> 5 -> 1 -> 9 |
| 1 | Copy: node.val = node.next.val = 1 | 4 -> 1 -> 1 -> 9 (temporarily) |
| 2 | Skip: node.next = node.next.next = 9's node | 4 -> 1 -> 9 |
| After | Node with original value 5 is gone | 4 -> 1 -> 9 |
The second 1 node (the original node.next) is now unreachable — garbage collected.
Solution (Optimal)
Python
def deleteNode(node):
# Copy the next node's value into this node
node.val = node.next.val
# Skip the next node (which now has a duplicate value)
node.next = node.next.nextTwo lines. No loops, no conditions, no return value (the function signature is void — it modifies in place).
JavaScript
var deleteNode = function(node) {
node.val = node.next.val; // overwrite current with next's value
node.next = node.next.next; // skip the next node
};Complexity:
| Metric | Value |
|---|---|
| Time | O(1) |
| Space | O(1) |
This is one of the rare O(1) time operations on a linked list — no traversal, just two pointer assignments.
Common Mistakes
1. Trying to traverse to find the predecessor. You are not given the head. Even if you were, traversing to find the predecessor makes this O(n). The intended solution is O(1).
2. Forgetting this only works for non-tail nodes.
The problem guarantees the node is not the tail, so node.next is always non-null. If the node could be the tail, this trick would fail (you'd need special handling — but that's a different problem).
3. Returning a value when the function should be void.
The function signature takes only node and returns nothing. The modification is in-place. Do not return node or attempt to return a modified list.
4. Trying to delete the node's memory.
In garbage-collected languages (Python, Java, JavaScript), simply making node.next unreachable is sufficient — the garbage collector reclaims it. In C/C++, you'd free(node->next) after the pointer reassignment, but before setting node->next = node->next->next (or you lose the reference). The Python/JavaScript solution above is correct as-is.
5. Second-guessing the approach. Some candidates think "this doesn't actually delete the node" — but from the caller's perspective, the value is gone and the list length is decreased. That is the definition of deletion for this problem.
Interview Tips
-
State the constraint clearly: "I don't have access to the head, so I can't find the predecessor. I'll use the copy-and-skip trick instead."
-
Mention the assumptions: "This only works because the problem guarantees the node is not the tail — so
node.nextis always non-null." -
Explain the tradeoff: "The downside is that we're not deleting the actual node object — we're overwriting its value. If other references to this node exist outside the list, they'd see the changed value. For most use cases this is acceptable."
-
Follow-up awareness: If asked "what if this could be the tail?", you'd need to return a boolean or throw an exception — the trick is impossible for the tail without head access.
-
Code speed: This should take under 60 seconds. If it takes longer, the interviewer may question your linked list fluency.
Follow-up Questions
Q: What if the node could be the tail?
If node.next is None (tail node), the copy-and-skip trick fails because there's nothing to copy from. In that case, you'd need access to the head to traverse and find the predecessor. The problem explicitly prevents this case.
Q: What if multiple nodes could have the same value? The problem guarantees unique values. If values are not unique, the copy approach still works mechanically (you're just replacing with the next node's value), but the semantics of "which value was deleted" might be ambiguous to the caller.
Q: Can you do this with a doubly linked list?
With a doubly linked list, you can access node.prev directly. The standard deletion node.prev.next = node.next; node.next.prev = node.prev works without the copy trick. You do need the tail case still handled.
Q: Is this approach safe in concurrent programming?
No — modifying node.val and node.next non-atomically can create race conditions if other threads are traversing the list. In concurrent scenarios, compare-and-swap (CAS) operations or locking are needed.
Q: How does this relate to the "delete middle node" problem? Similar trick: find the middle with fast/slow pointers, then apply copy-and-skip. But the standard "delete middle" (LC 2095) gives you the head, so you can use the normal predecessor-skip approach.
Key Takeaways
- When you only have the node to delete (not the head), use copy-and-skip: overwrite
node.valwithnode.next.val, then setnode.next = node.next.next. - This is O(1) time and O(1) space — no traversal needed.
- The trick only works for non-tail nodes —
node.nextmust be non-null. - The function is void (in-place modification) — do not return a value.
- From the caller's perspective, the effect is identical to true deletion: the value is gone, the list is shorter by one.
- This lateral thinking problem tests whether you can abandon the standard approach and find an equivalent solution from a different angle.
Advertisement