Down the Line
A medium Python interview practice problem on DataDriven. Write the Python and run it against test cases, with instant feedback.
- Domain
- Python
- Difficulty
- medium
- Seniority
- L4
Problem
A round-robin scheduler keeps its consumers in a fixed order and advances the assignment by shift slots each cycle so the load spreads evenly down the line. Given the consumers list and a shift, return the order rotated to the right by shift, so the last shift consumers wrap around to the front. The shift can exceed the roster length or be negative (a left rotation), and an empty roster comes back empty.
Load has to keep moving. Pass it down the line.
Example 1
Input:
consumers = ["ana","bo","cy","dan","ed"], shift = 2
Output:
["dan","ed","ana","bo","cy"]
Example 2
Input:
consumers = ["ana","bo","cy"], shift = 0
Output:
["ana","bo","cy"]
Worked solution and explanation
What this really is
This is a slice-and-swap hiding inside a scheduling story. The skill it probes is whether you normalize shift before you index, so the rotation lands no matter how big or how negative the number is. Anyone can type consumers[-shift:] + consumers[:-shift]. The trap is the moment shift is 7 on a 3-name roster, or shift is negative: index with the raw value and Python clamps the slice to the whole list, so rotating a 5-consumer roster by 7 hands back the original order untouched. It does not crash, it just silently misroutes load, which is the worst kind of bug because it looks like it worked.
Walking to the answer
Step 1: Fold the shift into range
Reduce shift to shift % len(consumers). On a roster of 5, a shift of 7 collapses to 2 and a shift of -1 folds to 4. Python's modulo on a negative left operand already returns a non-negative result, so left rotations turn into their equivalent right rotation for free. Do this first and every later line gets to assume shift is in range.
Step 2: Split at the wrap point and recombine
Take the last shift consumers and put them in front of the rest. consumers[-shift:] is the tail that wraps around, and consumers[:-shift] is everything before it. Put the tail first, then the head. Guard the empty roster up front, because the modulo divides by the length and would crash on zero. Skip a separate zero-shift branch. When shift folds to 0, consumers[-0:] is the whole list and consumers[:-0] is empty, so the same line already returns an unchanged copy.
The solution
def rotate_list(consumers, shift):
if not consumers:
return []
shift = shift % len(consumers)
return consumers[-shift:] + consumers[:-shift]- Raw shift, no modulo
- return consumers[-shift:] + consumers[:-shift] With shift=7 on a 5-name roster, consumers[-7:] clamps to the whole list and consumers[:-7] is empty, so you return the original order. No error, wrong answer.
- Normalized shift
- shift = shift % len(consumers) return consumers[-shift:] + consumers[:-shift] 7 folds to 2 and the wrap point is correct. Negative shifts fold to their right-rotation twin the same way.
Common follow-up questions
- How would you rotate without allocating a second list? (Tests the three-reversal trick: reverse the whole roster, then the first shift, then the rest, all in place.)
- What changes if the scheduler rotated left instead of right? (Tests that a left rotation by shift equals a right rotation by len minus shift, and that modulo already encodes it.)
- If you rotate the same roster many times per second, how would you avoid redoing the full slice each call? (Tests reasoning about repeated calls: the offsets are additive, so you can sum the shifts and rotate once.)