JavaScript gives you two encoding functions, encodeURI and encodeURIComponent, and it's tempting to assume encodeURIComponent is simply the safe default for everything. In most cases that assumption holds. But there's one extremely common scenario in web development — encoding a single query parameter value — where using encodeURI instead breaks the URL's structure outright. This article shows exactly how that breakage happens and where the safe boundary actually sits.

What encodeURI Leaves Unescaped That encodeURIComponent Doesn't

Both functions leave letters, digits, and - _ . ! ~ * ' ( ) untouched. That part is identical. The difference is in everything else — specifically ; / ? : @ & = + $ , #. encodeURI passes these straight through, while encodeURIComponent percent-encodes every one of them.

encodeURI('a=1&b=2 3')          // "a=1&b=2%203"
encodeURIComponent('a=1&b=2 3') // "a%3D1%26b%3D2%203"

Notice that encodeURI only converts the space to %20 and leaves = and & exactly as they are. That's working as designed: encodeURI is built for the case where you pass it an entire URI — scheme, host, path, and query string all together — and characters like = ? & # are part of that URI's own structural grammar, so it deliberately leaves them alone.

Why Encoding "Just One Value" Breaks

The problem is that what a web app actually needs to encode is almost never a whole URI — it's a single query parameter value. Say a user types C++ & Java into a search box, and you mistakenly reach for encodeURI to embed it as the q parameter.

const value = 'C++ & Java';

encodeURI(value)          // "C++%20&%20Java"
encodeURIComponent(value) // "C%2B%2B%20%26%20Java"

The & survives untouched in the encodeURI output. Append that to https://example.com/search?q= and the parser reads the leftover & as a parameter separator, splitting one value into two parameters.

new URL('https://example.com/search?q=C++%20&%20Java').searchParams
// q       = "C   "
// " Java" = ""

q ends up truncated to C followed by trailing spaces, and Java is sliced off as a separate, valueless parameter. The + characters compound the damage: in this parsing context they're treated as literal spaces (the same convention application/x-www-form-urlencoded uses), so q's final value is just C plus a few stray spaces — nothing like the original input. Encode the same string with encodeURIComponent instead, and & and + both get percent-encoded, so no parser can mistake them for delimiters; q=C++ & Java round-trips back to the original value intact.

The Real Distinction: a Whole URI vs. a URI Component

Once you see the breakage, the decision rule is straightforward:

  • If the string you're encoding is already a complete URI — something with a scheme and path, like https://example.com/search?q=JavaencodeURI is only a candidate when you want to preserve that structure while encoding. Even then it behaves inconsistently: already-percent-encoded %xx sequences pass through fine, but any new & or = you add won't be escaped. In practice there's rarely a good reason to reach for it.
  • If the string is a piece of a URI — one path segment, one query parameter value — use encodeURIComponent. Embedding user input into a URL in a web app falls into this category almost without exception.

This is exactly what the url-encoder tool's help text means when it says turning the toggle on uses encodeURIComponent and "encodes everything, including URL symbols like slashes and question marks." Turning it off and using encodeURI instead only makes sense when the input string is already shaped like a complete URI — that assumption is easy to miss.

The Other Trap: Double Encoding

Even once you've settled on encodeURIComponent, there's a second trap waiting: encoding a string that's already been encoded.

const once = encodeURIComponent('100%');   // "100%25"
const twice = encodeURIComponent(once);    // "100%2525"

decodeURIComponent(twice);                  // "100%25" (one decode pass isn't enough)
decodeURIComponent(decodeURIComponent(twice)); // "100%" (two passes recover the original)

Because % itself encodes to %25, running an already-encoded string through encodeURIComponent a second time turns that %25 into %2525. If whatever decodes the value only decodes once, it's left with 100%25 instead of the original 100%. Systems with redirects, proxies, or API gateways in the request path tend to apply encoding at more than one layer without anyone tracking it. Unless you're deliberate about whether a value is encoded or raw at each boundary, double encoding creeps in silently.

If you want to see how a given string transforms under each function, you can switch between encode and decode with the tool below.