substring() ist eine der meistgenutzten String-Methoden in JavaScript. Sie extrahiert Teile eines Textes, ohne das Original zu verändern. In diesem Leitfaden zeige ich dir präzise, wie substring() funktioniert, worin es sich von slice() und substr() unterscheidet, welche Edge-Cases dich in der Praxis erwarten, wie du Unicode- und Emoji-Fallen meidest, und welche Best Practices sich bewährt haben.
Was substring() konkret macht
Mit substring(start, end?) holst du dir die Zeichen von start bis exklusiv end. Lässt du end weg, geht es bis zum Ende des Strings. Der ursprüngliche String bleibt unverändert.
const s = "Mozilla";
s.substring(1, 3); // "oz" (Index 1 und 2)
s.substring(3); // "illa" (ab Index 3 bis Ende)
s.substring(0, 0); // "" (leer)
Merksatz: substring() nutzt 0-basierte Indizes und schließt den Endindex nicht ein.
Syntax, Parameter und Typumwandlungen
| Parameter | Typ | Bedeutung | Besonderheiten |
|---|---|---|---|
| start | Number | Index des ersten einzuschließenden Zeichens | 0-basiert; negative Werte werden auf 0 geklemmt |
| end (optional) | Number | Index des ersten auszuschließenden Zeichens | Fehlt end, geht die Extraktion bis zum Ende |
- Clamping: Werte < 0 werden zu 0; Werte > Länge werden zur Stringlänge.
- Vertauschen: Ist
start > end, werden die Werte automatisch getauscht. - NaN/Infinity:
NaNwird wie 0 behandelt,Infinitywie Stringlänge. - Fließkommazahlen: werden intern auf Integer abgeschnitten (Trunkierung Richtung 0).
- Typ-Coercion: Übergibst du z. B.
"5", wird es zu5konvertiert.
const str = "ABCDE";
str.substring(-3, 2); // "AB" (start => 0, end => 2)
str.substring(4, 2); // "CD" (start > end => swap)
str.substring(2, 2); // ""
str.substring(2, 99); // "CDE" (end wird auf Länge geklemmt)
str.substring(2.9, 4.2); // "CD" (2.9 => 2, 4.2 => 4)

substring() vs slice() vs substr(): Die Unterschiede auf einen Blick
| Merkmal | substring(start, end?) | slice(start, end?) | substr(start, length?) |
|---|---|---|---|
| End-Argument | exklusive Endposition | exklusive Endposition | Anzahl Zeichen |
| Negative Indizes | nicht unterstützt (geklammert zu 0) | unterstützt (zählt vom Ende) | Start negativ = vom Ende |
| Start > End | Werte werden getauscht | gibt „“ (leer) zurück | n/a |
| Status | Standard | Standard | Legacy/Veraltet, nicht empfohlen |
Codebeispiele, die die Unterschiede verdeutlichen:
const s = "Mozilla";
// Negative Indizes
s.substring(-5); // "Mozilla" (Start => 0, also gesamter String)
s.slice(-5); // "zilla" (die letzten 5 Zeichen)
s.substr(-5, 3); // "zil" (Start 5 vor Ende, dann 3 Zeichen)
// Vertauschte Indizes
s.substring(5, 2); // "zil" (swap zu (2,5))
s.slice(5, 2); // "" (kein Swap)
// Länge vs Endindex
"Hello, World!".substring(7, 12); // "World"
"Hello, World!".substr(7, 5); // "World"
"Hello, World!".slice(7, 12); // "World"
Empfehlung: Nutze substring(), wenn du bewusst mit Start-/Endpositionen arbeitest und kein negatives Indexing brauchst. Nutze slice(), wenn negative Indizes relevant sind oder du leeres Ergebnis bei vertauschten Parametern bevorzugst. substr() gilt als legacy und sollte vermieden werden.
Praxisnahe Anwendungsfälle
Domäne aus E-Mail extrahieren
const email = "user@example.com";
const at = email.indexOf("@");
const domain = at !== -1 ? email.substring(at + 1) : "";
// "example.com"
Dateiendung ermitteln
const filename = "report.final.v3.pdf";
const dot = filename.lastIndexOf(".");
const ext = dot !== -1 ? filename.substring(dot + 1) : "";
// "pdf"
Letzte n Zeichen (z. B. Maskierung)
const iban = "DE12345678901234567890";
const last4 = iban.substring(iban.length - 4); // "7890"
const masked = "**** **** **** **** " + last4;
// "**** **** **** **** 7890"
Erstes Wort oder Prefix extrahieren
const title = "ECMAScript Guide";
const space = title.indexOf(" ");
const firstWord = space === -1 ? title : title.substring(0, space);
// "ECMAScript"
Pfadteile aus URL ziehen (ohne URL API)
const url = "https://example.com/blog/articles/123?ref=home";
const protoSep = "://";
const schemaEnd = url.indexOf(protoSep);
const afterSchema = schemaEnd !== -1 ? url.substring(schemaEnd + protoSep.length) : url;
// "example.com/blog/articles/123?ref=home"
const pathStart = afterSchema.indexOf("/");
const host = pathStart !== -1 ? afterSchema.substring(0, pathStart) : afterSchema;
// "example.com"
const pathWithQuery = pathStart !== -1 ? afterSchema.substring(pathStart) : "";
// "/blog/articles/123?ref=home"
Normierung: Trimmen eines Slugs auf feste Länge
function normalizeSlug(s, max = 50) {
const clean = s.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9\-]/g, "");
return clean.length > max ? clean.substring(0, max) : clean;
}
Unicode, Emojis und zusammengesetzte Zeichen: das große Missverständnis
Wichtig: JavaScript-Strings sind UTF-16 sequenzen. substring(), slice() und Co. arbeiten auf Code Units, nicht auf Grapheme-Clusters. Dadurch kannst du Emojis und zusammengesetzte Zeichen unabsichtlich „zersägen“.
Beispiel mit Emoji:
const emoji = "A😊B";
emoji.length; // 4 (😊 besteht aus 2 UTF-16 Code Units)
emoji.substring(1, 2); // liefert nur die erste Code Unit des Emojis => kaputter String
So machst du es richtig:
- Codepoint-sicher via Iteration:
Array.from(str)nutzt den String-Iterator (iteriert über Unicode-Codepunkte). - Graphem-sicher:
Intl.Segmenter(moderne Engines) oder Bibliotheken wie grapheme-splitter.
// Codepoint-sicher
function cpSubstring(str, start, end = Infinity) {
const arr = Array.from(str); // iteriert über Codepoints
return arr.slice(start, end).join("");
}
cpSubstring("A😊B", 0, 2); // "A😊"
// Graphem-sicher (z. B. für zusammengesetzte Emojis/Flags/Skintones)
function graphemeSplit(str) {
if (typeof Intl !== "undefined" && Intl.Segmenter) {
const seg = new Intl.Segmenter(undefined, { granularity: "grapheme" });
return Array.from(seg.segment(str), s => s.segment);
}
// Fallback (vereinfachend): Codepoint-Split
return Array.from(str);
}
function graphemeSubstring(str, start, end = Infinity) {
const g = graphemeSplit(str);
return g.slice(start, end).join("");
}
Praxisregel: Sobald Nutzertext, Emojis oder Sprachen mit kombinierten Zeichen im Spiel sind, vermeide substring() für „sichtbare Zeichen“. Nutze Codepoint- oder Graphem-basierte Alternativen.

Häufige Fehler und wie du sie vermeidest
- Verwechselte Grenzen: Du willst Zeichen 0–4 und schreibst
substring(0, 5). Das ist korrekt. Häufige Fehler entstehen, wenn das exklusive Ende vergessen wird. Tipp: kommentiere „exklusive Endposition“, wenn der Kontext es nicht klar macht. - Negative Indizes erwartet: substring() klemmt negativ auf 0. Wenn du „vom Ende“ zählen willst, nutze slice() oder berechne die Indizes relativ zur Länge.
- Start > End nicht beachtet: substring() tauscht die Parameter – kann in Debug-Situationen verwirren. Wer leere Ergebnisse erwartet, nimmt slice().
- Unicode zerschnitten: vermeide substring() bei Emojis/Graphemen, nutze Codepoint/Graphem-Alternativen (siehe oben).
- Fehlende Bound-Checks: verlasse dich nicht nur auf „Clamping“. Prüfe in kritischen Pfaden selbst die Werte, damit die Absicht im Code klar ist.
- substr() weiterbenutzt: In neuem Code nicht mehr verwenden. Das API ist legacy; setze auf substring() oder slice().
Performanz und Speicher
Für die meisten Anwendungsfälle sind substring() und slice() in modernen Engines nahezu gleichwertig. Unterschiede hängen stark von Engine, Inputgröße und JIT-Optimierungen ab. Mikrobenchmarks variieren – plane deine API nach Semantik und Lesbarkeit, nicht nach vermeintlichen Pauschal-Speedvorteilen.
- Komplexität: O(n) in der Länge des extrahierten Teils.
- Kurzlebige Substrings: Engines optimieren häufig für diese Fälle. Häufige Substring-Operationen in Hot-Paths sind normalerweise unkritisch.
- Große Daten: Wenn du mehrfache Kopien sehr großer Strings erzeugst, achte auf Speicherverbrauch; streame oder arbeite segmentiert.
// Beispiel: segmentiertes Verarbeiten einer großen Datei (konzeptionell)
function processInChunks(str, size = 64 * 1024) {
for (let i = 0; i < str.length; i += size) {
const chunk = str.substring(i, Math.min(i + size, str.length));
// ... verarbeiten ...
}
}
Robuste Muster und Best Practices
1) Grenzen explizit behandeln
function safeSubstring(str, start, end) {
const len = str.length;
const s = Math.max(0, Math.min(len, start | 0)); // trunc to int + clamp
const e = end == null ? len : Math.max(0, Math.min(len, end | 0));
return s <= e ? str.substring(s, e) : str.substring(e, s); // bewusstes Spiegeln
}
2) Negative Indizes bewusst via slice oder Umrechnung
function fromEnd(str, startFromEnd, endFromEnd) {
const len = str.length;
const s = Math.max(0, len + startFromEnd);
const e = endFromEnd == null ? len : Math.max(0, len + endFromEnd);
return str.slice(s, e);
}
fromEnd("abcdef", -3); // "def"
fromEnd("abcdef", -4, -1); // "cde"
3) Lesbarkeit vor Mikro-Optimierung
Wenn das Team häufiger negative Indizes erwartet, etabliere slice() als Standard. Wenn „Start/End exklusive“ zentral ist, substring(). Lege dich teamweit fest und halte dich an einheitliche Muster.
4) String-APIs kombinieren
// Bereich zwischen zwei Marken extrahieren
function between(haystack, a, b) {
const i = haystack.indexOf(a);
if (i === -1) return "";
const j = haystack.indexOf(b, i + a.length);
if (j === -1) return "";
return haystack.substring(i + a.length, j);
}
between("foo [bar] baz", "[", "]"); // "bar"
5) TypeScript-Hinweis
Die Signatur ist eindeutig, dennoch lohnt sich ein kleines Helper-API für domänenspezifische Namen:
function extractPrefix(str: string, endExclusive: number): string {
return str.substring(0, endExclusive);
}
Edge-Cases im Detail
- start = end: Immer leerer String.
- start > end: Auto-Swap – kann Bugs verdecken. Wenn du so einen Fall als Fehler behandeln willst, prüfe die Reihenfolge vorher und wirf eine Exception.
- Argumente als Strings: „2“ wird zu 2; Achte auf unbewusste Typkonvertierungen aus Formularwerten.
- this-Kontext: Direkter Aufruf an String-Instanz unkritisch.
String.prototype.substring.call(obj, ...)coerctobjzu String.null/undefinedals this werfen.
String.prototype.substring.call(12345, 1, 3); // "23"
String.prototype.substring.call(null, 1, 3); // TypeError
Kompatibilität und Laufzeitumgebungen
substring() ist seit den Anfängen von JavaScript vorhanden und wird in allen modernen Browsern sowie in Node.js unterstützt.
| Umgebung | Support | Hinweise |
|---|---|---|
| Chrome, Edge, Firefox, Safari | Vollständig | Breit verfügbar seit vielen Jahren |
| Node.js | Vollständig | Läuft auf V8; substring wie im Browser |
| IE (historisch) | Vollständig | Legacy-Umgebungen sollten vermieden werden |
Gut zu wissen: substr() ist in vielen Engines noch vorhanden, gilt aber als legacy. Setze in neuem Code auf substring() oder slice().
Cheatsheet: die wichtigsten Muster kompakt
- Erste n Zeichen:
str.substring(0, n) - Letzte n Zeichen:
str.substring(str.length - n) - Von a bis b (exklusiv):
str.substring(a, b) - Vom Ende aus (lieber slice):
str.slice(-n) - Zwischen zwei Marken:
str.substring(str.indexOf(a) + a.length, str.indexOf(b, ...)) - Unicode-sicher (Codepoints):
Array.from(str).slice(a, b).join("")
Beispiele aus realen Projekten
1) Versionsstrings parsen
function parseSemver(v) {
// "1.2.3-alpha+build.5"
const plus = v.indexOf("+");
const baseAndPre = plus === -1 ? v : v.substring(0, plus);
const meta = plus === -1 ? "" : v.substring(plus + 1);
const dash = baseAndPre.indexOf("-");
const base = dash === -1 ? baseAndPre : baseAndPre.substring(0, dash);
const pre = dash === -1 ? "" : baseAndPre.substring(dash + 1);
const [major, minor, patch] = base.split(".").map(Number);
return { major, minor, patch, pre, meta };
}
2) Logzeilen normalisieren
function trimLogLine(line, max = 120) {
return line.length > max ? line.substring(0, max - 1) + "…" : line;
}
3) Token-Parsing (Header: „Bearer …“)
function extractBearerAuth(header) {
const prefix = "Bearer ";
return header.startsWith(prefix) ? header.substring(prefix.length) : "";
}
4) CSV-Feld (einfacher Fall, keine Quotes)
function nthCsvField(line, n) {
let start = 0, end, pos = 0;
while (pos < n) {
end = line.indexOf(",", start);
if (end === -1) return "";
start = end + 1;
pos++;
}
end = line.indexOf(",", start);
return end === -1 ? line.substring(start) : line.substring(start, end);
}
Debugging-Tipps
- Klarheit über End-Exklusivität: Logge bewusst Start/End in Debug-Ausgaben und benenne Variablen
endExclusive, um Missverständnisse zu vermeiden. - Unicode-Checks: Wenn seltsame Zeichen entstehen, gib
[...str]oderArray.from(str)aus, um Codepoints zu inspizieren. - Assert-Reihenfolge: Füge Dev-Asserts ein, wenn
start > endin deinem Use-Case niemals vorkommen darf.
Wann substring(), wann slice()?
Beide sind valide. Entscheide nach Semantik deines Codes:
- substring(): Klar ersichtlich, wenn du mit Indizes als Bereich (Start inklusive, Ende exklusiv) arbeitest und negatives Indexing ausgeschlossen ist.
- slice(): Sinnvoll, wenn du häufig „vom Ende“ zählst oder leeres Ergebnis bei vertauschten Werten erwartest.
Wenn du im Team eine gemeinsame Konvention definierst, vermeidest du kognitive Reibung und Bug-Klassen durch unterschiedliche Erwartungen.
Qualitätssicherung
- Tests: Spezifische Tests für Grenzwerte (0, Länge, -1, length+1, start=end, start>end).
- Unicode-Fälle: Lege Tests mit Emojis, zusammengesetzten Zeichen, RTL-Skripten an.
- Statische Analyse: Lintern beibringen, substr() zu verbieten, und ggf. Konsistenz-Regeln für slice()/substring() zu erzwingen.
Fazit
substring() ist ein stabiles, breit unterstütztes Werkzeug für String-Zuschnitte in JavaScript. Es überzeugt durch einfache Semantik (Start inklusive, Ende exklusiv), tolerantes Verhalten (Clamping, Auto-Swap) und gute Lesbarkeit in klassischen Extraktionsaufgaben. In Situationen, in denen du mit negativen Indizes arbeitest oder bewusst ein leeres Ergebnis bei vertauschten Parametern möchtest, ist slice() die bessere Wahl. substr() solltest du für neuen Code meiden. Achte in jedem Fall auf Unicode-Fallen und nutze bei Bedarf Codepoint- oder Graphem-basierte Alternativen. Wenn du klare Teamkonventionen definierst, Grenzwerte testest und bewusst mit den Parametern umgehst, ist substring() ein verlässlicher Baustein für robuste Textverarbeitung – von einfachen Maskierungen bis hin zu präziser Segmentierung komplexer Strings. Ein abschließender Hinweis: In diesem Tutorial hast du gelernt, wie substring() funktioniert – die Prinzipien von ECMAScript, Array-Iteration und JavaScript ermöglichen es, Code klar und effizient zu gestalten.
FAQ
Ist javascript substring schneller als slice?
In modernen Engines sind die Unterschiede meist gering und kontextspezifisch. Nutze die Methode, die semantisch besser passt. Miss Performance nur, wenn sie wirklich kritisch ist, und zwar in deiner Zielumgebung.
Warum gibt substring(start, end) das Zeichen bei end nicht zurück?
Das Design folgt dem üblichen Muster „Start inklusive, Ende exklusiv“. Dadurch lassen sich benachbarte Bereiche ohne Off-by-one-Fehler aneinanderreihen.
Wie bekomme ich die letzten n Zeichen mit substring()?
Berechne den Start relativ zur Länge: str.substring(str.length - n). Alternativ ist str.slice(-n) noch kompakter.
Warum ist substr() nicht empfohlen?
substr() gilt als legacy und ist in Standards nicht mehr priorisiert. Es funktioniert zwar in vielen Umgebungen, kann aber perspektivisch entfallen. Nutze substring() oder slice().
Wie gehe ich mit Emojis sicher um?
Nutze Array.from(str) (Codepoint-basiert) oder Intl.Segmenter (Graphem-basiert), um sichtbare Zeichen nicht zu zerschneiden. substring() arbeitet auf UTF-16 Code Units und kann Emojis beschädigen.
Was passiert bei negativen oder NaN-Indizes?
substring() klemmt negative Werte auf 0; NaN wird wie 0 behandelt. Werte über der Länge werden auf die Länge geklemmt. Bei start > end tauscht substring() die Parameter.
Wie extrahiere ich Text zwischen zwei Markern?
Nutze indexOf für die Marken und dann substring(start, end). Achte auf -1-Fälle, wenn ein Marker fehlt.
Kann ich substring() auf Nicht-Strings anwenden?
Indirekt ja: String.prototype.substring.call(value, ...) coerct value zu String. Für null/undefined wird ein TypeError geworfen.
Wie vermeide ich Off-by-one-Fehler?
Kommentiere bei Bedarf „Ende exklusiv“, verwende sprechende Variablennamen (z. B. endExclusive) und schreibe gezielte Tests für Grenzfälle.
Gibt es eine einfache Faustregel für substring vs slice?
Ja: Wenn du keine negativen Indizes brauchst und Start/End exklusiv-expressiv ausdrücken willst, substring(). Wenn du oft „vom Ende“ aus zählst oder explizit leere Ergebnisse bei vertauschten Parametern bevorzugst, slice().