PythonMastery

Build strings with join, not += in a loop

Adding to a string in a loop can copy it again and again. Collect the pieces and call join once. Measure the difference yourself, in the browser.

Strings in Python can't be changed. text += piece makes a new string holding the old text plus the piece, so a loop that grows a string can end up copying the same characters over and over. "".join(pieces) works out the final length once and copies each piece exactly once.

Measure it

Run this. The numbers are yours, from your own machine.

python
import timeit

words = ["reading"] * 20_000

def with_plus():
    text = ""
    for w in words:
        text += w + ","
    return text

def with_join():
    return ",".join(words) + ","

t_plus = timeit.timeit(with_plus, number=20)
t_join = timeit.timeit(with_join, number=20)
print(f"+= : {t_plus * 1000:.1f} ms   join: {t_join * 1000:.1f} ms")
print(f"join was about {t_plus / t_join:.0f}x faster")
example output · yours will differ
+= : 44.5 ms   join: 4.5 ms
join was about 10x faster

About 10× on my machine, for 20,000 short words. The gap is not fixed: it depends on how long the pieces are and on your Python.

The pattern in real code

Most of the time the string is built from a loop over records. Collect the parts in a list, then join once at the end.

python
orders = [("A-1001", 49.99), ("A-1002", 12.50), ("A-1003", 5.00)]

lines = []
for order_id, amount in orders:
    lines.append(f"{order_id}: £{amount:.2f}")

report = "\n".join(lines)
print(report)
output
A-1001: £49.99
A-1002: £12.50
A-1003: £5.00

Or, when each part comes straight out of the loop, a generator expression inside join does the same job in one line: "\n".join(f"{i}: {a:.2f}" for i, a in orders).

Why it works

join makes two passes: one to add up the lengths, one to copy. += has no idea more pieces are coming, so every step has to produce a complete, finished string. CPython sometimes cheats and grows the string in place when nothing else refers to it, which is why the gap here is 10× and not far worse. That shortcut isn't guaranteed: it depends on the interpreter and on whether another name still points at the old string, so don't write code that relies on it.

When not to use it

For two or three pieces, a + b or an f-string is clearer and just as fast; nobody needs "".join([first, " ", last]). The rule is about loops, where the number of pieces grows with the data.

Learn it properly: Working with Strings, Performance: Profile, Then Optimise