How to join a list into a string in Python
Call join on the separator, not on the list: ", ".join(items). If the list holds anything other than strings, convert first — ", ".join(map(str, nums)).
str.join() is the one right way to turn a list into a string: it walks the iterable once, measures the total length, and allocates the result once. The two things that trip people up are the "backwards" syntax — the separator owns the method — and the TypeError you get the moment a non-string sneaks into the list. Both are covered below, and every example runs on this page.
1The separator joins the listRecommended
join is a method on str, so you call it on the glue and pass the list: separator.join(iterable). It reads backwards at first, but it's what lets any string join any iterable — a list, a tuple, a generator, even another string.
Output
Prints red, green, blue, then red | green | blue, then redgreenblue. An empty separator concatenates with nothing between — that's the standard way to rebuild a string from a list of characters.
2Joining numbers and other non-strings
join refuses to guess how to stringify your objects, so a list of ints raises TypeError: sequence item 0: expected str instance, int found. The index in that message is the first offending item, which is handy when only one element is the odd one out. Fix it with map(str, ...) or a generator expression:
Output
The first line prints TypeError: sequence item 0: expected str instance, int found; both fixes print 1, 2, 3. map(str, ...) is marginally faster and shorter; the generator wins as soon as you need real formatting per item, e.g. f"{n:.2f}".
3Joining with a newline
"\n".join(lines) is the idiomatic way to build multi-line output — one print(), one write, no trailing blank line. Nest a second join inside a generator and you have a CSV-ish table in two lines:
Output
The inner join builds each row, the outer one stitches the rows together — ada | 1945 then linus | 1969. For real CSV with quoting and escaping, use the csv module instead of hand-rolling it.
4Building the string in a loop
You can append with += and track the separator yourself. It works, and the result is identical — but strings are immutable, so in principle every += copies the whole accumulated string, making the loop quadratic in the number of items. CPython has an in-place optimisation that often hides this, but it only applies when the string has a single reference and it is not part of the language spec, so it can vanish without warning.
Output
Prints build, run, share and then True — same output, five extra lines, and an if i: guard purely to avoid a leading separator. join handles the "n − 1 separators" problem for free. If you really must accumulate incrementally, append to a list and join it at the end.
5Which should you use?
| Method | Handles non-strings | Speed | Best for |
|---|---|---|---|
| ", ".join(items) | No | Fastest | Lists that are already strings |
| ", ".join(map(str, items)) | Yes | Fast | Numbers and mixed types |
| ", ".join(str(x) for x in items) | Yes | Slightly slower | Formatting each item |
| result += item (loop) | Needs str() | Quadratic | Nothing — prefer join() |
6Common variation: an Oxford-comma join
User-facing text usually wants a, b, and c rather than a, b, c. Join everything except the last item, then attach the last one with and — with a special case for one or two items:
Output
Prints Python, Python and Go, Python, Go, and Rust, and Python, Go, Rust, and C++. The str(i) pass up front means it never raises the TypeError from section 2. Drop the comma before and if your style guide says so.
Frequently asked questions
Why is the separator first in Python’s join?
join is a method on str, not on list, so it is called as separator.join(iterable). That way one method on the string type can join any iterable — a list, tuple, set, or generator — instead of every sequence type needing its own join.
How do I join a list of numbers into a string?
Convert them first: ", ".join(map(str, nums)) or ", ".join(str(n) for n in nums). Calling ", ".join([1, 2, 3]) directly raises TypeError: sequence item 0: expected str instance, int found — the number in the message is the index of the first non-string item.
Is join() faster than adding strings in a loop?
Yes. join walks the iterable once, sums the lengths, and allocates the result string a single time, so it is O(n). Repeated += conceptually copies the whole accumulated string each iteration, which is quadratic; CPython has an in-place optimisation that often hides this, but it is not guaranteed by the language.
What does join() do with a string, a dict, or an empty list?
A string is an iterable of characters, so "-".join("abc") gives a-b-c. A dict iterates over its keys, so ", ".join({"host": 1, "port": 2}) gives host, port. An empty list returns the empty string "", and a single-item list returns that item with no separator at all.