The Tail Trimmer
An easy Python interview practice problem on DataDriven. Write the Python and run it against test cases, with instant feedback.
- Domain
- Python
- Difficulty
- easy
- Seniority
- L3
Problem
What does a request log lst, held in arrival order, look like once the entry k positions back from the newest end is dropped? Return it as a new list without touching lst, and when k reaches past the oldest entry, return the log unchanged.
Remove the k-th item from the back without counting forward first.
Example 1
Input:
lst = [1,2,3,4,5], k = 2
Output:
[1,2,3,5]
Example 2
Input:
lst = [1,2,3], k = 1
Output:
[1,2]
Worked solution and explanation
This is an index-translation problem dressed up as log housekeeping. Anyone can say 'the k-th from the end'. The skill is turning that into a forward position, len(lst) - k, and checking k against the length before you touch anything. The trap is Python's negative indexing: it looks made for this, and it is wrong at both edges. lst[:-k] + lst[-k+1:] falls apart at k = 1, because -k+1 is 0 and lst[0:] is the whole log, so nothing gets removed and a full copy is glued back on. lst.pop(-k) raises IndexError once k reaches past the oldest entry, where the contract says to hand the log back unchanged.
Reasoning it out
Step 1: Guard before you compute
Check k > len(lst) first. That one comparison covers both an oversized k and the empty log, and it is the only way k can go wrong, so the rest of the function never sees a bad position. The k <= 0 half is defensive: the contract promises a positive k.
Step 2: Flip the position to count from the front
The newest entry sits at len(lst) - 1 and is k = 1, so the k-th from the end sits at len(lst) - k. Check it at both boundaries: k = 1 gives the last index, and k = len(lst) gives 0, the oldest entry.
Step 3: Slice around the gap
lst[:idx] + lst[idx + 1:] is everything before the entry plus everything after it. Both bounds are non-negative and in range, so there is no sign flip to reason about, and the caller's list is only read.
def remove_kth_from_end(lst: list, k: int) -> list:
if k <= 0 or k > len(lst):
return lst
idx = len(lst) - k
result = lst[:idx] + lst[idx + 1:]
return result- Negative-index shortcut
lst[:-k] + lst[-k+1:]atk= 1: the second slice islst[0:], the full log, so nothing is dropped.lst.pop(-k)withk= 9 on three entries raisesIndexError, and when it does work it mutates the caller's list.- Forward index after a guard
idx = len(lst) - kruns only afterk > len(lst)has returned early. Every slice bound lies between 0 andlen(lst), the same line handlesk= 1 andk=len(lst), and the input is never modified.
Common follow-up questions
- How would you do this in place? (`del lst[len(lst) - k]` mutates the caller's list and still shifts the tail, so it saves space but not time.)
- What if you could not compute the length in advance? (Tests the two-pointer approach: send a leader `k` steps ahead, then move both until the leader runs off the end.)
- What if a negative `k` meant counting from the start? (Tests extending the guard and the index translation to work in both directions.)