
In Python, joining a list means combining its elements into a single string, using a separator to place between each element. The standard tool for this is the string join() method, which takes an iterable (such as a list) and returns one string. This operation is useful whenever you need to turn a list of values into text, for example saving a list of names as a comma-separated line in a file or building a sentence from a list of words.
In this tutorial, you will learn the different ways to join a Python list and combine lists in Python. You will start with the join() method for joining a list of strings, then cover how to join a list of integers and mixed data types, how to handle None values and nested lists, how to concatenate two or more lists with the + operator, list.extend(), the * unpacking operator, and itertools.chain(), and how the methods compare in performance. You will also see why join() lives on the string class rather than the list class, and which modern alternatives Python now offers.
Key Takeaways:
join() method is the standard and fastest way to join a list of strings into one string with a chosen separator.separator.join(iterable): the separator is the string you call join() on, and the list is passed as the argument.join() works only on strings, so joining a list that contains integers, None, or other types raises a TypeError.map(str, list) or a list comprehension.None values before joining, use a generator expression such as ",".join(x for x in items if x is not None).+ operator or list.extend() to concatenate two lists into a new list, and the * unpacking operator (Python 3.5+) for a concise merge.itertools.chain() avoids building intermediate lists and uses less memory.+ in a loop runs in O(n^2) time, while join() runs in O(n); in a benchmark on 100,000 items, join() was about 8 times faster.join() is a string method (not a list method) because it works with any iterable and always returns a string.join()We can use the Python string join() method to join a list of strings. The method is called on the separator string and takes the iterable as its argument, so the general form is separator.join(iterable). Because a list is an iterable, we can pass a list directly. The list should contain only strings; if you try to join a list of integers you will get a TypeError, which the note below explains how to fix.
Let’s look at a short example that joins a list of vowels into a comma-separated string:
vowels = ["a", "e", "i", "o", "u"]
vowels_csv = ",".join(vowels)
print("Vowels are =", vowels_csv)
Vowels are = a,e,i,o,u
The separator can be any string, not just a comma. The next section shows how to join with a space, a newline, or any custom separator.
Because the separator is just the string you call join() on, you can change the output format simply by changing that string. A space joins words into a sentence, "\n" puts each item on its own line, and any other string works as a custom separator.
The following example joins the same list three different ways:
words = ["Python", "is", "awesome"]
print(" ".join(words)) # space separator
print("\n".join(words)) # newline separator
print(" -> ".join(words)) # custom separator
Python is awesome
Python
is
awesome
Python -> is -> awesome
The newline join is especially handy when writing a list to a text file, where each element should appear on its own line.
Joining an empty list is safe and simply returns an empty string, so you do not need a special check for it.
print(repr("".join([])))
print(repr(",".join([])))
''
''
This predictable behavior is convenient when the list might be empty at runtime, since the call will not raise an error.
A list of integers cannot be passed to join() directly, because join() only accepts strings. Attempting it raises TypeError: sequence item 0: expected str instance, int found. The fix is to convert each element to a string first, and there are two common ways to do that.
map() with join()The map() function applies str to every element of the list, producing string versions that join() can accept. This is concise and reads well for simple conversions, and you can learn more about it in the tutorial on the Python map() function.
int_list = [1, 2, 3, 4, 5]
joined_str = ",".join(map(str, int_list))
print("Joined string:", joined_str)
Joined string: 1,2,3,4,5
A list comprehension does the same conversion and is a good choice when you want to transform each item further, for example formatting numbers while converting them.
int_list = [1, 2, 3, 4, 5]
joined_str = ",".join([str(element) for element in int_list])
print("Joined string:", joined_str)
Joined string: 1,2,3,4,5
Both approaches are correct; map(str, ...) is slightly faster and more compact, while the comprehension is easier to extend when each element needs extra formatting.
When a list mixes strings with other types, join() raises a TypeError because it expects every element to already be a string. The list ['Java', 'Python', 1] is a typical example: the integer 1 causes the error.
names = ['Java', 'Python', 1]
single_str = ','.join(names)
print(single_str)
This raises the following error:
TypeError: sequence item 2: expected str instance, int found
The lesson is that a list containing multiple data types cannot be combined into a single string with join() directly; you must convert the non-string items first, as the next two sections show.
None values in a list before joiningA None value triggers the same TypeError, so you need to decide whether to drop the None values or convert them to text. To drop them, filter the list with a generator expression before joining.
items = ["a", None, "b"]
result = ",".join(x for x in items if x is not None)
print(result)
a,b
If you would rather keep a placeholder for the missing value, convert every element with str() instead, which turns None into the text "None".
items = ["a", None, "b"]
result = ",".join(str(x) for x in items)
print(result)
a,None,b
Choose the filtering approach when missing values should disappear, and the conversion approach when their position matters.
The same generator-expression pattern lets you keep only the items you want, for example joining just the string elements of a mixed list and ignoring everything else.
mixed = ['Java', 'Python', 1, None, 'Go']
result = ",".join(x for x in mixed if isinstance(x, str))
print(result)
Java,Python,Go
Using isinstance(x, str) keeps the strings and silently skips the integer and the None, which is useful when a list may contain unpredictable types.
A nested list (a list of lists) cannot be joined directly either, because its elements are lists rather than strings. You first need to flatten it into a single sequence of strings, and there are two clean ways to do that.
A nested list comprehension walks each sublist and then each item, producing a flat list of strings that join() can accept.
nested = [["a", "b"], ["c", "d"]]
flat = [item for sub in nested for item in sub]
print(",".join(flat))
a,b,c,d
itertools.chain.from_iterable()For larger nested lists, itertools.chain.from_iterable() flattens the structure lazily without building an intermediate list, which is more memory efficient.
import itertools
nested = [["a", "b"], ["c", "d"]]
print(",".join(itertools.chain.from_iterable(nested)))
a,b,c,d
Both produce the same result; reach for chain.from_iterable() when the nested data is large or streamed, and the comprehension when readability matters most.
join()A common point of confusion is calling join() on a single string instead of a list. Because a string is itself an iterable of characters, join() iterates over its individual characters and inserts the separator between each one.
message = "Hello ".join("World")
print(message)
WHello oHello rHello lHello d
As the output shows, this does not produce "Hello World". Because 'World' is a string and strings are iterables of characters, join() inserts 'Hello ' between every individual character of 'World', not between two whole words. To combine two whole strings, use concatenation with + or an f-string instead of join().
join() is a string method and not a list methodA question many Python developers ask is why join() belongs to the string class and not to the list class. Wouldn’t the syntax below be easier to remember?
vowels_csv = vowels.join(",")
There is a well-known StackOverflow discussion about this. The core reasoning is that join() works with any iterable, not just lists, and it always returns a string. If join() were a method on every iterable, the same logic would have to be duplicated across lists, tuples, sets, generators, and more. Placing it on the string class instead means a single implementation handles every iterable, and the separator is naturally the string you call the method on.
The official Python documentation describes str.join() as follows: “Return a string which is the concatenation of the strings in iterable.” It also notes that “a TypeError will be raised if there are any non-string values in iterable, including bytes objects.” This is exactly the behavior you saw in the integer and mixed-type examples above, and it confirms why the method is defined where it is.
Joining for string output is different from concatenating lists into a new list. When your goal is a combined list rather than a string, Python gives you several options, each with different performance characteristics. You can read more in the tutorial on concatenating lists in Python.
+ operatorThe + operator creates a new list by joining two existing lists, which is the simplest approach for a simple merge of small lists.
list1 = [1, 2, 3]
list2 = [4, 5, 6]
combined = list1 + list2
print(combined)
[1, 2, 3, 4, 5, 6]
list.extend()The list.extend() method adds every element of one list onto another in place, without creating a new list. It is the right choice when you are growing an existing list, and it is covered alongside other list-growing methods in the tutorial on adding elements to a list in Python.
combined = [1, 2, 3]
combined.extend([4, 5, 6])
print(combined)
[1, 2, 3, 4, 5, 6]
* unpacking operator (Python 3.5+)Since Python 3.5, the * unpacking operator can be used inside list literals to spread several lists into a new list. It is concise and works with more than two lists at once.
list1 = [1, 2, 3]
list2 = [4, 5, 6]
combined = [*list1, *list2]
print(combined)
[1, 2, 3, 4, 5, 6]
itertools.chain() for memory-efficient joinsFor merging many lists, or very large ones, itertools.chain() returns an iterator that yields elements from each list in turn without building intermediate lists, which keeps memory usage low.
import itertools
list1 = [1, 2, 3]
list2 = [4, 5, 6]
combined = list(itertools.chain(list1, list2))
print(combined)
[1, 2, 3, 4, 5, 6]
If you need a combined list with duplicate values removed while preserving order, concatenate the lists and pass the result to dict.fromkeys(), which keeps only the first occurrence of each value.
list1 = [1, 2, 3]
list2 = [3, 4, 5]
combined = list(dict.fromkeys(list1 + list2))
print(combined)
[1, 2, 3, 4, 5]
Using dict.fromkeys() preserves insertion order (guaranteed since Python 3.7), which a plain set() would not.
split()The split() method is the inverse of joining: it breaks a string into a list using a delimiter. This is useful when you have joined a list into a string and later need the original list back.
names = ['Java', 'Python', 'Go']
delimiter = ','
single_str = delimiter.join(names)
print('String:', single_str)
split = single_str.split(delimiter)
print('List:', split)
String: Java,Python,Go
List: ['Java', 'Python', 'Go']
Using the same delimiter to split the string returns the original list, so join() and split() round-trip cleanly.
n timesThe split() method takes an optional second argument that limits how many splits are performed, which is handy when only the first few fields matter.
names = ['Java', 'Python', 'Go']
delimiter = ','
single_str = delimiter.join(names)
split = single_str.split(delimiter, 1)
print('List:', split)
List: ['Java', 'Python,Go']
Because the split count was set to 1, the operation ran only once and left the remaining delimiter in place.
When combining strings or lists, the method you choose has real performance implications, especially as the data grows. The key idea is time complexity: join() and extend() run in linear O(n) time, while repeatedly using + in a loop runs in quadratic O(n^2) time because each + creates a brand-new object and copies all the existing elements again.
To make this concrete, the following benchmark builds a string of 100,000 items two ways, using join() and using += in a loop, and times each with the timeit module.
import timeit
words = [str(i) for i in range(100_000)]
def with_join():
return ",".join(words)
def with_plus_loop():
s = ""
for w in words:
s += w + ","
return s
print(f"join : {timeit.timeit(with_join, number=20) / 20 * 1000:.2f} ms")
print(f"+= : {timeit.timeit(with_plus_loop, number=20) / 20 * 1000:.2f} ms")
On one test machine running Python 3.14, this produced the following output (your exact numbers will vary by hardware):
join : 1.48 ms
+= : 12.61 ms
In this run, join() was about 8 times faster than building the string with +=. The gap widens as the list grows, which is the practical consequence of the O(n) versus O(n^2) difference. The same pattern holds for lists: concatenating 1,000 lists of 1,000 integers each with repeated out = out + lst took roughly 540 ms in our testing, while list.extend() took about 1 ms and itertools.chain() about 3 ms, since the repeated + rebuilds and copies the growing list on every step.
The table below summarizes the complexity and memory behavior of each method so you can choose based on the size and shape of your data.
| Method | Time Complexity | Creates Intermediate Copies? | Best For |
|---|---|---|---|
str.join() |
O(n) | No (single pass) | Joining a list of strings into one string |
+ in a loop |
O(n²) | Yes (new object each step) | Avoid for large data; fine for a single small merge |
list.extend() |
O(n) | No (in place) | Growing an existing list |
itertools.chain() |
O(n) | No (lazy iterator) | Merging many or very large lists |
Choose the method that fits your use case: join() for strings, extend() or chain() for lists, and reserve a single + for small, one-off merges.
Beyond raw performance, the methods differ in what they are for, how readable they are, and which Python versions support them. The table below compares the main options at a glance.
| Method | Use case | Output | Readability | Python version |
|---|---|---|---|---|
str.join() |
Join a list of strings into one string | str |
High | All 3.x |
+ operator |
Concatenate two lists or two strings | new list / str |
High | All 3.x |
list.extend() |
Add items to an existing list in place | modifies list |
High | All 3.x |
* unpacking |
Merge several lists into a new list | new list |
Medium | 3.5+ |
itertools.chain() |
Iterate over many lists without copying | iterator | Medium | All 3.x |
dict | operator |
Merge two dictionaries | new dict |
High | 3.9+ |
| operatorRecent Python versions added cleaner syntax for merging collections. The * unpacking operator, shown earlier, is the idiomatic modern way to merge lists into a new list. For dictionaries, PEP 584 introduced the | merge operator in Python 3.9, giving dictionaries the same kind of concise merge syntax that + gives lists.
defaults = {"theme": "light", "lang": "en"}
overrides = {"lang": "fr"}
settings = defaults | overrides
print(settings)
{'theme': 'light', 'lang': 'fr'}
When the same key appears in both dictionaries, the value from the right-hand dictionary wins, which is why lang becomes "fr". There is also an in-place version, |=, which updates the left dictionary instead of creating a new one.
These methods map naturally onto everyday tasks, so it helps to see when each one shines.
The first common case is using join() for strings: when you have a list of strings and need a single delimited string, join() is the preferred method. This is useful for building a sentence from a list of words or combining names with commas, as shown throughout this tutorial.
The second common case is combining lists with itertools.chain(): for merging multiple lists, especially large ones, itertools.chain() offers a memory-efficient solution because it avoids creating intermediate lists. This matters when working with large datasets or when you need to process elements from several lists in a single iteration. You can read more in the tutorial on concatenating lists in Python.
Lists, dictionaries, and sets behave differently, so combining them requires a little care. Lists are ordered sequences, dictionaries are collections of key-value pairs (ordered by insertion since Python 3.7), and sets are unordered collections of unique elements. The examples below show how to merge a list with each of the other two.
To combine a list with a dictionary, extract the dictionary’s keys or values and concatenate them with the list. This is useful when merging data from different sources.
my_list = [1, 2, 3]
my_dict = {'a': 4, 'b': 5}
combined_keys = my_list + list(my_dict.keys())
print(combined_keys)
combined_values = my_list + list(my_dict.values())
print(combined_values)
[1, 2, 3, 'a', 'b']
[1, 2, 3, 4, 5]
In the code above, list() converts the dictionary’s keys or values into a list, which is then concatenated with my_list using the + operator.
To combine a list with a set, convert the set to a list first, since sets are unordered and cannot be concatenated to a list directly.
my_list = [1, 2, 3]
my_set = {4, 5, 6}
combined = my_list + list(my_set)
print(combined)
[1, 2, 3, 4, 5, 6]
Here list() turns the set into a list before concatenation. Note that the order of elements coming from the set is not guaranteed, because sets are unordered.
To join a list of strings into a single string, call the join() method on a separator and pass the list as the argument, using the form separator.join(list). The separator can be any string, such as a comma or a space.
words = ['Hello', 'World']
sentence = ' '.join(words)
print(sentence)
This prints Hello World. If the list contains non-string items, convert them first with map(str, list).
To combine two lists into one new list, use the + operator for a quick merge, or itertools.chain() for better performance with large lists. Use list.extend() if you want to grow an existing list in place rather than create a new one.
list1 = [1, 2]
list2 = [3, 4]
combined = list1 + list2
print(combined)
This prints [1, 2, 3, 4].
join() do in Python?The join() method concatenates the strings in an iterable into a single string, separated by the string you call it on. It works on any iterable of strings (lists, tuples, generators) and always returns a string, which is why it is a method of str rather than list.
strip() do in Python?The strip() method removes leading and trailing whitespace (or other specified characters) from a string and returns the cleaned string. It is often used to tidy up individual items before or after joining them, for example removing stray spaces parsed from a file.
str.join() in Python?The correct syntax is separator.join(iterable), where the separator is the string placed between elements and iterable is the list (or other iterable) of strings to join. A frequent mistake is writing list.join(separator), which does not exist and raises an AttributeError.
print("-".join(["2026", "06", "09"]))
This prints 2026-06-09, showing the separator placed between each element.
Convert each integer to a string first, because join() accepts only strings. The simplest way is ",".join(map(str, numbers)), which applies str to every element before joining.
numbers = [1, 2, 3]
print(",".join(map(str, numbers)))
This prints 1,2,3. Joining the integers directly would raise TypeError: sequence item 0: expected str instance, int found.
To produce a combined list (not a string), use the + operator, the * unpacking operator, list.extend(), or itertools.chain(). This is different from join(), which always returns a string rather than a list.
a = [1, 2]
b = [3, 4]
print([*a, *b])
This prints [1, 2, 3, 4].
join() and concatenation using + in Python?The join() method is built for combining many strings efficiently in a single pass (O(n)), while + creates a new object every time it runs. Using + once on two small values is fine, but using + repeatedly in a loop is O(n^2) and slow, so join() is preferred for combining a list of strings.
Use "\n" as the separator, so each list element appears on its own line. This is the standard way to prepare a list for writing to a text file.
lines = ["first", "second", "third"]
print("\n".join(lines))
This prints each word on a separate line.
None values?Not directly, because a None value raises TypeError: sequence item N: expected str instance, NoneType found. Filter the None values out with a generator expression, or convert every element with str() if you want a placeholder.
items = ["a", None, "b"]
print(",".join(x for x in items if x is not None))
This prints a,b.
itertools.chain() instead of the + operator?Use itertools.chain() when merging many lists or very large ones, because it returns a lazy iterator and avoids building intermediate lists, keeping memory usage low. The + operator is fine for a single merge of small lists, but chaining many + operations copies the growing result repeatedly and becomes slow.
str.join(list) instead of list.join(str)?Because join() works with any iterable and always returns a string, it makes sense to define it once on the string class rather than duplicate it across every iterable type. The separator is naturally the string you call the method on, which is why the design is separator.join(iterable).
In this tutorial, you learned the various ways to join and combine lists in Python. You joined a list of strings with the join() method and customized the separator, handled lists of integers, mixed types, None values, and nested lists, and concatenated lists using the + operator, list.extend(), the * unpacking operator, and itertools.chain(). You also compared the methods by performance and complexity, saw why join() is a string method, and explored modern alternatives like the dictionary | operator in Python 3.9+.
To further enhance your knowledge of Python programming, we recommend checking out the following tutorials:
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
Java and Python Developer for 20+ years, Open Source Enthusiast, Founder of https://www.askpython.com/, https://www.linuxfordevices.com/, and JournalDev.com (acquired by DigitalOcean). Passionate about writing technical articles and sharing knowledge with others. Love Java, Python, Unix and related technologies. Follow my X @PankajWebDev
I help Businesses scale with AI x SEO x (authentic) Content that revives traffic and keeps leads flowing | 3,000,000+ Average monthly readers on Medium | Sr Technical Writer(Team Lead) @ DigitalOcean | Ex-Cloud Consultant @ AMEX | Ex-Site Reliability Engineer(DevOps)@Nutanix
With over 6 years of experience in tech publishing, Mani has edited and published more than 75 books covering a wide range of data science topics. Known for his strong attention to detail and technical knowledge, Mani specializes in creating clear, concise, and easy-to-understand content tailored for developers.
message = "Hello ".join(“World”) print(message) #prints ‘Hello World’ This actually prints: ‘WHello oHello rHello lHello d’
- Miguel
Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.
Full documentation for every DigitalOcean product.
The Wave has everything you need to know about building a business, from raising funding to marketing your product.
Stay up to date by signing up for DigitalOcean’s Infrastructure as a Newsletter.
New accounts only. By submitting your email you agree to our Privacy Policy
Scale up as you grow — whether you're running one virtual machine or ten thousand.
From GPU-powered inference and Kubernetes to managed databases and storage, get everything you need to build, scale, and deploy intelligent applications.