In Go's net/http, what does Header.Get return when a request carries the same field twice?
answer
- one map key, several lines
- Get answers about the first line only
- there is a plural sibling method
- Add appends where Set replaces
- the parser neither joins nor splits commas
basics
~20 sHeader.Get returns only the first value. Go's http.Header maps each canonical field name to a slice with one entry per header line, so Header.Values gives every value in arrival order, Add appends a line and Set replaces them all.
solid answer
~40 s`http.Header` is `map[string][]string` and the parser appends one slice entry per header line, so two `X-Trace` lines give `["a", "b"]`. `Header.Get("X-Trace")` is defined as the first entry, or `""` when the key is absent — exactly right for single-valued fields and quietly wrong for repeatable ones. `Header.Values("X-Trace")` returns the whole slice in arrival order. On the write side `Add` appends a value, `Set` replaces the slice with a single value, and `Del` drops the key. The subtlety worth naming: Go never merges repeated lines into one comma-joined string, and never splits a comma-joined line into separate entries. A field defined as a list can legitimately arrive either way, so code that must see every element flattens both shapes itself.
code
go · 8 linesh := http.Header{}
h.Add("X-Trace", "a")
h.Add("X-Trace", "b")
fmt.Println(h.Get("X-Trace")) // a
fmt.Println(h.Values("X-Trace")) // [a b]
h.Set("X-Trace", "c")
fmt.Println(h.Values("X-Trace")) // [c]go deeper
Know that a header name maps to a slice of values, that Get hands you the first one, and that Add and Set are not interchangeable when you are writing a field out.
Explain the full method set and its semantics, and be able to say what happens to a field sent twice versus a field sent once with commas in it, because Go treats those as different.
Demonstrate the flattening helper and Clone discipline: one place that normalises repeated and comma-joined shapes, and a deep copy whenever inbound headers become outbound ones.
Set the platform rule for fields your own services invent: single-valued or repeatable, decided once and documented, so no consumer has to guess whether Get is sufficient.
## One key, several values `http.Header` is `map[string][]string`. The value is a slice because HTTP lets the same field appear on more than one line of a request, and Go preserves that faithfully: each line the parser reads is appended, in arrival order, to the slice under that field's canonical key. Two `X-Trace` lines carrying `a` then `b` produce the entry `"X-Trace": {"a", "b"}`. ## The five methods and what each one means - **`Get(name)`** returns the **first** value, or `""` if the key is absent. It cannot distinguish an absent field from one present with an empty value, and it silently ignores every value after the first. - **`Values(name)`** returns the whole slice in arrival order, or `nil` if absent. This is the method to reach for whenever a field may legitimately repeat. - **`Add(name, value)`** appends one value, keeping anything already there. - **`Set(name, value)`** replaces the entire slice with a single value. - **`Del(name)`** removes the key outright. All five canonicalize the name you pass, so casing at the call site does not matter. ## Go neither joins nor splits This is the part candidates get wrong in both directions. A client may send a list-valued field as **two lines**, or as **one line with commas**. Go stores what it received, unchanged: - two lines give a two-entry slice; - one comma-joined line gives a **one-entry** slice whose single string still contains the commas. The parser does not helpfully merge the two lines into `"a,b"`, and `Values` does not helpfully split `"gzip, br"` into two entries. If your code must reason about the individual elements of a list-valued field, you flatten both shapes yourself: range over `Values`, split each entry on commas, and `strings.TrimSpace` the pieces. Writing that flattening once, in a small helper, is far better than sprinkling `strings.Split(h.Get(name), ",")` around, which loses every line after the first. ## The classic bug for _, v := range values { h.Set("X-Trace", v) // wrong: keeps only the last } `Set` is a replace. The loop above ends with one value. `Add` is the append, and it is what you want when emitting a field that is allowed to repeat. The mirror-image bug is reading with `Get` a field the client is allowed to send twice, and then wondering why one of the two values never has any effect. ## Copying a Header `Header.Clone()` deep-copies the map **and** each value slice, returning `nil` for a nil receiver. A plain map assignment (`h2 := h`) copies the map reference, and a naive `for k, v := range h { h2[k] = v }` copies the map but shares each `[]string`. Either way a later `Add` on one header can be visible through the other, or can silently overwrite spare capacity in a slice someone else is holding. When you are building an outbound request from an inbound one, `Clone` and then mutate. ## Presence versus emptiness Because `Get` returns `""` in both cases, distinguishing "the field was not sent" from "the field was sent empty" needs the comma-ok map form on the canonical key, or a `len(h.Values(name)) > 0` check. This matters for fields where the empty value is meaningful, and it is a good sign of a candidate who has actually read the type rather than only used it. ## Ordering Within one field name, the slice preserves the order the lines arrived in — that is guaranteed and is why `Get` returning "the first" is well defined. **Across** field names there is no order at all: `Header` is a map, and ranging over it yields randomised key order. Any code that needs deterministic output, a canonical log line or a signature over headers must collect and sort the keys itself.
- A list-valued field arrives as a single line, `gzip, br`. What does Header.Values give you?A one-entry slice holding the whole line, `["gzip, br"]`. Go stores lines, not list elements, so if you need the elements you split on commas and `strings.TrimSpace` them yourself. Send the same field on two lines instead and you get two entries. Robust code flattens both shapes through one helper rather than assuming either.
- How do you copy a request's headers so the copy and the original do not share slices?Use `r.Header.Clone()`, which deep-copies the map and every value slice and returns `nil` for a nil Header. A plain map assignment shares the map itself, and a `for k, v := range` copy shares each `[]string`, so a later `Add` through one reference can surprise the holder of the other.
- How do you tell a field that was not sent from one sent with an empty value?`Get` returns `""` for both, so use the comma-ok form on the canonical key — `v, ok := h[http.CanonicalHeaderKey(name)]` — or check `len(h.Values(name)) > 0`. This only matters for fields where an empty value carries meaning, but it is the difference between a correct check and a guess.
saying these in an interview costs you the question
- Says Header.Get joins repeated values with commas
- Uses Set inside a loop and keeps only the last value
- Assumes a list-valued field always arrives on one line
- Thinks Header.Values splits a comma-separated line
- Copies a Header with a plain map assignment