# Down the Line

> Load has to keep moving. Pass it down the line.

Canonical URL: <https://datadriven.io/problems/down_the_line>

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.

## 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, `consumers[:-shift]` is everything before it. Concatenate tail then head. Guard the empty roster up front and the zero-remainder case so you return a clean copy instead of leaning on a slice that reads oddly when `shift` is 0.

---

### The solution

**Modulo-normalized slice rotation**

```python
def rotate_list(consumers, shift):
    if not consumers:
        return []
    shift = shift % len(consumers)
    if shift == 0:
        return list(consumers)
    return consumers[-shift:] + consumers[:-shift]
```

> **Time and space**
>
> **Time:** O(n). One modulo, two slices, one concatenation, each linear in the roster size.
> 
> **Space:** O(n) for the new list. The input roster is never mutated, so the caller's order is safe.

**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.

> **Interviewers watch for**
>
> Whether you normalize `shift` before slicing. A candidate who slices with the raw value is one stray oversized input away from a silent failure in production. The modulo line is the tell that you have actually thought about the boundary, not just the happy path.

> **Common pitfall**
>
> Assuming a negative `shift` needs a special branch. It does not: Python's `-1 % 5` is `4`, so the single modulo already turns a left rotation into the matching right rotation. Writing a separate negative-handling path is wasted code that often introduces an off-by-one.

---

## 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.)_

## Related

- [All practice problems](https://datadriven.io/problems)
- [Mock interview mode](https://datadriven.io/interview/down_the_line)
- [Python Interview Questions](https://datadriven.io/python-interview-questions)
- [Data Engineering Interview Prep Guide](https://datadriven.io/data-engineer-interview-prep)
- [Daily Challenge](https://datadriven.io/daily)

---

Source: DataDriven (https://datadriven.io). 100% free data engineering interview prep. Live code execution against Postgres 16, Python 3.11, and Spark sandboxes. No paywall, no premium tier, no signup gate.