When an *os.File is assigned to an io.Writer variable, what does that interface variable hold?
answer
- two words, not one
- the value plus something else
- one word says which concrete type
- the other word points at the data
- that is how %T still knows
basics
~20 sA pair of words: the dynamic type *os.File, plus a data word pointing at the file. The variable is not a copy of the file, and it carries the concrete type along, which is why %T prints *os.File.
solid answer
~40 sAn interface value in Go is two machine words wide, not one. The first word identifies the dynamic type: for a method-bearing interface like `io.Writer` it points at an itab, the table that pairs `io.Writer` with `*os.File` and holds the addresses of that type's methods. The second word is the data pointer, here the `*os.File` value itself. Nothing about the file is copied and nothing is stamped onto `os.File` at declaration time; the pairing is made at the assignment. Because the concrete type travels inside the value, `fmt.Printf("%T", w)` prints `*os.File`, a type assertion can get the `*os.File` back out, and `unsafe.Sizeof(w)` is 16 on a 64-bit build no matter how large the underlying value is.
code
go · 6 linesvar w io.Writer = os.Stdout // (itab for io.Writer/*os.File, os.Stdout)
var c io.Closer = os.Stdout // (itab for io.Closer/*os.File, os.Stdout)
fmt.Printf("%T\n", w) // *os.File
fmt.Println(unsafe.Sizeof(w)) // 16 on a 64-bit build
_ = cgo deeper
Be ready to say that an interface variable carries two things — which concrete type is inside, and the value itself — and to name *os.File as what %T would print for it.
Explain that the first word points at a method table built for the io.Writer/*os.File pairing and the second at the data, and that the pairing is made at the assignment rather than declared on the type.
Show why it matters in practice: the value is two words wide whatever the payload, the concrete type survives inside it for assertions and logging, and non-pointer values get copied on conversion.
Frame it as an API question. Handing back an interface hands the caller a type-and-value pair whose dynamic type they can inspect and come to rely on, so what you box is part of the contract you are publishing.
### The two-word value A variable whose static type is an interface — `var w io.Writer` — does not hold the concrete value the way a `*os.File` variable does. It holds a **pair**: 1. a pointer that says **which concrete type is in here**, and 2. a pointer to **the data**. On a 64-bit build that is 16 bytes, whatever is inside; `unsafe.Sizeof(w)` reports 16 for any interface variable. This is the single most useful fact about interfaces in Go, because almost every surprising interface behaviour — printing, assertions, comparison with nil, the cost of conversion — falls out of it. ### What goes in the first word For an interface that declares methods, such as `io.Writer`, the first word points at an **itab** (interface table). An itab is built for one *pair*: the interface type on one side, the concrete type on the other. The itab for (`io.Writer`, `*os.File`) records that the concrete type is `*os.File` and holds the address of the `Write` method that `*os.File` provides. A call `w.Write(p)` becomes an indirect call through a fixed slot in that table — position, not name lookup. For the empty interface, spelled `any` or `interface{}`, there are no methods to dispatch, so the first word points straight at the concrete type's descriptor with no method table in between. ### What goes in the second word The data word is a pointer. When you assign `os.Stdout` — itself a `*os.File` — the pointer goes into the data word unchanged, so the interface refers to the same file object the rest of the program is using. Write through `w`, and the same file is written. If the value being assigned is *not* pointer-shaped (an `int`, a `string`, a struct value), the data word cannot hold it, so the conversion copies the value into storage the pointer can refer to. That copy is why mutating the original variable afterwards does not change what the interface holds. ### Why nothing is declared There is no registration step. `os.File` does not mention `io.Writer` anywhere; the compiler checks at the assignment that `*os.File` has the methods `io.Writer` needs, and emits the itab for that pair. The same `*os.File` can be assigned to an `io.Writer` variable and an `io.Closer` variable at the same time: the data word is identical in both, and only the first word differs, because each interface needs its own pairing. ### Consequences you will actually use - **`%T` prints the concrete type.** `fmt.Printf("%T\n", w)` prints `*os.File`, not `io.Writer`, because the dynamic type is inside the value. `%v` formats the value the data word refers to. - **The concrete type is recoverable.** A type assertion or a type switch reads the first word and hands the original `*os.File` back. - **Size is constant.** Passing an interface to a function copies 16 bytes, regardless of the payload behind the data pointer. - **The zero value is a true nil.** `var w io.Writer` has both words unset; that state, and only that state, compares equal to `nil`. Calling a method on it panics with a nil pointer dereference, because there is no method table to dispatch through. ### The mental model Think of an interface value as a labelled reference: the label names the concrete type (and, for a non-empty interface, points at the method table for this interface/type pairing), and the reference points at the value. Everything else about interfaces in Go follows from the fact that both parts are there, and that they can be set independently.
- Does assigning a large struct value to an interface copy it?Yes. The data word has to point at something, so the struct is copied into storage the interface refers to. Mutating the original variable afterwards does not change what the interface holds, which is a common surprise when people expect interface assignment to behave like taking a reference.
- Can two interface variables of different interface types hold the same concrete value?Yes. `var w io.Writer = f` and `var c io.Closer = f` both put the same `*os.File` in their data word. Only the first word differs, because each interface type needs its own pairing with `*os.File`. The value is shared; the method tables are not.
- What does an io.Writer variable hold before anything is assigned to it?Both words are unset — no dynamic type and no data. That is the state that compares equal to `nil`. Calling `Write` on it panics with a nil pointer dereference, because there is no method table for the call to dispatch through.
A parcel with a shipping label. The label says what is inside and the box holds it, which is why the sorting office can route the parcel without ever opening the box.
saying these in an interview costs you the question
- Says an interface variable stores only a pointer to the value
- Thinks the concrete type is lost once the value is assigned to an interface
- Assumes the assignment wraps the file in some new object
- Thinks %T on an interface variable prints the interface type
- Confuses the interface's size with the size of the concrete value