Coding Interview Patterns Module 28 – String patterns
Text justification and word wrap
Fit words into lines of an exact width and spread the spaces evenly: the problem, worked examples, a brute force that tries every layout, and what to notice.
What you will learn
- Build lines greedily from words for a given width, and prove that greedy packing uses the fewest lines
- Split the spaces of a line among its gaps from left to right with one division and its remainder
- Handle the last line, one-word lines and words longer than the width as stated rules
- Measure a line in display columns when the text holds wide characters or combining marks
- Compare greedy wrapping with the most even wrapping found by dynamic programming
Before you start
What you’ll learn · Before you start
What you will learn
- Build lines greedily from words for a given width, and prove that greedy packing uses the fewest lines
- Split the spaces of a line among its gaps from left to right with one division and its remainder
- Handle the last line, one-word lines and words longer than the width as stated rules
- Measure a line in display columns when the text holds wide characters or combining marks
- Compare greedy wrapping with the most even wrapping found by dynamic programming
Before you start
On this page
Text justification lays out words in lines of exactly the same width by widening the spaces between them, so that both margins are straight. Word wrap is the simpler half of the job: it breaks text into lines no wider than the width and leaves the right margin ragged. The usual method packs as many words as fit into each line, then shares a line’s spare spaces among its gaps with one division, the leftmost gaps taking any remainder. The rules that trip people up are the edges: the last line, a line with a single word, and a word that is wider than the line.
The problem
The problem
A school’s notice board shows announcements in a monospace font, width characters to a line. Write
justify(words, width), which lays the words out in the fewest lines possible and returns them, each exactly
width characters long. In every line but the last, the gaps between words are widened as evenly as possible, any
extra spaces going to the leftmost gaps. A line with one word, and the last line, keep single spaces and are padded
with spaces on the right. Every word is at most width characters long. Limits: 1 ≤ width ≤ 100, and up to 10⁴
words.
Two examples, with each line between bars:
"Exam results will be posted on the notice board on Friday."at width 16 gives four lines:|Exam results|,|will be posted|,|on the notice|and|board on Friday.|."Library closed for stock taking"at width 12 gives|Library |, a line with one word padded on the right, then|closed for|and|stock taking|.
Word wrap of the first notice gives the same four lines without the extra spaces: Exam results, will be posted,
on the notice and board on Friday..
One line of the notice, its letters and its gaps
Text description of the diagram
The diagram has two rows, one under the other.
- The line is drawn as five cells from left to right: the word on, 2 letters; a gap of 3 spaces; the word the, 3 letters; a gap of 2 spaces; and the word notice, 6 letters. The words have double borders and the gaps dashed borders.
- Three lines give the arithmetic. The letters add up to 2 + 3 + 6 = 11. The line must be 16 characters wide, so 16 − 11 = 5 spaces go into its 2 gaps. Dividing 5 by 2 gives 2 with a remainder of 1, so every gap gets 2 spaces and the first gap gets 1 more: 3 and 2.
The brute force: try every layout
The fewest lines can be found by trying every layout: a line may end after any of the n − 1 gaps between words, so there are 2ⁿ⁻¹ layouts, and the shortest one whose lines all fit wins. The run counts them:
# The fewest lines by brute force: try every way to break the words into lines, and keep the shortest valid layout.
def every_layout(words, width):
"""(fewest lines of a valid layout, layouts tried): a layout breaks after any subset of the n - 1 gaps."""
n = len(words)
best, tried = None, 0
for breaks in range(2 ** (n - 1)):
tried += 1
lines, current = 0, [words[0]]
ok = True
for k in range(1, n):
if breaks >> (k - 1) & 1: # a line ends before word k
ok = ok and len(" ".join(current)) <= width
lines, current = lines + 1, [words[k]]
else:
current.append(words[k])
ok = ok and len(" ".join(current)) <= width
if ok and (best is None or lines + 1 < best):
best = lines + 1
return best, tried
notice = "Exam results will be posted on the notice board on Friday.".split()
fewest, tried = every_layout(notice, 16)
print(f"{len(notice)} words, width 16: fewest lines {fewest}, {tried:,} layouts tried")
longer = (notice + "Parents may collect the printed sheets at the office".split())
for n in (12, 14, 16):
fewest, tried = every_layout(longer[:n], 16)
print(f"{n} words: fewest lines {fewest}, {tried:,} layouts tried") Output
11 words, width 16: fewest lines 4, 1,024 layouts tried 12 words: fewest lines 5, 2,048 layouts tried 14 words: fewest lines 6, 8,192 layouts tried 16 words: fewest lines 7, 32,768 layouts tried
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 every_layout.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
Every extra word doubles the count. The 11-word notice needs 1,024 layouts, 16 words need 32,768, and 10⁴ words would need 2⁹⁹⁹⁹, a number with 3,010 digits.
What to notice
A line that can take one more word gains nothing by refusing it: pushing a word to the next line can only push the following lines later. So packing each line as full as it can be gives the fewest lines without trying any other layout. After that, a line’s spaces are simply the width minus its letters, and one division with remainder shares them among its gaps. Text justification is that packing plus the division, and word wrap is the packing alone.
Greedy packing, and why it uses the fewest lines Pro: opens with a Pro, Premium or Ultimate pass
In this part:
- The proof that no layout uses fewer lines
- Spreading the spaces with one division
- The last line, one-word lines and words that are too long
- The code in five languages
- Word wrap, next to Python’s textwrap
- Width is not len() on a real notice board
- The most even layout, by dynamic programming
- Five bugs reproduced against the correct version
- Exercise: justify a notice
- Justify a notice to an exact width
- A practice set with sample answers
- Check yourself
- Interview questions
- A justified line holds four words with 13 letters in all and must be 24 characters wide. How many spaces go in each gap, which gaps get the extra ones, and how do you compute it without a loop? Which lines must not be justified, and why?
- Prove that putting as many words as fit on each line gives the fewest lines possible. Then explain why the same rule can leave a very uneven right margin, and what you would use when an even margin matters.
- A justifier that works on English notices misaligns lines on a monospace screen for notices in Japanese and for names with accents. Explain why with an example, and change the width measurement so that every line is exactly as wide as the board.
- Key takeaways
7 runnable examples (C++, Go, Java, JavaScript, Python) · 1 exercise with sample tests · 3 interview answers · about 19 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
References
- textwrap: text wrapping and filling (The Python Standard Library) (Python Software Foundation)
- divmod() (The Python Standard Library: built-in functions) (Python Software Foundation)
- unicodedata: east_asian_width() and combining() (The Python Standard Library) (Python Software Foundation)
- Unicode Standard Annex #11: East Asian Width (Unicode Consortium)
- Breaking paragraphs into lines (D. E. Knuth and M. F. Plass, Software: Practice and Experience 11(11), 1981) (Wiley)
- String.prototype.padEnd() (JavaScript reference) (MDN Web Docs)
- StringBuilder.repeat(int, int) (Java SE 25 API) (Oracle)
- Package strings: Fields and Repeat (The Go Authors)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress