String.prototype.endsWith()

The suffix half of the startsWith pair. Its optional second argument is a LENGTH to pretend the string has, not an index to start from — the opposite convention from startsWith.

String methodES2015Live demo
Common call
name.endsWith('.json')
Returns
boolean
Replaces
s.slice(-x.length) === x
Watch out
second argument is a length, not a start index
string.endsWith(searchString[, endPosition])
→ boolean

Demo

Live evaluation
Try:
Inputs
sstringthe string to test
searchstringthe suffix
Output
'report.json'.endsWith('.json')
true

Checking an extension is the canonical use, and the second case shows why it beats includes: 'report.jsonx' CONTAINS '.json' but does not end with it, so a contains-check would wrongly accept it. Uppercase filenames are a real-world hazard — extensions are conventionally case-insensitive, so lowercase before testing. And as with the other two membership methods, an empty suffix is always present.

Parameters

NameTypeRequiredDescription
searchStringstringyesThe suffix to test for. A RegExp is a TypeError, as with startsWith and includes.
endPositionnumberno (length)Treat the string as if it ended here — as if it had been truncated to this many characters. Not an index to search from.

Return value

boolean — True if the string ends with the search text — or would, if it were truncated to endPosition characters.

Common patterns

Check a file extension
Lowercase first; extensions are not case sensitive.
const isJson = name.toLowerCase().endsWith('.json');
Normalise a trailing slash
Reads better than comparing the last character.
const base = url.endsWith('/') ? url.slice(0, -1) : url;
Match any of several suffixes
A list plus some.
const isImage = ['.png', '.jpg'].some(e => name.endsWith(e));

Examples

1. Matching suffix
'hello'.endsWith('lo')
Returns
true
2. Not a suffix
'hello'.endsWith('ell')
Returns
false
3. Truncated first
'hello'.endsWith('ell', 4)
Returns
true
4. Empty is true
'hello'.endsWith('')
Returns
true
5. Contains is looser
'report.jsonx'.includes('.json')
Returns
true
6. endsWith is not
'report.jsonx'.endsWith('.json')
Returns
false

Pitfalls

1. The second argument is a length, not a start index
startsWith takes a position to anchor AT; endsWith takes a length to pretend the string HAS. endsWith("ell", 4) asks whether "hell" ends with "ell" — true. Reading it as "search from index 4" gives exactly the wrong mental model.
Misread as an offset
'hello'.endsWith('ell', 4)
true // tests 'hell'
Plain call
'hello'.endsWith('ell')
false
2. Case sensitive, and extensions usually are not
A file called REPORT.JSON is a JSON file. Uploads from Windows and older cameras routinely arrive uppercase, so an extension check without lowercasing rejects valid files.
Rejects it
'REPORT.JSON'.endsWith('.json')
false
Fold first
'REPORT.JSON'.toLowerCase().endsWith('.json')
true
3. An extension check is not a content check
The name says nothing about the bytes. For uploads this matters — a file called photo.png may contain anything at all, so validate the actual content type rather than trusting the suffix.
Name only
file.name.endsWith('.png')
true for any content
Check the type
file.type === 'image/png'
reported MIME type
4. A RegExp argument throws
As with startsWith and includes. Use an anchored pattern with test when a real pattern is needed.
Throws
'abc'.endsWith(/c/)
TypeError: First argument to String.prototype.endsWith must not be a regular expression
Anchored test
/c$/.test('abc')
true

When to use

Use it
  • File extension checks, after lowercasing
  • Trailing slash and delimiter normalisation
  • Detecting a suffix marker on a key or identifier
  • Anywhere slice(-n) === x appears
Reach for something else
  • The match may be anywhere → includes
  • You are testing the beginning → startsWith
  • You need a pattern → an anchored RegExp with test
  • Validating upload content → check the MIME type or the bytes

Notes

Complexity
O(m) in the length of the suffix
Return
A boolean; nothing is allocated
CPython impl
V8: Builtins-string-endswith
Memory
No allocation
Thread-safe
Single-threaded; the string is only read

FAQ

Because it is defined as "pretend the string ends here", which makes endsWith(x, n) equivalent to s.slice(0, n).endsWith(x). It mirrors startsWith in purpose but not in convention, which is a genuine wart — startsWith takes an index to anchor at.

'hello'.endsWith('ell', 4);        // true
'hello'.slice(0, 4).endsWith('ell'); // true — the same thing

History

ES2015
Added with includes and startsWith.