String.prototype[Symbol.iterator]() makes strings iterable by yielding Unicode code points (same API as MDN String.prototype[Symbol.iterator]()). Learn for...of and spread, why surrogate pairs stay together, when emoji clusters still split, manual next(), five examples, and try-it labs.
01
Kind
Instance method
02
Returns
Iterator
03
Yields
Code points
04
Used by
for...of / spread
05
Mutates?
No
06
Baseline
Widely available
Fundamentals
Introduction
Strings implement the iterable protocol through [Symbol.iterator](). That is why you can write for (const ch of str) or [...str] and walk the text one Unicode code point at a time.
You almost never call the method yourself. Loops and spread call it for you. Call it manually when you want fine-grained control with iterator.next().
💡
Beginner tip
Prefer for...of over for (let i = 0; i < str.length; i++) when you care about full characters (including many emoji), not raw UTF-16 units.
next() returns { value, done }. The loop stops at the first space without consuming the rest of the sentence.
Example 2 — for...of (Usual Way)
MDN demo: you seldom call the method β for...of does it.
JavaScript
const str = "A\uD835\uDC68B\uD835\uDC69C\uD835\uDC6A";
for (const v of str) {
console.log(v);
}
// "A"
// "\uD835\uDC68" (mathematical italic capital A)
// "B"
// "\uD835\uDC69"
// "C"
// "\uD835\uDC6A"
Surrogate pairs stay together, but modifiers and zero-width joiners are separate code points. For βuser-perceived characters,β you need a grapheme library β not only the string iterator.
Example 5 — Spread vs charAt
Practical compare: spread uses the iterator; charAt does not.
JavaScript
const s = "π"; // mathematical bold A (surrogate pair)
console.log(s.length); // 2 (UTF-16 units)
console.log(JSON.stringify([...s])); // ["π"]
console.log(JSON.stringify(s.charAt(0))); // half of the pair
console.log(Array.from(s).join("-")); // "π"
Build arrays of code points — [...str] or Array.from(str).
Early-exit scans — manual next() until a delimiter.
Not grapheme clustering — skin tones / ZWJ may still split.
Not UTF-16 indexing — use charAt / indexes when you need units.
🧠 How String Iteration Works
1
Get the iterator
for...of / spread call str[Symbol.iterator]().
Start
2
Read the next code point
If a high surrogate is paired, both units become one string.
Yield
3
Repeat until done
next() returns { done: true } at the end.
Loop
4
✅
Original string unchanged
Iteration only reads values.
Important
📝 Notes
You rarely call [Symbol.iterator] directly β prefer for...of.
Surrogate pairs are preserved; grapheme clusters may still split.
str.length counts UTF-16 units, not iterator steps.
Lone surrogates yield as single broken units β pair with isWellFormed when needed.
Each call to [Symbol.iterator]() creates a new iterator from the start.
Compatibility
Browser & Runtime Support
String.prototype[Symbol.iterator]() is Baseline Widely available β supported across modern browsers since September 2015.
✓ Baseline · Widely available
String[Symbol.iterator]()
Safe for production in browsers and runtimes (Node.js, Deno, Bun). Powers for...of and spread on strings.
UniversalWidely available
Google ChromeSupported · Desktop & Mobile
Full support
Mozilla FirefoxSupported · Desktop & Mobile
Full support
Apple SafariSupported · macOS & iOS
Full support
Microsoft EdgeSupported · Chromium
Full support
Internet ExplorerNo native support · Use a polyfill
Polyfill
OperaSupported · Modern versions
Full support
Samsung InternetSupported · Android
Full support
BunSupported · JavaScript runtime
Supported
DenoSupported · JavaScript runtime
Supported
Node.jsSupported · Server runtime
Supported
Android WebViewSupported · Modern WebView
Full support
[Symbol.iterator]()Excellent
Bottom line: Use for...of or spread to walk Unicode code points. Call [Symbol.iterator]() only when you need manual next() control.
Wrap Up
Conclusion
String.prototype[Symbol.iterator]() is why strings work with modern iteration syntax. Prefer for...of for everyday loops, and remember that code points are safer than UTF-16 indexes for emoji β but not always identical to visible graphemes.
Remember grapheme clusters may need extra libraries
❌ Don’t
Assume every emoji is a single iterator step
Use str[i] loops for emoji-heavy text
Equate length with the number of for...of steps
Forget each [Symbol.iterator]() call starts over
Call the method when for...of already does the job
Summary
Key Takeaways
Knowledge Unlocked
Five things to remember about String[Symbol.iterator]()
Iterable by code points β for...of usually calls it for you.
5
Core concepts
📝01
Returns
iterator
API
🌐02
Yields
code points
Unicode
🔄03
Used by
for...of
Syntax
😀04
Clusters
may split
Emoji
⚡05
Mutates
no
Immutable
❓ Frequently Asked Questions
It returns a string iterator that yields each Unicode code point as a string. That makes strings iterable for for...of, spread (...), Array.from, and similar APIs.
Rarely. for...of and the spread syntax call it automatically. Call it manually when you want step-by-step control with next().
str[i] and charAt(i) read UTF-16 code units. The iterator yields full Unicode code points, so a single emoji made of a surrogate pair is one step, not two.
Surrogate pairs stay together as one code point. Grapheme clusters (emoji + skin tone, ZWJ sequences) can still split into multiple code points.
An object { value, done }. value is the next code-point string (or undefined when done is true).
No. It only creates an iterator that reads the string. Strings are immutable.
Did you know?
Before Symbol.iterator, people often wrote for (var i = 0; i < str.length; i++) and accidentally split emoji. The iterable protocol is why modern loops βjust workβ for most Unicode letters β as long as you remember grapheme clusters are a separate problem.