urlencode() List Value Without doseq Encodes str(list) (tested)
Passing a list as a query parameter value to urllib.parse.urlencode() looks like the natural way to send ?a=1&a=2&a=3. We measured what it actually sends by default — and it isn't that.
1. The default output encodes the list's string repr, not its items
from urllib.parse import urlencode
print(urlencode({"a": [1, 2, 3]}))
a=%5B1%2C+2%2C+3%5D
Measured: no exception, and the output looks like a plausible URL-encoded query string. But decoding %5B1%2C+2%2C+3%5D gives the literal text [1, 2, 3] — urlencode() called str() on the whole list and percent-encoded that one string. A server reading this parameter gets a single value that's the Python repr of a list, not three separate values.
2. Feeding it back through parse_qs() shows exactly what a server would see
from urllib.parse import parse_qs
s = urlencode({"a": [1, 2, 3], "b": "x"})
print(s)
print(parse_qs(s))
a=%5B1%2C+2%2C+3%5D&b=x
{'a': ['[1, 2, 3]'], 'b': ['x']}
Measured: round-tripping through parse_qs() — standing in for what a typical server-side framework does with an incoming query string — confirms a comes back as a single-item list containing the string '[1, 2, 3]', not three separate 1/2/3 values. Any code expecting request.args.getlist("a")-style multi-value handling silently gets one garbled string instead.
3. Fix: pass doseq=True
s2 = urlencode({"a": [1, 2, 3], "b": "x"}, doseq=True)
print(s2)
print(parse_qs(s2))
a=1&a=2&a=3&b=x
{'a': ['1', '2', '3'], 'b': ['x']}
Measured: adding doseq=True confirmed urlencode() treating list (and tuple) values as sequences to expand into repeated key=value pairs instead of stringifying them whole. Non-sequence values like "x" are encoded exactly the same either way, so doseq=True is safe to use as the default for any dict that might contain a list value.
Frequently asked questions
Why does urlencode() turn a list value into %5B1%2C+2%2C+3%5D?
We measured urlencode() calling str() on the whole list and percent-encoding that single string by default, instead of expanding it into repeated key=value pairs.
How do I send a query parameter with multiple values using urlencode?
We verified passing doseq=True to urlencode() makes list and tuple values expand into repeated key=value pairs (a=1&a=2&a=3) as most servers expect.
Does urlencode raise an error if I pass a list without doseq=True?
No — we confirmed it runs silently and produces a syntactically valid but wrong query string containing the Python string representation of the list.
What does parse_qs() return for a urlencode() list value sent without doseq=True?
We measured parse_qs() returning a single-item list containing the literal string '[1, 2, 3]', not the three separate values '1', '2', '3' most code expects.
Is doseq=True safe to always use with urlencode()?
In our test, yes — plain string and non-sequence values encoded identically with or without doseq=True; only sequence values (lists, tuples) behaved differently.
Written with AI assistance. Measured on 2026-10-04 with Python 3.13.5's built-in urllib.parse module: ran urlencode() on a dict with a list value, with and without doseq=True, then fed both results back through parse_qs() to see what a server would actually receive. PlainHeaders is our extension; Python's urllib.parse module is built in.