String.prototype.slice()

The one to reach for by default. JavaScript has three substring methods; this is the only one that handles negative indices, and the only one whose name matches the array method that behaves the same way.

String methodES3 (1999)Live demo
Common call
s.slice(0, 10)
Returns
a new string; end is exclusive
Replaces
substring and the deprecated substr
Watch out
a backwards range gives "", it does not swap the arguments
string.slice(start[, end])
→ string

Demo

Live evaluation
Try:
Inputs
sstringthe source string
startnumberstart index
endnumberend index (exclusive)
Output
'abcdef'.slice(1, 3)
'bc'

The end index is exclusive, so slice(1, 3) gives two characters, not three — the same convention as every other range in the language. Negative indices count from the right, which is what makes slice(0, -1) the idiomatic way to drop the last character. The backwards case is the important contrast with substring: given a start after the end, slice returns an empty string, while substring silently swaps the two arguments and returns a result. Indices past the end are clamped rather than throwing.

Parameters

NameTypeRequiredDescription
startnumberyesIndex to start at. Negative counts back from the end, so -3 is the third character from the right.
endnumberno (length)Index to stop BEFORE. Also accepts negatives. Omitted, the slice runs to the end of the string.

Return value

string — The characters from start up to but NOT including end. An empty string when the range is backwards or out of bounds — it never throws.

Common patterns

Truncate to a maximum length
Out-of-range is clamped, so no length check is needed.
const short = title.slice(0, 80);
Drop the last character
The classic negative-end use.
const withoutComma = line.slice(0, -1);
Take the file extension
Negative start, counted from the right.
const ext = name.slice(name.lastIndexOf('.') + 1);

Examples

1. A middle range
'abcdef'.slice(1, 3)
Returns
'bc'
2. To the end
'abcdef'.slice(2)
Returns
'cdef'
3. Last three
'abcdef'.slice(-3)
Returns
'def'
4. Drop the last
'abcdef'.slice(0, -1)
Returns
'abcde'
5. Backwards is empty
'abcdef'.slice(3, 1)
Returns
''
6. substring swaps
'abcdef'.substring(3, 1)
Returns
'bc'

Pitfalls

1. A backwards range returns an empty string
No error, no swap — just "". When the indices come from a computation this shows up as a mysteriously blank value rather than an exception, so a start that has drifted past the end is easy to miss.
Silently empty
'abcdef'.slice(3, 1)
''
Order them
'abcdef'.slice(Math.min(a, b), Math.max(a, b))
'bc'
2. Confusing it with substring
They differ on exactly two things: substring swaps a backwards range instead of returning empty, and substring treats every negative index as 0. Mixing them up produces results that look plausible on well-formed input and wrong on edge cases.
Negative ignored
'abcdef'.substring(-3)
'abcdef' // the whole string
slice counts back
'abcdef'.slice(-3)
'def'
3. It cuts by code unit, not character
Slicing through an emoji or other astral character leaves a lone surrogate — a broken half-character that renders as a replacement glyph. Truncating user text to a fixed length is exactly where this bites.
Splits a pair
'\u{1F600}ab'.slice(0, 1).length
1 // half an emoji
Slice code points
[...'\u{1F600}ab'].slice(0, 1).join('')
'\u{1F600}'
4. It returns a new string and changes nothing
As with every string method. Strings are immutable, so a bare slice call is a no-op — the result has to be assigned.
Discarded
let s = 'abc';
s.slice(1);
s
'abc'
Assign it
let s = 'abc';
s = s.slice(1);
s
'bc'

When to use

Use it
  • Any substring extraction — this is the sensible default
  • Taking characters from the end with a negative index
  • Truncating to a maximum length without a bounds check
  • Matching the behaviour of Array.prototype.slice
Reach for something else
  • You want the arguments auto-ordered → substring, though that is rarely a feature
  • You want a length rather than an end index → the deprecated substr, or slice(i, i + n)
  • The text may contain emoji and you are truncating → spread to an array first
  • You want to split on a delimiter → split

Notes

Complexity
O(n) in the length of the slice
Return
A new string; the original is untouched
CPython impl
V8: Builtins-string-slice
Memory
Engines often use a sliced-string representation that shares the original buffer
Thread-safe
Single-threaded; the string is only read

FAQ

slice, almost always. substring differs only in swapping backwards ranges and clamping negatives to zero, neither of which is usually wanted. substr takes a LENGTH rather than an end index and is deprecated Annex B — it still works everywhere but should not appear in new code.

'abcdef'.slice(1, 3);       // 'bc'
'abcdef'.substring(1, 3);   // 'bc'  — same here
'abcdef'.substr(1, 3);      // 'bcd' — length, not end

History

ES3
slice added alongside substring, adopting the negative-index behaviour of the array method.