Why did a hand edit to a stringer-generated Go file vanish, and where should that code live?
answer
- the generator writes the whole file
- read the first line of that file
- methods need not share a file
- ask what the source of truth is
- a compile-time guard catches reordered constants
basics
~20 sGenerators rewrite their output file whole, so any hand edit is overwritten the next time go generate runs. That is what the DO NOT EDIT header warns about. Put custom code in a separate file in the same package, or change the source the generator reads.
solid answer
~50 sA generator like `stringer` does not patch its output — it regenerates `status_string.go` from scratch from the constant declarations, so anything typed into that file is gone on the next `go generate`. That is exactly what the conventional header `// Code generated by "stringer -type=Status"; DO NOT EDIT.` is announcing, and tooling across the ecosystem matches on that line to skip such files. The fix is to decide what the source of truth is. If the change belongs to the generated behaviour, change the input — the `const` block, or the generator's flags — and regenerate. If it is genuinely hand-written behaviour, put it in a *different* file in the same package: Go lets methods on a type live in any file of the package, so a hand-written helper sits happily beside the generated `String()`. The generated file is still committed and still reviewed like any other source, because `go build` will never recreate it.
code
go · 7 lines// status.go — hand written, safe to edit
func (s Status) Valid() bool {
return s >= Pending && s <= Closed
}
// status_string.go — first line, written by the generator
// Code generated by "stringer -type=Status"; DO NOT EDIT.go deeper
Recall that a generator rewrites its whole output file, that the DO NOT EDIT header means it literally, and that your own code goes in a different file in the same package.
Explain the source-of-truth reasoning: change the input or the generator flags, keep hand-written methods in their own file, and describe how a generated file drifts from the declarations it was built from.
Show how you catch staleness — the compile-time guard some generators emit, what it does not catch when a constant is merely appended, and how you review a generated diff meaningfully rather than rubber-stamping it.
Own the policy: which parts of a codebase are allowed to be generated, who is accountable when generated output and source disagree, and when repeated patching of output means the generator should change or go.
## What actually happened Generators in the Go ecosystem are whole-file writers. `stringer -type=Status` reads the type and its `iota` constants, computes a name table, and writes `status_string.go` from scratch, truncating whatever was there. There is no merge, no protected region, no "custom code here" block. So the developer who added a method or tweaked a string in that file did not have their change rejected — it was simply overwritten, silently, the next time anyone ran `go generate ./...`. ## The header is a contract Generated files conventionally open with a line of the form: ```go // Code generated by "stringer -type=Status"; DO NOT EDIT. ``` This is a recognised convention, not decoration. The shape is fixed — the words `Code generated`, then a description, then `DO NOT EDIT.` at the end of the line — precisely so that tools can detect generated files mechanically and treat them differently: skipping them in reviews, collapsing them in diffs, excluding them from certain checks. If you write your own generator, emitting that exact line is part of doing it properly. ## Where the code should have gone There are three honest destinations, and picking the right one is the actual skill: 1. **Change the input and regenerate.** If a constant should have a different printed name, that is a fact about the constants, not about the generated file. Some generators accept flags for this — `stringer` has `-linecomment`, which makes it use a trailing line comment on each constant as the printed name, so `Active Status = iota // running` prints `running`. Change the source, run `go generate`, commit both. 2. **Put hand-written code in a separate file in the same package.** Go has no rule that a type's methods live in one file. `status.go` can hold your `Valid()` method and `status_string.go` can hold the generated `String()`. Nothing collides, and the generator can rewrite its file freely. This is the standard answer for "I need one extra method on a generated type". 3. **Change the generator, or stop generating.** If you keep needing to patch the output, the generator is the wrong shape for the problem. Either it grows a flag, or that type stops being generated. Fighting a generator by editing its output is a slow leak. ## The drift problem behind the question The deeper issue is that the generated file is a *cache* of a computation over the source, but it is stored in the repository, where nothing forces the two to stay in step. If someone edits the `const` block and forgets to regenerate, the committed file is now a lie, and the compiler is generally happy to compile a lie. Generators mitigate this where they can. `stringer` emits a small compile-time guard function alongside the name table: ```go func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} _ = x[Pending-0] _ = x[Active-1] _ = x[Closed-2] } ``` If someone reorders the constants or inserts one in the middle, the indices no longer line up, the array index goes negative, and the **build breaks** with a pointed message. That is a deliberate, cheap trick: turn a silent staleness into a compile error. Note what it does **not** catch. Appending a new constant at the end changes no existing value, so the guard still compiles — and `String()` on the new value falls back to something like `Status(3)` instead of its name. That is the classic silent drift for this generator, and it is worth naming in an interview because it shows you know the guard's limits rather than just its existence. ## Why the file is committed at all Because the build never regenerates. Anyone who fetches the module and runs `go build` compiles what is in the repository; they may not have `stringer` installed, and the toolchain would not run it if they did. So the generated file is a first-class source file: it is committed, it is reviewed, and its diff is part of the change. Treat a surprising generated diff the way you would treat a surprising hand-written one — it usually means the input changed in a way nobody described in the commit message. ## How to answer the chair's question The developer whose edit vanished is asking "where do I put my change?" The answer is a question back: *what is the source of truth for this behaviour?* If it is the constants, edit them. If it is hand-written logic, it belongs in a hand-written file. The generated file is owned by the generator, and the only edit that survives there is the one made by re-running it.
- Someone appends a new constant to the const block and forgets to regenerate. What breaks, and when?Usually nothing at compile time. stringer's generated guard function only checks the values it already knew about, so appending at the end still compiles, and `String()` on the new value falls back to a numeric form like `Status(3)`. Reordering or inserting a constant in the middle does shift the checked values and breaks the build with an invalid-array-index error — a deliberate staleness alarm.
- Should generated files be reviewed in a pull request, or skipped?They are compiled code, so they are reviewed — but the review question is different. You are checking that the diff is consistent with the input change and the generator invocation, not reading it line by line. An unexplained generated diff usually means the input moved, or someone ran a different version of the generator, and both are worth a comment.
- Where do you put an extra method on a type whose String method is generated?In a separate, hand-written file in the same package. Go places no restriction on which file a type's methods are declared in, so the generated file can be rewritten freely while your method survives. Adding it below the DO NOT EDIT header instead is the mistake that started this whole question.
It is like editing a printed report instead of the spreadsheet behind it. The next print run comes out of the spreadsheet, and your pen marks are nowhere.
saying these in an interview costs you the question
- Adds hand-written methods below the DO NOT EDIT header
- Thinks the generated file is rebuilt during go build
- Deletes generated files, expecting the compiler to recreate them
- Treats the generated diff as noise not worth reviewing
- Patches the output instead of changing the constants or the flags