split() turns a string into an array of substrings. The two things worth knowing beyond that: the limit argument truncates rather than groups, and splitting on an empty string mangles any character outside the basic multilingual plane.
Everyone knows "a,b,c".split(","). What is less known is that the method takes a second argument almost nobody uses correctly, accepts a regular expression with capture groups that change the output shape, and has a Unicode behaviour that produces broken characters in a way that is invisible until someone types an emoji.
Table of contents
- Separator behaviour, including the empty cases
- The limit argument does not do what people expect
- Regular expression separators and capture groups
- The Unicode trap
- When split is the wrong tool
- Splitting on the way in
- How this fits the rest of the stack
- FAQ
Separator behaviour, including the empty cases
"deploy,build,ship".split(",") // ["deploy", "build", "ship"]
"deploy build ship".split(" ") // ["deploy", "build", "ship"]
"no-separator".split(",") // ["no-separator"] -- whole string, in an array
"deploy".split("") // ["d","e","p","l","o","y"]
When the separator is absent, you get a single-element array rather than an error or null. That is convenient, and it means a missing separator is indistinguishable from a value with no separators in it.
Omitting the separator entirely is different from passing an empty string:
"deploy".split() // ["deploy"] -- the whole string, unsplit
"deploy".split("") // ["d","e","p","l","o","y"] -- every character
And the empty-string edge case that catches people writing generic parsers:
"".split(",") // [""] -- an array containing one empty string
"".split("") // [] -- an empty array
"".split(",").length is 1, not 0. Code counting fields in a CSV line will count one field in an empty line unless it checks for that.
The limit argument does not do what people expect
The second argument caps the array length. It does not put the remainder in the last element:
"a,b,c,d".split(",", 2) // ["a", "b"] -- "c,d" is DISCARDED
This surprises anyone coming from Python or Java, where the equivalent gives you ["a", "b,c,d"]. In JavaScript the tail is simply gone.
To split on the first occurrence and keep the rest, use indexOf and slice:
function splitFirst(s, sep) {
const i = s.indexOf(sep);
return i === -1 ? [s] : [s.slice(0, i), s.slice(i + sep.length)];
}
splitFirst("Authorization: Bearer abc:def", ": ");
// ["Authorization", "Bearer abc:def"]
This is the correct way to parse headers, key=value pairs, and anything where the value may legitimately contain the separator. Using split(": ")[1] on that header gives you "Bearer abc" and quietly loses the rest.
Regular expression separators and capture groups
split accepts a regex, which handles variable separators:
"a, b,c , d".split(/\s*,\s*/) // ["a", "b", "c", "d"]
"one1two22three".split(/\d+/) // ["one", "two", "three"]
"line1\r\nline2\nline3".split(/\r?\n/) // handles both line endings
That last one is worth keeping. Splitting on "\n" alone leaves a trailing \r on every line of a file written on Windows, and the resulting bugs — a config value that will not match, a trailing character in a log parser — are memorably annoying to track down.
Capture groups change the output: matched groups are included in the result array.
"a1b2c".split(/(\d)/) // ["a", "1", "b", "2", "c"] -- separators kept
"a1b2c".split(/\d/) // ["a", "b", "c"] -- separators dropped
This is a feature when you want to keep the delimiters — tokenising an expression, for instance — and a bug when someone adds parentheses to a regex for grouping and doubles the array length. Use non-capturing (?:...) when you only need grouping.
The Unicode trap
split("") splits on UTF-16 code units, not on characters. For anything outside the basic multilingual plane — emoji, many CJK extension characters, some scripts — that means splitting a single character in half:
"café".split("") // ["c","a","f","é"] -- fine
"deploy 🚀".split("") // ["d","e","p","l","o","y"," ","\ud83d","\ude80"]
The rocket has become two broken surrogate halves that render as replacement characters. This is the bug behind truncated usernames displaying garbage, and it is invisible until someone uses an emoji.
The spread operator and Array.from iterate by code point and handle this correctly:
[..."deploy 🚀"] // [...," ","🚀"] -- correct
Array.from("deploy 🚀") // same
Even that is not the full story. Combining characters and emoji with modifiers — skin tones, flags, family sequences — are multiple code points forming one user-perceived character. For genuinely correct handling, use Intl.Segmenter:
const seg = new Intl.Segmenter('en', { granularity: 'grapheme' });
[...seg.segment("👨👩👧 team")].map(s => s.segment);
// ["👨👩👧", " ", "t", "e", "a", "m"]
The rule: never use split("") to get characters. Use spread for code points, Intl.Segmenter for anything user-facing like truncation or character counts.
When split is the wrong tool
Two cases where reaching for split produces something that works on your test data and fails on real data.
CSV parsing. line.split(",") breaks on the first quoted field containing a comma, which in real data is immediate:
Smith, John,42,"London, UK" -- naive split gives 5 fields, not 4
Quoted fields, escaped quotes and embedded newlines are all part of the format. Use a CSV parser. Every hand-rolled CSV splitter eventually becomes a bad CSV parser.
Sentence or word splitting for display. Splitting on " " or "." fails on abbreviations, decimals, non-space-delimited scripts, and punctuation. Intl.Segmenter with granularity: 'word' or 'sentence' is locale-aware and already in the platform.
The general principle: split is for structured data with a known, unambiguous separator. When the separator can appear inside a value, you need a parser.
Splitting on the way in
Most split calls in a service sit at an input boundary — parsing a header, a query parameter, a delimited config value, a line from an uploaded file. That means they are also where malformed input arrives.
Two habits worth having. Validate the resulting array length before destructuring, because split never throws and a malformed input silently yields the wrong shape. And trim the parts when the separator is user-typed:
const parts = value.split(",").map(s => s.trim()).filter(Boolean);
if (parts.length < 2) throw new Error(`expected at least 2 values, got ${parts.length}`);
Node services doing this parsing deploy on RunxBuild from a repository with a build log and a live route, and runtime logs in the same place as the deploy — which is where you find out that a real upload had a quoted comma in it.
How this fits the rest of the stack
split is straightforward until the details matter: the limit argument discards the tail rather than keeping it, capture groups change the output shape, and split("") breaks any character above the basic multilingual plane. Use indexOf and slice to split once, spread or Intl.Segmenter for characters, and a real parser for CSV. When the service doing the parsing needs somewhere to run, the RunxBuild hosting calculator puts the service and its database on one page.
Useful related references:
- JavaScript Sleep: Why There Is No sleep() and What To Use Instead
- JavaScript Uppercase: toUpperCase, and the Locale That Breaks It
- How to Disable JavaScript in Chrome, and Why You Should Do It on Purpose
- Services on RunxBuild
FAQ
How does the limit parameter in split work?
It caps the length of the returned array and discards everything after it. "a,b,c,d".split(",", 2) gives ["a", "b"], not ["a", "b,c,d"]. To split once and keep the remainder, use indexOf and slice instead.
How do I split a string into characters in JavaScript?
Use the spread operator ([...str]) or Array.from(str), which iterate by code point. Do not use split("") — it splits UTF-16 code units and breaks emoji and other characters above the basic multilingual plane into unusable surrogate halves.
Can split take a regular expression?
Yes, and it is the right approach for variable separators such as /\s*,\s*/ or /\r?\n/ for line endings. Be aware that capture groups are included in the output array, so use non-capturing (?:...) when you only need grouping.
Why does splitting an empty string return an array with one element?
"".split(",") returns [""] because there are no separators, so the whole string — empty — is the single element. "".split("") returns []. Code that counts fields needs to handle the first case explicitly.
Should I use split to parse CSV?
No. line.split(",") breaks on quoted fields containing commas, which appear in real data immediately, and it does not handle escaped quotes or embedded newlines. Use a proper CSV parser.