Lesson 79 of 80
Building a query string with template literals and manual encodeURIComponent calls is how you end up with a subtle escaping bug. The platform already parses and builds URLs correctly.
12 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
URL parses a URL string into its parts — protocol, host, path, query, hash — as live properties you can read and write. URLSearchParams does the same for just the query string, and it knows how to encode and decode values correctly, which is the part people get wrong by hand. Together they replace string concatenation and regex parsing for a job the browser (and Node) already does correctly.
const url = new URL("https://example.com/search");
url.searchParams.set("q", "react hooks & closures");
url.searchParams.set("page", "2");
url.searchParams.append("tag", "js");
url.searchParams.append("tag", "react");
url.toString();
// "https://example.com/search?q=react+hooks+%26+closures&page=2&tag=js&tag=react"
set replaces a key's value (or creates it); append adds another value under the same key without removing existing ones — that's how you correctly represent ?tag=js&tag=react as a repeatable parameter, something naive object-to-query-string code usually mishandles. Notice & in the search term became %26 automatically — URLSearchParams percent-encodes reserved characters for you, so a user typing & or # into a search box can't accidentally break your URL structure.
Reading is the mirror image — parse a URL, and everything is already split out:
const parsed = new URL("https://example.com/search?q=hooks&page=2&tag=js&tag=react");
parsed.searchParams.get("q"); // "hooks" — first value only
parsed.searchParams.getAll("tag"); // ["js", "react"] — all values for a repeated key
parsed.pathname; // "/search"
[...parsed.searchParams.keys()]; // "q", "page", "tag", "tag" — iterable, in order
URLSearchParams also works standalone, without a full URL, when you just need to build a query string from an object:
const params = new URLSearchParams({ page: "2", sort: "recent" });
params.toString(); // "page=2&sort=recent"
encodeURIComponent by hand and URLSearchParams don't always agree. URLSearchParams encodes spaces as +, not %20, because it follows application/x-www-form-urlencoded, while encodeURIComponent always uses %20. Both are valid, but mixing manual encoding with URLSearchParams in the same URL produces inconsistent, hard-to-debug output..get() only returns the first match for a repeated key. If a query string can legitimately have ?tag=js&tag=react, .get("tag") silently drops everything after the first — reach for .getAll("tag") whenever a parameter might repeat.new URL("relative/path") throws without a base. A bare relative string isn't a valid absolute URL, so the one-argument form only works for full URLs — pass new URL("relative/path", window.location.origin) (or any base URL) for anything relative.This is the same instinct as reaching for Intl instead of hand-rolling number and date formatting, or structuredClone instead of a manual object walk: the platform has already solved a problem — correct percent-encoding, correct URL component splitting — that looks simple enough to redo with string methods but has edge cases (reserved characters, repeated keys, encoding schemes) that a bespoke version reliably gets wrong.