JavaScript String Slicing: slice(), substring(), and substr() — Which to Use
JavaScript has three methods that extract substrings — and developers mix them up constantly. Getting them straight pays off immediately: you'll write cleaner code, avoid silent bugs with negative indices, and know exactly why slice() is the one to reach for first. Slicing appears constantly in real code: parsing URLs, trimming display text, extracting file extensions, and working with timestamps.
Visualising the Indices
Like all strings, JavaScript uses zero-based indexing. Think of the index as the position before each character — slice(2, 6) cuts at position 2 and position 6:
const s = "JavaScript";
// J a v a S c r i p t
// 0 1 2 3 4 5 6 7 8 9 ← positive
// -10 -9 -8 -7 -6 -5 -4 -3 -2 -1 ← negative (slice only)
console.log(s.slice(2, 6)); // "vaSc" — positions 2, 3, 4, 5
console.log(s.slice(4)); // "Script" — from 4 to end
console.log(s.slice(0, 4)); // "Java" — first 4 characters
The Three Methods: slice vs substring vs substr
All three extract substrings, but they handle parameters very differently:
const s = "JavaScript";
// slice(start, end) — end exclusive, supports negative indices ✓
console.log(s.slice(0, 4)); // "Java"
console.log(s.slice(-6, -2)); // "Scri" — negative indices work
// substring(start, end) — end exclusive, NO negative indices
// Negatives treated as 0; swaps args if start > end
console.log(s.substring(0, 4)); // "Java"
console.log(s.substring(-4)); // "JavaScript" — -4 becomes 0 (no negative support)
// substr(start, length) — second arg is LENGTH not end index (deprecated)
console.log(s.substr(4, 6)); // "Script" — 6 chars starting at index 4
Rule of thumb: Use slice() — it's the most predictable and supports negative indices. Avoid substr() (deprecated in modern JS).
Negative Indices in slice()
Negative indices count backward from the end of the string. -1 is the last character, -2 is second-to-last:
const s = "JavaScript";
console.log(s.slice(-1)); // "t" — last character
console.log(s.slice(-6)); // "Script" — last 6 characters
console.log(s.slice(-6, -2)); // "Scri" — last 6 to last 2 (exclusive)
// ES2022 shorthand for last character:
console.log(s.at(-1)); // "t"
console.log(s.at(-2)); // "p"
Out-of-Bounds: No Error, Just Clipped
Unlike Java, JavaScript's slice() never throws — it silently clips out-of-range indices:
const s = "Hi";
console.log(s.slice(0, 100)); // "Hi" — clips to string length
console.log(s.slice(5, 10)); // "" — empty string (start past end)
console.log(s.slice(-100)); // "Hi" — clips to start of string
Dynamic Slicing with indexOf()
Combine indexOf() with slice() to find boundaries at runtime — one of the most common patterns in string processing:
// Extract domain from an email address:
const email = "user@example.com";
const atPos = email.indexOf("@");
const domain = email.slice(atPos + 1);
console.log(domain); // "example.com"
// Extract path from a URL (skip "https://"):
const url = "https://example.com/blog/post-1";
const pathStart = url.indexOf("/", 8);
const path = url.slice(pathStart);
console.log(path); // "/blog/post-1"
// Truncate a title for a preview card:
const title = "Introduction to Machine Learning";
const preview = title.length > 20 ? title.slice(0, 20) + "..." : title;
console.log(preview); // "Introduction to Mach..."
Real-World Patterns
// Get file extension:
const filename = "report_2024.pdf";
const ext = filename.slice(filename.lastIndexOf(".") + 1);
console.log(ext); // "pdf"
// Remove first and last characters (trim brackets, quotes):
const withBrackets = "[hello]";
const inner = withBrackets.slice(1, -1);
console.log(inner); // "hello"
// Check if string starts with a prefix (manual version):
const route = "/api/users";
console.log(route.slice(0, 4) === "/api"); // true
// (Or use the built-in: route.startsWith("/api"))
Key Takeaways
- Prefer
slice(start, end): start inclusive, end exclusive, supports negative indices. substring()doesn't support negative indices;substr()uses length as second param and is deprecated.- Negative indices count from end:
s.slice(-3)returns the last 3 characters. - Out-of-range indices never throw — JavaScript clips them silently.
- Combine with
indexOf()orlastIndexOf()for dynamic boundaries.
What to Learn Next
With slice() solid, explore the JavaScript string methods that pair with it: indexOf() and lastIndexOf() to locate positions, startsWith() and endsWith() for prefix/suffix checks (often cleaner than slicing), and split() to break a string into an array by delimiter. In algorithm problems, slicing is key for palindrome checking, anagram detection, and sliding window patterns.
