# Go Cheat Sheet

Dense quick-reference for revision and interviews. Terms in [GLOSSARY.md](./GLOSSARY.md);
our code specifics in [repo-go-map.md](./repo-go-map.md).

## The mental model in five lines
1. **Everything is a value, copied** on assign/call/return — unless you pass a pointer.
2. Every type has a usable **zero value**; design so the zero value works.
3. **Interfaces are satisfied implicitly** — small, and declared where they're consumed.
4. **Composition (embedding), not inheritance.** No classes.
5. **Errors are values** (`if err != nil`); concurrency is **goroutines + channels**.

## Value vs pointer — the decision
| Use a pointer `*T` when… | Use a value `T` when… |
|---|---|
| you must mutate the caller's value | the value is small & immutable-ish |
| the struct is large (avoid copy) | you want an independent copy |
| you need `nil` to mean "absent" | (our rule) it's a slice/map/interface — **never** `*` those |
Repo rule: pointer receiver for mutation / large / `(nil,err)` / consistency; prefer `[]Book` over `[]*Book`.

## Zero values
```
int/float → 0        string → ""        bool → false
pointer/slice/map/chan/func/interface/error → nil        struct → all fields zeroed
```
`var mu sync.Mutex` is ready to use. `var s []int` is nil but you can `append` & `range` it. `type EmailRepo struct{}` — zero value is a working adapter.

## Slices & maps — the gotchas
- Slice = **header (ptr,len,cap)** over a backing array. Passing it copies the header, **shares the array** → mutating elements is visible to the caller.
- `append` may **reallocate** → always use the return value: `s = append(s, x)`.
- `nil` slice: fine to `range`/`append`. `nil` map: fine to **read** (zero value), **panics on write** → `make(map...)` first.
- Map **iteration order is random** — never rely on it (sort keys if you need order).
- `s2 := s[1:3]` shares `s`'s array — a classic aliasing bug source.

## Interfaces & generics
```go
type Reader interface { Read(p []byte) (int, error) }   // implicit satisfaction
// small, consumer-side; accept interfaces, return structs

func Map[T, R any](in []T, f func(T) R) []R { ... }      // type parameter
func Contains[T comparable](s []T, v T) bool { ... }     // constraint
```
**Nil-interface gotcha**: an interface holding a `(*T)(nil)` is **not** `== nil`. Return `error` as a concrete `nil`, not a typed nil pointer.

## Errors
```go
return fmt.Errorf("svc.Create: %w", err)   // %w wraps → errors.Is/As can unwrap
var ErrNotFound = errors.New("not found")   // sentinel
errors.Is(err, ErrNotFound)                 // compare
var pe *PathError; errors.As(err, &pe)      // extract typed
```
⚠️ `%v` formats but **breaks** the unwrap chain (a real inconsistency in our repo). gRPC: map to `codes.*` **only at the controller** (`InvalidArgument`/`Internal`/`ResourceExhausted`/`NotFound`/`PermissionDenied`).

## Concurrency
```go
go f(x)                              // launch goroutine (pass args, don't capture)
ch := make(chan int, 8)              // buffered channel
select { case v := <-ch: ...; case <-ctx.Done(): return }   // multiplex + cancel
var wg sync.WaitGroup; wg.Add(1); go func(){ defer wg.Done(); ... }(); wg.Wait()
var once sync.Once; once.Do(setup)   // run exactly once
```
"Share memory by communicating." Detect races with `go test -race`. Happens-before rules = the [memory model](https://go.dev/ref/mem).

## Context
```go
ctx, cancel := context.WithTimeout(ctx, 15*time.Second); defer cancel()
ctx, cancel := context.WithCancel(ctx)
```
First arg by convention. Carries cancellation, deadline, request-scoped values. Repo: claims live in ctx via private-int keys; propagated into Kafka via message headers.

## defer / panic
- `defer` runs at function return, **LIFO**. Great for `Close()`/`cancel()`/`span.End()`.
- `defer` args evaluate **when deferred**, not when run.
- `panic`/`recover` = exceptional only; `recover` works only inside a deferred func.

## Testing (repo style)
```go
func TestX(t *testing.T) {
  t.Parallel()
  ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second); defer cancel()
  db := new(mock_database.Ext); repo := new(mock_repo.MockEmailRepo)
  repo.On("UpsertEmail", ctx, db, mock.Anything).Once().Return(nil)   // arrange
  err := handler.Create(ctx, payload)                                 // act
  assert.NoError(t, err)                                              // assert
  mock.AssertExpectationsForObjects(t, repo)
}
```
Table-driven: `for _, tc := range cases { t.Run(tc.name, func(t *testing.T){...}) }`.

## Interview one-liners
- *Value vs reference?* Go is all pass-by-value; slices/maps/channels just have reference-like internal headers.
- *Why is my `err != nil` when it "should" be nil?* An interface holding a typed nil pointer isn't nil.
- *Slice vs array?* Array has fixed length & is a value; slice is a header over an array and is the everyday type.
- *Concurrency vs parallelism?* Concurrency = structure (independent tasks); parallelism = simultaneous execution. Go gives you the first; the runtime may give the second.
- *Goroutine vs thread?* Goroutines are runtime-scheduled, cheap (KB stacks), multiplexed onto OS threads (GMP).
- *Channels vs mutex?* Channels to transfer ownership/coordinate; mutex to protect shared state. Use the simpler one.
- *`%w` vs `%v`?* `%w` keeps the error unwrappable; `%v` flattens it to text.

## Our repo — quick facts
```
Go version    1.25.8            (loop-var capture bug fixed in 1.22 → not a bug here)
Layout        CQRS+hexagonal (spike, notif modules) / core+port (conversationmgmt)
DB            pgx via internal/golibs/database (Ext / QueryExecer / Tx); pgtype entities
Interfaces    small, consumer-side (inline) + segregated port files at seams
DI            constructors wire concrete → interface fields at composition root
Generics      bootstrap servicers [T any]; sliceutils helpers
Errors        %w wrap (mostly), sentinels, errors.Is/As, status.* at controller only
Concurrency   centralized in golibs (kafka engine, try, asyncawait); services rarely raw
Mocks         custom tool → testify mock.Mock; make service=X gen-mock-service
IDs           idutil.ULIDNow()
```
