Go was designed to be simple. It achieved that by removing things. No inheritance, no exceptions, no sum types, no pattern matching, no macros worth the name. What is left is a small language that compiles quickly and produces binaries you can deploy without thinking. That is a genuine achievement. It is also why every Go program eventually reinvents the same missing features, badly, in a pkg/ directory.

Error handling is the architecture

Go does not have exceptions, so every function returns an error. Every caller checks it. Every check is four lines. The happy path is buried under a sediment of if err != nil { return fmt.Errorf("...: %w", err) }. This is not error handling. It is error accounting.

The error pathcallcallerrorwrapped errorif err != nillog and returnCallerFunctionDependencyLogging
The error pathThe happy path is the short one. Nobody remembers the short one.

Diagram: The error path. Caller connects to Function (call). Function connects to Dependency (call). Dependency connects to Function and fails (error). Function connects to Caller (wrapped error). Caller connects to Caller (if err != nil). Caller connects to Logging asynchronously (log and return).

Typed nil is a trap the language built

An interface value is a type and a value. A nil pointer stored in a non-nil interface is not nil. This is not an edge case. It is a rite of passage. You will write it, your tests may pass, and the panic will arrive in production with a stack trace that points at the line after the bug.

How a nil interface becomes not nilassign nil pointer*Buffernilnot nilnil receiverinterface != nilvar b *BufferInterfaceType WordValue WordComparisonMethod CallBug
How a nil interface becomes not nilTwo words of runtime representation, one lifetime of debugging.

Diagram: How a nil interface becomes not nil. var b *Buffer connects to Interface (assign nil pointer). Interface connects to Type Word (*Buffer). Interface connects to Value Word (nil). Type Word connects to Comparison (not nil). Value Word connects to Method Call and fails (nil receiver). Comparison connects to Bug asynchronously (interface != nil).

No sum types, so everything is a struct with flags

An enum is iota plus a comment. A variant is a struct with fields that are only valid in some states. The type system does not know which state you are in, so the switch is not exhaustive and the zero value is always reachable. Every state machine is a map[string]any with a dream.

Generics arrived late and tired

Go 1.18 added type parameters. It did not add a way to abstract over them the way the rest of the ecosystem expects. Constraints are interfaces, methods cannot introduce their own type parameters, and generic code is often slower to read than the duplicated code it replaced. The standard library itself took years to use the feature.

panic is a design pattern

panic is for programmer errors, until it is for HTTP handlers, startup checks and anything a library author did not want to return. recover exists. It is used in a middleware. That is the whole error strategy.

Go is a fine language for network services written by large teams that need to agree on nothing. It is not a language for expressing ideas. It is a language for ending the meeting.