JavaScript split() and join(): strings to arrays and back
JavaScript split() turns a string into an array and join() turns it back. The edge cases: empty strings, the limit argument, regex separators and emoji.
In JavaScript, split() turns a string into an array by cutting it wherever a separator appears, and join() does the reverse, gluing an array's items into one string with a separator between them:
"red,green,blue".split(","); // ["red", "green", "blue"]
["red", "green", "blue"].join(", "); // "red, green, blue"
The separator is the whole story. Here is what different separators give you:
| Call | Result |
|---|---|
"a,b,c".split(",") |
["a", "b", "c"] |
"abc".split("") |
["a", "b", "c"] |
"abc".split() |
["abc"] |
"a,b,c,d".split(",", 2) |
["a", "b"] |
["a", "b", "c"].join() |
"a,b,c" |
["a", "b", "c"].join("") |
"abc" |
Neither method changes what it was called on: split returns a new array and leaves the string alone, and join returns a new string and leaves the array alone. Every output in this post was measured in Node v24.14.1.
split(): turn a string into an array
The signature is str.split(separator, limit). The separator can be a string or a regular expression, and it is removed from the output:
"2026-10-30".split("-"); // ["2026", "10", "30"]
"one two three".split(" "); // ["one", "two", "three"]
Call it with no separator and you get the whole string back as a one-item array, which is rarely what anyone means:
"abc".split(); // ["abc"]
The same happens when the separator never appears in the string. That one is useful rather than surprising: split never throws over a missing separator, so "no-commas-here".split(",")[0] is always safe and always gives you the whole string.
The split edge cases that surprise people
Most split bugs come from empty strings appearing where you did not expect them, or failing to appear where you did:
| Call | Result | Why |
|---|---|---|
"a,b,,c".split(",") |
["a", "b", "", "c"] |
two separators in a row leave an empty item between them |
",a,b,".split(",") |
["", "a", "b", ""] |
a separator at either end leaves an empty item at that end |
"".split(",") |
[""] |
an empty string splits into one empty item, not zero |
"".split("") |
[] |
except with an empty separator |
"a b c".split(" ") |
["a", "b", "", "c"] |
a double space counts as two separators |
The third row causes the most real bugs. "".split(",").length is 1, so code that counts comma-separated tags reports one tag for an empty input field. Guard for it explicitly:
const tags = input ? input.split(",") : [];
Or remove empty strings after splitting with .filter(Boolean), which fixes the first, second and fifth rows as well. Be careful with that in data where an empty field carries meaning, such as a spreadsheet export, where an empty cell still has to occupy its column.
Split on whitespace, several separators, or a regex
Splitting human-typed text on " " fails as soon as someone types two spaces or a tab. A regex handles any run of whitespace:
" a b c ".trim().split(/\s+/); // ["a", "b", "c"]
" a b ".split(/\s+/); // ["", "a", "b", ""]
Trim first. Without the trim, the whitespace at either end still produces an empty item. Python's split() with no argument does both steps in one call, and JavaScript has no equivalent, which is one of the bigger differences covered in Python's split() and join().
To split on more than one separator, use a character class or an alternation:
"a,b;c".split(/,|;/); // ["a", "b", "c"]
"a, b;c".split(/[,;]\s*/); // ["a", "b", "c"]
The second version also swallows any spaces after each separator, so "a, b" does not leave a leading space on "b".
Capture groups keep the separators
If the regex contains parentheses, whatever they capture is spliced into the result between the pieces:
"a,b,c".split(/(,)/); // ["a", ",", "b", ",", "c"]
"1+2-3".split(/([+-])/); // ["1", "+", "2", "-", "3"]
That is handy for a tiny tokenizer that needs to keep the operators. It is a surprise when you added the parentheses only for grouping. Use (?:...) for a group that does not capture.
Line endings
"line1\r\nline2\nline3".split("\n"); // ["line1\r", "line2", "line3"]
"line1\r\nline2\nline3".split(/\r?\n/); // ["line1", "line2", "line3"]
Text from Windows ends lines with \r\n. Splitting on "\n" alone leaves an invisible \r on the end of those lines, so line === "line1" is false and the last value on each line carries a stray character. /\r?\n/ handles both styles.
The limit argument drops the rest
"a,b,c,d".split(",", 2); // ["a", "b"]
"a,b,c,d".split(",", 0); // []
The second argument caps how many items you get back, and everything after the cap is thrown away. It does not stop splitting and keep the remainder. Python's maxsplit does keep it, and the JavaScript behaviour bites anyone parsing key-value pairs:
"key=val=ue".split("=", 2); // ["key", "val"]
The =ue is gone. To split on the first separator only, collect the rest and join it back:
const [key, ...rest] = "key=val=ue".split("=");
const value = rest.join("="); // "val=ue"
Or find the separator and slice around it, which avoids building an array at all:
const line = "key=val=ue";
const i = line.indexOf("=");
const key = line.slice(0, i); // "key"
const value = line.slice(i + 1); // "val=ue"
Check for i === -1 before slicing. With no separator in the line, line.slice(0, -1) does not fail; it quietly drops the last character. "novalue".slice(0, -1) is "novalu".
Split on the last separator
To pull a file name off a path, or an extension off a file name, you want the last separator, not the first. lastIndexOf finds it:
const path = "src/components/button.tsx";
const i = path.lastIndexOf("/");
path.slice(0, i); // "src/components"
path.slice(i + 1); // "button.tsx"
path.split("/").pop(); // "button.tsx"
split("/").pop() is the shortest way to get the last piece when you do not need the rest.
Split a string into chunks of a fixed length
split cuts on separators, so it cannot split "abcdefg" into groups of three. A regex with match can:
"abcdefg".match(/.{1,3}/g); // ["abc", "def", "g"]
"".match(/.{1,3}/g); // null
.{1,3} means "one to three of any character", and the g flag collects every match. The last chunk is shorter when the length does not divide evenly.
Two traps. On an empty string match returns null, not an empty array, so write str.match(/.{1,3}/g) ?? []. And . does not match a newline, so "ab\ncd" comes back as ["ab", "cd"] with the newline silently gone; add the s flag (/.{1,3}/gs) when the text can contain line breaks.
Split a string into characters
split("") splits by UTF-16 code unit, which is not the same as a character:
"Hi 😀".split(""); // ["H", "i", " ", "\ud83d", "\ude00"]
[..."Hi 😀"]; // ["H", "i", " ", "😀"]
An emoji takes two code units, so split("") cuts it in half. Spreading the string, or Array.from(str), iterates by code point and keeps it whole. This is the same bug that breaks the classic way to reverse a string, and that post covers flags and skin tones, where even spreading is not enough.
The rule of thumb: never use split("") on text a person typed.
join(): turn an array back into a string
["a", "b", "c"].join(); // "a,b,c"
["a", "b", "c"].join(""); // "abc"
["a", "b", "c"].join(", "); // "a, b, c"
The default separator is a comma, not an empty string. Forgetting the "" when you want the pieces glued together is the classic mistake with join.
join converts every item to a string first, with a few rules worth knowing:
[1, null, undefined, 2].join("-"); // "1---2"
[[1, 2], [3]].join(";"); // "1,2;3"
[{ a: 1 }].join(); // "[object Object]"
[].join(","); // ""
null and undefined become empty strings, not the words "null" and "undefined". Nested arrays are joined with commas whatever separator you passed, because each inner array is converted with its own default. Objects turn into "[object Object]", so map them to the text you want first:
const users = [{ name: "Ada" }, { name: "Grace" }];
users.map((u) => u.name).join(", "); // "Ada, Grace"
The same conversion happens implicitly whenever an array is treated as a string. String([1, 2, 3]) and [1, 2, 3] + "" are both "1,2,3", which is why an array dropped into a template literal shows up comma-separated.
split and join recipes
Replace every occurrence. Splitting on the old text and joining with the new was the standard replace-all trick before replaceAll arrived in 2021:
"a-b-c".split("-").join("+"); // "a+b+c"
"a-b-c".replaceAll("-", "+"); // "a+b+c"
Both give the same result. replaceAll says what it does, so prefer it in new code.
Make a URL slug.
const slugify = (title) => title.trim().toLowerCase().split(/\s+/).join("-");
slugify(" Hello World From JS "); // "hello-world-from-js"
Real slug functions also strip punctuation, but split-on-whitespace and join-with-hyphen is the core of every one of them.
Turn a list of numbers into numbers.
"1,2,3".split(",").map(Number); // [1, 2, 3]
"1,2,3".split(",").map(parseInt); // [1, NaN, NaN]
The second line is a famous trap. map calls its function with the value, the index and the array, and parseInt reads its second argument as the radix. So it runs parseInt("2", 1) and parseInt("3", 2), neither of which is a valid number in that base. Use Number, or wrap it as (s) => parseInt(s, 10). The differences between those two are in how to convert a string to a number.
Title-case a phrase. Split into words, capitalize each one, join with spaces. The capitalize helper and its emoji edge case are in how to capitalize the first letter of a string.
When split is the wrong tool
'Ada,"Hello, world"'.split(","); // ["Ada", "\"Hello", " world\""]
CSV allows commas inside quoted fields, and split knows nothing about quotes. One comma in a customer's address shifts every later column one place to the right. Use a real CSV parser for CSV.
The same goes for other formats with their own escaping rules. Use URLSearchParams for query strings rather than splitting on & and =, since it also decodes %20 and +. split is for text whose separator can never appear inside a value. The MDN split() reference has the full specification of the regex and limit behaviour.
Quick reference
str.split(",") // on a fixed separator
str.trim().split(/\s+/) // into words
str.split(/\r?\n/) // into lines, any line ending
str.split(/[,;]\s*/) // on several separators
[...str] // into characters, emoji-safe
input ? input.split(",") : [] // empty input means no items
const [first, ...rest] = str.split("=") // first separator only
arr.join("") // glue with nothing between
arr.join(", ") // readable list
Want to practise strings and arrays with code that runs as you type? Start JavaScript Basics.
More from the blog
What if __name__ == '__main__' actually does in Python
In Python, if __name__ == '__main__' runs code only when you run the file directly, not when it is imported. What __name__ holds and when you need it.
Read moreHow to reverse a list in Python (and a string)
To reverse a list in Python, use reverse() to change it in place, [::-1] for a reversed copy, or reversed() to loop backwards. Which to use and why.
Read moreReady to write some code?
Put this into practice - start your first free lesson. No setup, no credit card.