When you send a struct value on a Go channel, does the receiver get a copy or the sender's original?
answer
- a send is an assignment
- copy, not reference
- how deep does the copy go?
- a slice field copies only the header
- pointer element type means handoff instead
basics
~20 sA channel send copies the value. The receiver gets its own copy of the struct, so later writes by the sender are invisible. The copy is shallow: pointer, slice and map fields still refer to the same underlying data.
solid answer
~40 sSending on a channel is an assignment, and assignment in Go copies. `ch <- d` copies `d` into the channel or straight into the receiving goroutine, and the receiver gets an independent struct; if the sender then writes `d.Title = ...`, the receiver never sees it. The copy is shallow, exactly like `b := a`: value fields such as ints, strings and nested structs are duplicated, but a slice field copies only the three-word header, so both structs still point at the same backing array, and a map, channel or pointer field is likewise shared. So the isolation you get from a send is only as deep as the element type. Sending `chan *Document` copies just the pointer, which is a handoff of ownership rather than a copy of the document.
code
go · 11 linestype Doc struct {
Title string
Body []byte
}
func produce(ch chan<- Doc, d Doc) {
ch <- d // the Doc struct is copied here
d.Title = "changed" // private to this goroutine; the receiver has its own copy
d.Body[0] = 'X' // NOT private: both copies share one backing array
}go deeper
Be ready to say plainly that a send copies the value, and to name one field type where the copy is only skin deep, such as a slice. Recall that a pointer element type copies just the pointer.
Explain the shallow-copy boundary field by field, and connect it to the memory-model guarantee that writes made before a send are visible after the receive. Be able to reason about when the pointer form is worth its ownership rule.
Show you pick the element type deliberately: value for isolation, pointer only when a measurement or a non-copyable type demands it, and say how you keep the ownership convention visible to the next reader of the code.
Own the consequence at an API boundary: whether your package's channel carries values or pointers is a contract about who may mutate what, and it is far harder to change once other teams have written against it.
## A send is an assignment Every channel operation in Go moves a **value**, and moving a value in Go means copying it — exactly like `b := a`, like passing an argument to a function, or like returning a result. `ch <- d` copies `d` into the channel's slot (or, when a receiver is already waiting, straight into that goroutine's variable), and `x := <-ch` copies out of it. There is no hidden reference, no boxing, no shared cell that both sides mutate. The channel's element type decides how many bytes move: a `chan Document` moves a whole `Document` struct, a `chan *Document` moves one pointer-sized word. This is the single most useful thing to know about channels as a beginner, because it is what makes the slogan work. If the send handed over a reference, every send would be a fresh opportunity for two goroutines to write the same memory, and channels would buy you nothing over a shared variable. ## What the copy reaches, and what it does not The copy is **shallow**, in exactly the sense that struct assignment is shallow: - `int`, `float64`, `bool`, `time.Time`, arrays such as `[16]byte`, and nested structs made only of those: fully duplicated. The two sides are now independent. - `string`: the header is copied, and because strings are immutable in Go there is nothing to alias — treat it as fully independent. - **slice** (`[]byte`, `[]string`, ...): the copy duplicates the header — pointer, length, capacity — but **both slices point at the same backing array**. `recv.Body[0] = 'X'` is visible to the sender, and if both goroutines touch it, that is unsynchronized shared memory again. - **map**, **channel**, **func**, **pointer** fields: the word is copied, the thing it refers to is shared. So the isolation a send gives you is only as deep as the element type. A `struct { ID int; Name string }` is genuinely handed over as an independent value. A `struct { Body []byte }` is not — it is a shared buffer with a copied wrapper around it. ## The ordering guarantee that rides along A send and its matching receive are a synchronization point: everything the sending goroutine wrote **before** the send is guaranteed visible to the receiving goroutine **after** the receive. That is why the handoff pattern is safe at all — the receiver does not need extra locking to read what the sender built. Note the word *before*: the guarantee covers writes that already happened, not writes the sender makes afterwards. ## When you send a pointer anyway Copying is not always what you want: - The struct is large and copying it in a hot loop shows up in a benchmark. - The receiver must mutate the value and the mutation must be observed by whoever holds it next. - The type is documented as not safe to copy because it embeds synchronization state. In all three cases you send a `*T`, and the moment you do, the semantics change completely: the channel copies the pointer, both goroutines can reach the same struct, and safety now rests on a **convention** — after the send, the sender stops touching the value. The compiler does not enforce that, which is why the pointer form is a handoff of ownership and the value form is a copy. Do not reach for the pointer form by reflex on size grounds alone. A small struct copy is a few machine words; sending a pointer usually forces the value to be heap-allocated instead of living in a stack frame, and it trades a cheap copy for an ownership rule a human has to remember. Measure with a benchmark before deciding it matters. ## What an interviewer is listening for They want to hear the word *copy*, then the qualifier *shallow*, then the consequence: sending a value isolates the two goroutines, sending a pointer does not — it transfers a responsibility. A candidate who says channels pass references has usually carried an assumption over from a language where every object argument is a reference, and everything else they say about channel safety will be built on that mistake.
- If the struct is large, is sending a pointer always the faster choice?Not automatically. Sending a pointer avoids the copy but usually forces the value onto the heap, adds garbage-collector work, and replaces a compiler-checked copy with an ownership rule people have to remember. For a small struct the copy is a handful of words and wins. Write a benchmark on the real element type before switching; if the copy does dominate, the pointer form is correct and the ownership convention comes with it.
- What does the receiver see about writes the sender made before the send?All of them. A send and its matching receive are a synchronization point in Go's memory model: everything the sending goroutine wrote before the send is visible to the receiver after the receive, with no extra locking. That is what lets a producer fully build a value and then publish it. The guarantee covers only prior writes — anything the sender writes after the send is outside it.
- Does it change anything if the channel has a buffer?Not for copy semantics. The value is copied into the buffer slot on the send and copied out on the receive, so the receiver still gets an independent struct, and slice or map fields are still shared. Buffering changes when the sender resumes, not what the receiver gets.
Sending a struct is posting a photocopy of a form; sending a pointer is posting the key to the filing cabinet the original sits in.
saying these in an interview costs you the question
- Says a channel passes a reference to the sender's variable
- Thinks the receiver observes the sender's edits made after the send
- Assumes the send deep-copies slice, map and pointer fields
- Believes copying a struct on send is always too expensive
- Cannot say what changes when the element type is a pointer