Repeated query-string keys have no agreed meaning
The part of a URL after the question mark looks like a dictionary, and almost every language hands it to you as one. It is not a dictionary. It is a string, with a widely followed convention laid over it, and the places where the convention runs out are where the surprises live.
What the specification actually says
Less than you would hope. The standard for URLs describes the query as the characters between the ? and any #, and leaves the interpretation to whatever is receiving it. The familiar shape — pairs joined by &, key and value split by =, percent-escapes decoded — comes from HTML form submission, not from the URL standard. Everything follows it, and nothing is obliged to.
That is why the edges differ. Some stacks accept ; as a pair separator as well as &. Some treat a bare + as a space, following the form convention; a strict reading of the URL standard says a plus in a query is just a plus. Some accept a key with no = at all. None of these are bugs; they are different readings of a convention.
The repeated key
Now the interesting one: ?a=1&a=2. There is no correct answer, and real frameworks pick every available option. Some give you the first value. Some give you the last. Some give you a list of both. Some give you the string 1,2. PHP historically wanted a[]=1&a[]=2 before it would build a list at all, and plain a=1&a=2 gets you the last value there.
The consequences are worth being concrete about. If one component reads the first value and another reads the last, the two are looking at different requests while parsing the same bytes — a proxy, a cache key and an application can disagree about what was asked for. And a parser that returns a single value where your code expected a list, or a list where it expected a string, produces a type confusion that only shows up when someone sends the duplicate.
The practical position is to be explicit rather than clever. Decide on one shape for lists — repeated keys, or a bracketed key, or a single delimited value — document it, and have the receiving code reject anything that does not match instead of silently taking one of the values. When a key that should appear once appears twice, that is worth treating as a bad request, not as a puzzle to solve.
Why you cannot split after decoding
This is the part that turns a parsing detail into a correctness problem. Consider ?note=a%26b%3Dc&page=2. The value of note is the six characters a&b=c, with the ampersand and the equals sign escaped precisely so they are read as data.
Decode first, and the string becomes note=a&b=c&page=2. Split that on & and you get three pairs instead of two, with a key b that the sender never wrote. The escapes existed to prevent exactly this, and decoding early threw that information away before the structure was read.
So the order is fixed and there is only one of it: split on &, split each pair on the first =, then decode the key and the value separately. Note "the first" — a value is allowed to contain a raw =, so splitting on every equals sign rather than the first one truncates values that happen to contain one.
What this tool does, exactly
Worth stating, since the whole point is that implementations differ. It takes everything after the last ?, splits on &, skips empty pairs, splits each pair on its first =, treats a pair with no = as a key with an empty value, converts + to a space, and then percent-decodes. Repeated keys are printed as repeated lines rather than merged, because merging them would mean choosing one of the answers above on your behalf.
So ?a=1&a=2 lists a = 1 and a = 2, both of them. ?note=a%26b%3Dc&page=2 lists note = a&b=c and page = 2, two pairs, correctly. ?q=gg+network&sort=new lists q = gg network and sort = new, the plus having become a space. And ?flag&x= lists flag = and x = , with nothing to the right of either equals sign.
The tool
The Query String Parser takes a full URL or just the query part and prints the pairs one per line, decoded, in your browser with nothing uploaded. Its job is to show you what a query string actually contains, including the duplicates and the empty values that a language's built-in parser may have already resolved for you before your code saw them. When a request is behaving strangely, seeing the raw pairs is usually the quickest way to find out which convention someone else assumed.