Convert a List to a String in Python (join, map and More)

Joining is how you turn a Python list to a string: ", ".join(my_list). The text before .join is the separator, and the list goes inside the parentheses:

groceries = ["apples", "bread", "milk"]
print(", ".join(groceries))    # apples, bread, milk

That covers a list of strings. Numbers, None values and speed each need a little more, and I’ll show each one with real output from Python 3.14.7.

Diagram showing Python join placing the separator between list items but not after the last one
join() puts the separator between items, never after the last one.

Convert a list to a string in Python with join()

Here’s the same list joined three ways, plus what str() does to it:

groceries = ["apples", "bread", "milk"]

print(repr("".join(groceries)))
print(repr(" ".join(groceries)))
print(repr(", ".join(groceries)))
print()
print("str(groceries) gives:", str(groceries))

Output:

'applesbreadmilk'
'apples bread milk'
'apples, bread, milk'

str(groceries) gives: ['apples', 'bread', 'milk']
Command Prompt showing a Python list of strings converted to a string with join using no separator a space and a comma
The separator is whatever string you call join on, including an empty one.

"".join() glues the items together with nothing between them, " ".join() puts a space between them, and ", ".join() gives a readable list.

The last line shows why str() is rarely the answer. It gives you the list exactly as Python prints it, brackets and quotes included.

Can you use str() on a list?

You can, but it answers a different question. str(groceries) returns "['apples', 'bread', 'milk']", a text copy of the list’s code-like form.

That’s useful for logging or debugging, where you want to see the structure. For anything a person will read, like a message or a CSV cell, use join().

If you only need to print a list without the brackets, printing a list without brackets covers the other options.

Join a list of numbers into a string

join() only accepts strings, so a list of numbers fails straight away:

order_ids = [1042, 1043, 1044]

try:
    ", ".join(order_ids)
except TypeError as err:
    print("TypeError:", err)

print(", ".join(map(str, order_ids)))
print(", ".join(str(n) for n in order_ids))
print()

prices = [19.99, 5, 42.5]
print(", ".join(f"${p:.2f}" for p in prices))

Output:

TypeError: sequence item 0: expected str instance, int found
1042, 1043, 1044
1042, 1043, 1044

$19.99, $5.00, $42.50
Command Prompt showing Python join raising TypeError for a list of integers and map str fixing it
The error names the first item that isn’t a string; map(str, ...) converts them all.

The message tells you exactly what went wrong: sequence item 0: expected str instance, int found. The index points at the first item that isn’t a string.

Join a list of digits into one number

A related trick: turning a list of digits into a single number. Join them as text, then convert:

digits = [2, 0, 2, 6]

year_text = "".join(map(str, digits))
print(repr(year_text), "->", int(year_text) + 1)

Output:

'2026' -> 2027

"".join(map(str, digits)) builds the text "2026", and int() turns it into a number you can do math with.

map(str, order_ids) converts every item on the way in, and a generator expression does the same. I use the f-string version when the numbers need formatting, like the dollar amounts in the last line.

Join with a separator: comma, space, newline or none

The separator can be any string at all, which covers most of the formats people ask for:

states = ["Texas", "Ohio", "Utah"]

print(repr(", ".join(states)))      # comma and space
print(repr(",".join(states)))       # comma only, CSV-style
print(repr(" | ".join(states)))     # any text works
print(repr("".join(states)))        # no separator at all
print("\n".join(states))            # one per line

Output:

'Texas, Ohio, Utah'
'Texas,Ohio,Utah'
'Texas | Ohio | Utah'
'TexasOhioUtah'
Texas
Ohio
Utah

A comma with no space gives CSV-style output, and an empty string removes the separator completely. "\n".join() puts each item on its own line.

There’s more on newlines in the Python newline guide.

Join lines and write them to a file

"\n".join() is the quick way to write a list of lines to a file. Just remember the last line:

lines = ["order 1", "order 2", "order 3"]

with open("orders.txt", "w") as f:
    f.write("\n".join(lines) + "\n")    # join adds no newline after the last line

print(repr(open("orders.txt").read()))

Output:

'order 1\norder 2\norder 3\n'

join() only puts the newline between items, so I add one more at the end. Many tools expect a text file to finish with a newline, and appending to it later then starts on a fresh line.

Put quotes around each item

Sometimes each item needs quotes, for a message or a config value. Wrap each one on the way into join():

states = ["Texas", "Ohio", "Utah"]

print(", ".join(f'"{s}"' for s in states))
print(", ".join(repr(s) for s in states))

Output:

"Texas", "Ohio", "Utah"
'Texas', 'Ohio', 'Utah'

The f-string version always uses double quotes, while repr() uses Python’s own quoting. If you’re tempted to build a SQL IN (...) list this way, don’t; pass the values as query parameters instead, so quotes inside the data can’t break the query.

Split the string back into a list

split() is the reverse of join(), as long as you use the same separator:

states = ["Texas", "Ohio", "Utah"]

text = ",".join(states)
back = text.split(",")

print(repr(text))
print(back)
print("same list again?", back == states)

Output:

'Texas,Ohio,Utah'
['Texas', 'Ohio', 'Utah']
same list again? True

The round trip only works if no item contains the separator itself. For the details of the reverse direction, see splitting a string by comma.

Join a list of characters into a word

A list of single characters joins into a word with an empty separator:

letters = ["P", "y", "t", "h", "o", "n"]

word = "".join(letters)
print(repr(word))
print(list(word) == letters, "- list() splits it back into characters")

Output:

'Python'
True - list() splits it back into characters

list(word) goes the other way, splitting a string into its characters. That pair is common in puzzles and in code that edits one character at a time.

Join only the first few items

For previews, show a few items and summarize the rest. Slicing does the work before join():

states = ["Texas", "Ohio", "Utah", "Iowa", "Maine", "Idaho"]

shown = 3
preview = ", ".join(states[:shown])
extra = len(states) - shown
print(preview + (f" and {extra} more" if extra > 0 else ""))

Output:

Texas, Ohio, Utah and 3 more

states[:shown] takes the first three, and the count of the rest turns into “and 3 more”. It keeps a long list from flooding a message or a table cell.

Why is there no list.join() in Python?

If you’re coming from JavaScript, where it’s array.join(", "), this trips you up at first:

fruits = ["apple", "pear"]

try:
    fruits.join(", ")               # the JavaScript habit
except AttributeError as err:
    print("AttributeError:", err)

print(", ".join(fruits))            # separator first, list inside

Output:

AttributeError: 'list' object has no attribute 'join'
apple, pear

In Python, join belongs to the separator string, not the list. It reads backwards at first, but it lets join accept any iterable, not just lists, as the next sections show.

Java’s String.join(", ", list) is actually the closest match: separator first, then the items.

Join a list with None or mixed types

Real data often mixes strings with numbers and missing values. Here’s what happens:

values = ["Austin", None, "Denver", 2026]

try:
    ", ".join(values)
except TypeError as err:
    print("TypeError:", err)

print("map(str):   ", ", ".join(map(str, values)))
print("skip None:  ", ", ".join(str(v) for v in values if v is not None))

Output:

TypeError: sequence item 1: expected str instance, NoneType found
map(str):    Austin, None, Denver, 2026
skip None:   Austin, Denver, 2026
Command Prompt showing Python join with None and an integer in the list and two ways to handle them
map(str, ...) turns None into the word None; filtering removes it.

map(str, values) fixes the error, but look at the result: None became the text None. That’s easy to miss and embarrassing in a report.

Decide what a missing value should become. Filtering it out with if v is not None is usually right, or swap in a placeholder like "N/A".

Join a pandas column into one string

With a pandas column, drop the missing values first or let pandas handle them:

import pandas as pd

orders = pd.DataFrame({"state": ["TX", "OH", None, "UT"]})

print(repr(", ".join(orders["state"].dropna())))
print(repr(orders["state"].str.cat(sep=", ")))
print(repr(orders["state"].str.cat(sep=", ", na_rep="N/A")))

Output:

'TX, OH, UT'
'TX, OH, UT'
'TX, OH, N/A, UT'

.dropna() removes the gap before joining. .str.cat(sep=", ") skips missing values on its own, and na_rep fills them with a placeholder instead.

join() works on tuples, sets, dictionaries and strings

Anything you can loop over works with join(), as long as the items are strings:

print(repr("-".join(("2026", "09", "30"))))            # tuple
print(repr(", ".join(sorted({"blue", "red"}))))        # set: sort it for a stable order
print(repr(", ".join({"x": 1, "y": 2})))               # dict: joins the KEYS
print(repr(", ".join(f"{k}={v}" for k, v in {"x": 1, "y": 2}.items())))
print(repr("-".join("abc")))                           # a string is an iterable of characters
print(repr(", ".join([])))                             # empty list -> empty string

Output:

'2026-09-30'
'blue, red'
'x, y'
'x=1, y=2'
'a-b-c'
''
Command Prompt showing Python join on a tuple a set a dictionary and a string
Tuples, sorted sets, dictionary keys, generators and even strings all join.

A set has no fixed order, so I sort it first to get the same string every run. A dictionary joins its keys; use .items() when you need the values too.

The surprising one is a plain string. "-".join("abc") treats it as a sequence of characters and gives a-b-c, which is handy for formatting codes.

Join a list into a sentence with commas and “and”

For text people will read, a bare comma list isn’t quite right. American English style puts “and” before the last item, usually with a serial comma:

def human_list(items):
    if not items:
        return ""
    if len(items) == 1:
        return items[0]
    if len(items) == 2:
        return f"{items[0]} and {items[1]}"
    return ", ".join(items[:-1]) + ", and " + items[-1]

for colors in (["red"], ["red", "white"], ["red", "white", "blue"]):
    print(human_list(colors))

Output:

red
red and white
red, white, and blue

The function handles one and two items separately, since “red and white” shouldn’t get a comma. For three or more, it joins all but the last with commas and adds “, and”.

join() vs + vs sum(): which is fastest?

You can build a string with += in a loop, but it’s the slow way:

import timeit

setup = "parts = [str(i) for i in range(100000)]"

join_time = min(timeit.repeat("''.join(parts)", setup=setup, number=50, repeat=3))
loop_time = min(timeit.repeat("s = ''\nfor p in parts: s += p", setup=setup, number=50, repeat=3))

print(f"''.join(parts):   {join_time:.3f} s")
print(f"+= in a loop:     {loop_time:.3f} s")
print(f"the loop was {loop_time / join_time:.0f}x slower here")
print()

try:
    sum(["a", "b", "c"], "")
except TypeError as err:
    print("sum() ->", err)

Output:

''.join(parts):   0.033 s
+= in a loop:     0.621 s
the loop was 19x slower here

sum() -> sum() can't sum strings [use ''.join(seq) instead]
Command Prompt comparing Python join with a plus equals loop and showing sum refusing to add strings
join() beat the += loop by a wide margin, and sum() points you to join().

join() works out the final size once and copies each piece a single time. The loop builds a new string on every step, so it falls further behind as the list grows.

sum() won’t even try: its error message tells you to use ''.join(seq) instead. When Python’s own error suggests a function, that’s a strong hint.

Convert a list of lists to a string

Nested lists need two joins, one inside the other:

cities = [["New", "York"], ["San", "Diego"], ["Des", "Moines"]]

print(", ".join(" ".join(parts) for parts in cities))
print()

rows = [["name", "state"], ["Ava", "TX"], ["Liam", "OH"]]
print("\n".join(",".join(row) for row in rows))

Output:

New York, San Diego, Des Moines

name,state
Ava,TX
Liam,OH

The inner " ".join(parts) builds each city name, and the outer join puts commas between them. The second example builds a small CSV the same way, one row per line.

Be careful not to confuse this with combining lists themselves. Concatenating multiple lists is about merging lists into one list, not into a string.

If you work with lists and strings a lot, these are worth a look next:

Frequently asked questions

How do I convert a list to a string in Python?

Use join(): ", ".join(my_list). The string before .join is placed between the items.

How do I join a list of numbers?

Convert the items first: ", ".join(map(str, numbers)). Joining numbers directly raises TypeError.

Why does str(list) keep the brackets?

str() returns the list’s printed form, brackets and quotes included. Use join() for readable text.

How do I join a list without commas or spaces?

Join with an empty string: "".join(my_list).

Why is there no list.join() method?

join is a string method, so you call it on the separator: ", ".join(my_list). That lets it accept any iterable.

Is join() faster than using + in a loop?

Yes. In my test a += loop over 100,000 strings was many times slower than a single join().

What does join() do with None values?

It raises TypeError. map(str, ...) turns None into the text None, so filter missing values out first. The details are in the Python str.join() documentation.