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

## 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

**Modulo-normalized slice rotation**

```python
def rotate_list(consumers, shift):
    if not consumers:
        return []
    shift = shift % len(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). DataDriven is the data engineering interview community. Live code execution in SQL, Python, and Spark sandboxes. Every feature is open to every member.