What does Go's built-in error interface require a type to implement?
answer
- one method, nothing else
- it returns a printable string
- satisfaction is never declared
- the method name matches the type name
- structs are not required
basics
~20 serror is a predeclared interface with exactly one method, Error() string. Any type that declares that method - a struct, a named int, anything - is an error and can be returned wherever error is expected.
solid answer
~50 s`error` is not a class or a keyword: it is a predeclared interface type whose entire definition is `type error interface { Error() string }`. Satisfaction is implicit, so a type becomes an error simply by declaring a method named `Error` that takes nothing and returns a `string` - there is no `implements` clause and nothing to register. That means any named type works, not just structs; a `type exitCode int` with an `Error() string` method is a perfectly good error. Because `error` is an interface value, its zero value is `nil`, which is how a function signals success. `fmt` verbs like `%v` and `%s` call `Error()` for you when printing, and the standard library ships plenty of concrete implementations such as `*fs.PathError`, `*net.OpError` and `*strconv.NumError`, alongside the anonymous ones `errors.New` and `fmt.Errorf` hand back.
code
go · 10 linestype ValidationError struct {
Field string
Msg string
}
func (e *ValidationError) Error() string {
return e.Field + ": " + e.Msg
}
// *ValidationError may now be returned wherever an error is expected.go deeper
Be ready to write the interface from memory: one method, Error() string, no arguments. Say that satisfaction is implicit and that nil is the zero value meaning success.
Explain that the method signature must match exactly, that any named type can carry it, and that fmt calls Error() for %v - preferring it over String() when both exist.
Show design judgment: when a plain message type is enough versus a concrete type worth exporting, and why an interface this small keeps wrapping and classification as library choices rather than language rules.
Frame it as an API-surface decision. The one-method contract means every richer behaviour your teams rely on is a convention you must choose, document and hold stable across packages.
## The whole contract Go has no exception class, no `Throwable` root type and no `throws` clause. A failing operation reports failure by returning a value, and the type of that value is `error` - a *predeclared* interface, available without importing anything, whose definition is just: ```go type error interface { Error() string } ``` That is the entire contract. One method, no arguments, one `string` result. Everything else people associate with Go errors - sentinel values, wrapping, typed errors carrying fields - is built *on top of* this one-method interface by ordinary library code, not by the language. ## Satisfaction is implicit Go interfaces are satisfied structurally. You never write that a type implements `error`; the compiler checks, at every assignment or call, that the type has the required method. So this type is an error the moment the method exists: ```go type ValidationError struct { Field string Msg string } func (e *ValidationError) Error() string { return e.Field + ": " + e.Msg } ``` Nothing in that snippet mentions `error` at all. `*ValidationError` may now be returned from any function declared to return `error`, stored in an `error` variable, put in a `[]error`, sent over a `chan error` or kept in a struct field. The method signature must match *exactly*. A method named `error` (lowercase) does not count - it is a different name, and it is also unexported. `Error() (string, error)` does not count. `Error(prefix string) string` does not count. If your type "mysteriously" will not compile as an error, compare the signature character by character first. The receiver you declare decides which of `T` and `*T` carries the method, which matters when you design a custom error type; that receiver choice is a topic of its own. For the purposes of "what makes something an error", the point is only that some type ends up with the method. ## Any type, not just structs Because the requirement is a method and Go lets you declare methods on any named type in your package, an error does not have to be a struct: ```go type exitCode int func (c exitCode) Error() string { return "exit status " + strconv.Itoa(int(c)) } ``` A named string type, a named slice type, even a named function type can be an error. This is a real design tool: when the failure is one small value, a named scalar is often a better error than a one-field struct. ## The zero value is the success signal `error` is an interface value, and the zero value of any interface is `nil`. That is why a function returning `error` costs nothing extra on the happy path: it returns `nil`, and the caller's check sees an interface with nothing in it. A `nil` error is not a special "no error" object; it is genuinely the absence of a value. One consequence to be aware of early: because an interface value carries both a type and a value, an interface that has been given a *concrete type* but a nil value is **not** `nil`. That is the classic trap when a helper is declared to return a concrete pointer type instead of `error`, and it is worth knowing exists the moment you learn what the interface is. ## How errors get printed You almost never call `Error()` yourself. `fmt` does it: for `%v`, `%s` and friends, if an operand implements `error`, `fmt` calls `Error()` and prints the result. That check takes precedence over `fmt.Stringer`, so a type declaring both `Error() string` and `String() string` will print via `Error()`. A `nil` error printed with `fmt.Println` shows `<nil>`. ## Where the implementations come from Most errors you handle are one of three shapes: - The anonymous ones from `errors.New` and `fmt.Errorf`, which return values of small unexported types whose only job is to hold a message string. - Concrete types exported by the standard library so callers can inspect them, such as `*fs.PathError` (with `Op`, `Path` and `Err` fields), `*net.OpError` and `*strconv.NumError`. - Your own types, declared exactly like `ValidationError` above. All three are the same thing to the caller: a value satisfying a one-method interface. ## Two things that follow First, satisfaction is not opt-in, so it can happen by accident. If some unrelated type gains a method called `Error` that returns a `string`, that type is now assignable to `error`, and a careless `return x` may compile where you did not intend it to. Second, `error` carries nothing but a message unless you add more. No stack trace, no error code, no cause - those come from choosing a richer concrete type or from wrapping. The interface deliberately asks for the minimum so that everything else stays a library decision.
- Is error a keyword, or a type declared somewhere you can read?It is a predeclared identifier in the universe block - an ordinary interface type available without an import, spelled `type error interface { Error() string }`. Nothing about it is compiler magic. `errors.New` and `fmt.Errorf` are equally ordinary: plain library functions that return values of small types implementing that method.
- If a type declares both Error() string and String() string, which one does fmt use for %v?`Error()`. When `fmt` formats an operand with `%v`, `%s` and similar verbs, it checks for the `error` interface before `fmt.Stringer`, so `Error()` wins. Declaring both is legal but usually a smell - pick one representation so logs and error messages cannot drift apart.
- Can a type satisfy error by accident?Yes. There is no opt-in: any type that happens to declare `Error() string` is assignable to `error`, so a method added for an unrelated reason can silently make a type usable as an error and let a mistaken `return x` compile. It is a small risk, but it is why the method name is worth reserving for real errors.
The error interface is a job description with a single duty: say in words what went wrong. Any type that can do that is hired, without applying.
saying these in an interview costs you the question
- Says error is a base struct you embed
- Thinks a type must declare that it implements error
- Names the method error() or Message() instead of Error()
- Believes only structs can be errors
- Claims returning an error unwinds the stack