Text Justification is a classic string-formatting problem. There is no clever data structure here — the whole challenge is being careful and greedy: fit as many words as the width allows on each line, then distribute the leftover spaces evenly.
Problem. Given an array of words and a column width maxWidth, format the text so each line is
exactly maxWidth characters and fully justified (extra spaces spread between words, left gaps
getting more when it does not divide evenly). The last line is left-justified.
Example: words = ["This", "is", "an", "example", "of", "text"], maxWidth = 16 →
["This is an", "example of text "].
The slow way first
You might try to decide line breaks by looking ahead — counting words to the end, balancing line lengths, minimizing raggedness. That is a much harder optimization problem and it is not what this question asks. The interview version uses a simple greedy rule, and overthinking it is the main way people get stuck.
The question to ask: while scanning words left to right, when does the current line become full? The line is full the moment adding one more word would push it past maxWidth.
The idea: greedy pack, then spread
Walk the words once, collecting them into a current line cur. Track length, the total letter count so far. Adding a word w also needs one space per existing word (that is len(cur) spaces). So if length + len(cur) + len(w) > maxWidth, the line is full: justify it, push it to the result, and start fresh.
To justify a full line: the leftover space is maxWidth - length. Split it across gaps = len(cur) - 1 gaps, handing them out one at a time from the left so earlier gaps get the extra when it does not divide evenly. The last line is special — single spaces, then pad the right.
Walk through it
Step through the animation. Words turn from gray to green as they are committed. We greedily add This, is, an (10 chars), then example would make 18 > 16, so the line is full. We spread 10 spaces over 2 gaps (5 each) and emit This is an. The rest — example, of, text — is the final line, so it is left-justified and right-padded.
Pseudocode
res, cur, length = [], [], 0
for each word w:
if length + (len(cur) spaces) + len(w) > maxWidth:
spaces = maxWidth - length # leftover spaces to distribute
gaps = max(1, len(cur) - 1) # at least 1 to avoid div-by-zero
hand them out one at a time, left to right (i % gaps)
push the joined line to res
reset cur and length
add w to cur; length += len(w)
last line = single-spaced and padded on the right to maxWidth
return res + [last line]The Python solution
def justify(words, maxWidth):
res, cur, length = [], [], 0
for w in words:
if length + len(cur) + len(w) > maxWidth:
spaces = maxWidth - length
gaps = max(1, len(cur) - 1)
for i in range(spaces):
cur[i % gaps] += " "
res.append("".join(cur))
cur, length = [], 0
cur.append(w); length += len(w)
last = " ".join(cur).ljust(maxWidth)
return res + [last]curis the words on the current line;lengthis their combined letter count (no spaces yet).- Line 4 is the overflow test:
len(cur)is the number of single spaces already implied between the words on the line. spaces = maxWidth - lengthis exactly how many space characters must be inserted to fill the line.gaps = max(1, len(cur) - 1)— a one-word line has zero real gaps, so we force1to avoid dividing by zero (the space just goes after the word).i % gapshands spaces out round-robin from the left, so the left gaps get the extra when it does not divide evenly.- After the loop,
curholds the last line: join with single spaces andljustto pad the right.
Complexity
| Case | Time | Notes |
|---|---|---|
| Pack + justify each line | O(n) (moderate) | each word handled once |
| Spreading spaces | O(total chars) (moderate) | bounded by output size |
O(total chars) (moderate)We touch every word a constant number of times and write each output character once, so the work is linear in the size of the output text.
When this pattern shows up
Greedy line-packing — "fit as much as you can, then commit" — shows up in word-wrap, pagination, and layout problems. The reusable move is: scan once, keep a running total, and flush the buffer the moment adding the next item would exceed the budget.
Two off-by-one traps: remember the implied space between words when testing overflow (that is the
len(cur) term), and handle the single-word line (zero gaps) so you never divide by zero. The last
line is always left-justified — do not fully justify it.
Practice
The line holds This, is, an (6 letters, 2 gaps) and maxWidth is 16. How many spaces go in each gap?
1. When is the current line considered full?
2. Why include len(cur) in the overflow test?
3. How are leftover spaces distributed when they do not divide evenly?
4. How is the last line formatted?