LearnThatStack Ace your next interview

Golang

Assumes Go 1.22. Newer rows name their version Checked Nov 2026 125 code snippets

The model

  • Go is statically typed, compiled to native code and garbage collected.
  • Every argument is passed by value. Assigning a slice copies only its header, so both slices share the same elements.
  • An interface is satisfied implicitly, with no implements keyword.
  • A goroutine is a function the runtime schedules onto a few OS threads.
  • Errors are values, returned last and checked by the caller.
G1G2PM
RunningG1 runs on P, on OS thread M. G2 waits. GOMAXPROCS sets how many Ps there are.
G1G2PM
G1 blocksG1 blocks on a channel, a lock or I/O, so P runs G2 in its place.

Variables & types

declare names, pick types, convert values

Declare variables

Declare inside a functionx := 10
Redeclare when one name is newval, err := f()
Start at the zero valuevar name string0, false, "", and nil for pointers, slices, maps, channels and functions.

Tip:= works only inside functions. The compiler rejects an unused local variable.

Trap:= in an inner block makes a new variable that shadows the outer one.

Types, constants and conversions

List the basic typesbool, string, sized ints, float64, complex128. byte is uint8 and rune is int32.
Convert a numberfloat64(i)
Convert an int to a string and backstrconv.Itoa(42)strconv.Atoi("123")
Define constants, counting up with iotaconst Pi = 3.14159Sunday = iota

TipGo never converts types on its own. A float converted to an int drops its fraction.

Trapstring(65) gives "A", not "65". Go reads the number as a code point.

Pointers, new and make

Point at x, then change itp := &x*p = 21
Point at a zeroed valuep := new(int)
Give new a starting value (1.26+)p := new(42)
Get a ready slice, map or channel, not a pointermake(map[string]int)

TipPass a pointer to change the caller's variable. Returning a pointer to a local variable is safe.

TrapDereferencing a nil pointer panics, and so does the first write to a map made with new.

Slices, maps & loops

collections, text and how to walk them

Slices and append

ab[0 0 99]
Shared arrayb := a[:2] shares a's array, so append(b, 99) writes over a[2].
accap 5new array
New arrayc := append(a, 1, 2, 3) needs 6 but cap is 5, so c gets its own array.
Tell an array from a slice[5]int is a fixed value, copied whole. []int is a pointer, a length and a capacity.
Add to a slice, assigning the results = append(s, x)
Share the array but force append to copyd := a[:2:2]

TrapA small slice of a huge array keeps the whole array in memory. Keep a copy from slices.Clone instead.

Maps and strings

Tell a missing key from a stored zerov, ok := m[key]
Delete a keydelete(m, key)
Split at the first separatorstrings.Cut(s, "=")
Build text in a loop, not with +var b strings.Builder

Tiplen(s) counts bytes, not characters. Keys must be comparable, and map order is not specified.

TrapA nil map can be read but panics on write. Concurrent writes need a sync.Mutex.

Loops and control flow

Loop on a condition, or until breakfor x < 10 { }for { }
Walk a slicefor i, v := range s
Count from 0 to 9 (1.22+)for i := range 10
Range over an iterator (1.23+)for k := range maps.Keys(m)

TipA switch stops after the first match unless you write fallthrough. An if can start with err := f();.

TrapBefore Go 1.22, closures in a loop shared one variable. Now each iteration has its own.

Functions, defer & errors

return, clean up and report failure

defer, panic and recover

Clean up when the function returnsdefer f.Close()
Stop on a bug or impossible statepanic("bad state")
Stop a panic, inside a deferred functionr := recover()

TipDefers run last in, first out. defer fmt.Println(x) prints the value x had at that line.

TrapOne goroutine can't recover another's panic. An unrecovered panic in a goroutine you start crashes the whole program.

Errors

Check an errorif err != nil
Wrap with contextfmt.Errorf("load: %w", err)
Look for a sentinel error value in the chainerrors.Is(err, ErrNotFound)
Get a custom error type from the chainerrors.As(err, &target)
Do the same in one call (1.26+)errors.AsType[*MyErr](err)
Make your own error typefunc (e *E) Error() string

TipAdd Unwrap() error when your type wraps a cause. Handle each error once.

TrapWrapping makes the inner error part of your API, so wrap on purpose.

Structs & interfaces

build types by composition, with no inheritance

Structs, embedding and receivers

Create a struct by field namePerson{Name: "Alice"}
Embed a type so dog.Name workstype Dog struct{ Animal }
Work on a copyfunc (r Rect) Area()
Change the original, or avoid a big copyfunc (r *Rect) Scale()

TipIf one method needs a pointer receiver, give all the type's methods one.

TrapDog is not an Animal and can't be passed as one. Code inside Animal still calls Animal.Speak.

A nil pointer in an interface

type nilvalue nil
Equal to nilNo type and no value, so err == nil is true.
*MyErrorvalue nil
Not nilA nil *MyError still sets the type, so err == nil is false.
Return success from an error functionreturn nil

TrapNever keep a *MyError in a variable you later return as error. Every caller then sees a failure.

Dependency injection

Pass dependencies in, and wire them in mainNewUserService(store, log)
Say where the interface livesIn the consumer's package, sized to what it calls. Tests pass a small fake.

TrapPackage-level variables and init hide what each part needs and make tests share state.

Goroutines, channels & locks

run work at once and share data safely

Goroutines

Start onego worker(id, jobs)
Name the scheduler's partsG is a goroutine, M is an OS thread, and P is the state needed to run Go code.

TipEach starts with a few KB of stack. Since Go 1.25, the GOMAXPROCS default respects a container's CPU limit on Linux.

Trapmain does not wait for goroutines. Coordinate them with channels or a sync.WaitGroup.

Channels and select

no buffersend blocksreceivebuffer 34th blockstime

With no buffer, a send waits for a receiver. With a buffer of 3, three sends finish at once and the fourth waits.

Make an unbuffered or a buffered channelmake(chan int)make(chan int, 5)
Allow send only or receive onlychan<- int<-chan int
Close a channel, and detect that it closedclose(ch)v, ok := <-ch
Wait on channels with a timeoutcase <-time.After(d):A default case makes a select non-blocking.

TipA buffer doesn't fix a slow consumer. It only delays the moment the sender blocks.

TrapSending on a closed channel panics. Sending to or receiving from a nil channel blocks forever.

Worker pools and priority

Read jobs in each worker, then close the channelfor job := range jobsclose(jobs)
Run the highest priority firstheap.Pop(pq)

TipA dispatcher pops from container/heap. Raise a job's priority as it waits, or low-priority work can starve.

TrapSend to the workers outside the queue's lock. Otherwise a full channel blocks every producer.

The context package

Set a timeout, and always cancelcontext.WithTimeout(ctx, d)defer cancel()
Stop work when cancelledcase <-ctx.Done():return ctx.Err()

TipPass it as the first parameter, never in a struct. Cancelling a parent cancels every child.

TrapGive WithValue a private key type, not a string, or packages can collide. Never use it for optional parameters.

sync, races and the memory model

Guard shared data, or allow many readersmu.Lock()mu.RLock()
Wait for goroutines to finishwg.Wait()
Start and count a goroutine in one call (1.25+)wg.Go(f)
Run setup exactly onceonce.Do(f)
Know when to use sync.MapFor write-once keys, or separate keys per goroutine. Otherwise use a map and a mutex.
Find data racesgo test -race
Update one counter with no lockvar n atomic.Int64n.Add(1)
Name what creates a happens-before orderChannel operations, unlock then lock, go statements, once.Do and atomics.

TipThe race detector reports only code paths that actually run.

Trapcounter++ is a read, an add and a write, so two goroutines can lose an update.

HTTP servers & APIs

serve requests, limit them and stop cleanly

Servers, middleware and shutdown

Match a method and a wildcard (1.22+)mux.HandleFunc("GET /u/{id}", h)r.PathValue("id")
Write middlewarefunc(http.Handler) http.Handler
Log every request, even rejected oneslogging(auth(mux))
Turn a signal into a cancelled contextsignal.NotifyContext(ctx, os.Interrupt)
Stop new connections, finish requestssrv.Shutdown(ctx)

Tipmain must wait until Shutdown returns, or it cuts off requests. Handlers must be safe for concurrent use.

TrapA bare ListenAndServe sets no timeouts. In production, build an http.Server with ReadHeaderTimeout set.

Rate limiting

Make a token bucketrate.NewLimiter(r, b)
Reject at once with a 429, or wait for a tokenlimiter.Allow()limiter.Wait(ctx)

TipKeep one limiter per client. Several instances share counters in Redis.

TrapTrust X-Forwarded-For only from your own proxy, or a client can fake a new IP on every request.

Secure a web API

Hash a passwordargon2.IDKey(pw, salt, t, m, p, n)
Scan dependencies in CIgovulncheck ./...

TipTake salts from crypto/rand, never math/rand. Compare secrets with subtle.ConstantTimeCompare.

TrapCheck a JWT's signing method first. Otherwise an attacker can sign an HS256 token with your public RSA key as the secret.

JSON, files & databases

read and write data, settings and logs

JSON

Convert to JSON and backjson.Marshal(p)json.Unmarshal(data, &p)
Read an HTTP bodyjson.NewDecoder(r.Body)
Name a key and drop empty valuesjson:"name,omitempty"

TrapOnly exported fields are encoded, the ones that start with an upper-case letter.

Files and time

Read a small file in one callos.ReadFile(name)
Read line by linebufio.NewScanner(f)
Format and parse a datenow.Format("2006-01-02")time.Parse(layout, s)
Compare two instants, not with ==a.Equal(b)

TipA bufio.Writer holds small writes. Call Flush and check its error before the file closes.

TrapA layout is the reference time Mon Jan 2 15:04:05 MST 2006 as you want it shown. YYYY doesn't work.

SQL databases

Open the connection poolsql.Open("pgx", dsn)
Stop SQL injection with placeholdersWHERE id = $1
Walk the rowsfor rows.Next()rows.Scan(&u.ID)

TipAfter the loop, check rows.Err(). QueryRow(...).Scan returns sql.ErrNoRows when nothing matches.

Trap*sql.DB is a connection pool, not one connection. Open it once and share it. Open may not connect, so call PingContext.

Flags, config and logs

Read the raw argumentsos.Args[1:]
Define a named optionflag.Int("n", 1, "help")flag.Parse()
Load settings once, into a typed structos.Getenv("PORT")
Log JSON in production (1.21+)slog.NewJSONHandler(w, nil)
Print plain lines with no levelslog.Println("started")

Traplog.Fatal exits at once, without running deferred calls.

Modules, tests & builds

the go command, from packages to binaries

Packages, init and modules

Export a nameStart it with an upper-case letter. Lower-case names stay private to the package.
Say when init() runsBefore main. Imports finish first, then package variables, then init.
Add or upgrade a dependencygo get github.com/lib/pq@v1.10.7
Add missing modules, remove unused onesgo mod tidy
Say how Go picks a versionMinimal version selection, the lowest that meets every requirement. go.sum holds the hashes.

TrapA major version of two or more must appear in the module path, as in /v2.

Tests and benchmarks

Write a test in a _test.go filefunc TestAdd(t *testing.T)
Run every package, with coveragego test -cover ./...
Name each case of a table-driven testt.Run(name, fn)
Loop in a new benchmark (1.24+)for b.Loop()

Tipt.Errorf marks the test failed and continues, and t.Fatalf stops it. Fuzz tests are FuzzXxx(f *testing.F).

Build tags and go generate

Compile a file only on some systems or tags//go:build linux || darwingo build -tags sqlite
Read the syntax from before Go 1.17// +build linux
Name a generator, then run it//go:generate stringer -type=Colorgo generate ./...

TipGo has no macros. Generated code is ordinary Go the compiler checks, while reflection is slower and unchecked.

Trapgo build never runs go generate. Run it yourself and commit the output.

Profiling, GC & unsafe

measure a running program and cut its costs

GC, allocators and unsafe

Say how the collector worksConcurrent tri-color mark-and-sweep. It never moves objects and isn't generational.
Set how often it runs, and a soft memory limitGOGC=100GOMEMLIMIT=1GiB
See what escapes to the heapgo build -gcflags=-m
Name the default collector (1.26+)Green Tea. It marks and scans small objects faster.
Turn bytes into a string with no copyunsafe.String(unsafe.SliceData(b), len(b))

TrapNever modify bytes that back a string. Strings are immutable and other code may share them.

Profile and debug a service

Expose profiles on a private portimport _ "net/http/pprof"
Read a CPU profilego tool pprof <host>/debug/pprof/profile
Dump every goroutine stackFetch /debug/pprof/goroutine?debug=2, or send SIGQUIT.
Find leaked goroutines (1.27+)The goroutineleak profile reports goroutines blocked on something no running code can reach.

TipA goroutine leak is one blocked forever. Give each goroutine an exit path, such as a select on ctx.Done().

TrapKeep the pprof port private, because it exposes internals.

Distributed services

what senior interviews add

Locks and caches

Take a lock in RedisSET key token NX PX 30000
Release only your own lockDelete the key with a Lua script, only if it still holds your token.
Stop a paused holder from writingUse a fencing token, a growing number. The resource rejects older ones.
Read from two cache levelsTry the in-process cache, then Redis. On a miss, load from the database and fill both.
Stop a cache stampedesingleflight.Group

TrapIf work outlasts the expiry, another process gets the lock while you still think you hold it.

Service calls, events and tracing

Decide what to retryOnly transient errors on idempotent requests, with exponential backoff and jitter.
Run work across servicesA saga. Each step is a local transaction with a compensating action.
Start a child spantracer.Start(ctx, "db.load")
Store state as eventsAppend an immutable event. Replaying the events rebuilds the state.
Serve readsProjections turn events into query tables, so reads are eventually consistent.

Trapspan.RecordError doesn't mark a span as failed. Also call span.SetStatus(codes.Error, msg).

Found this useful? Pass it on.
Pro · $10/mo

This sheet stays free. With Pro, you can:

  • Every answer on this sheet. Read the full answer to all 68 questions it links, not just the 10 free ones.
  • My sheet. The cards you star and the ones you miss in Quiz me, from every sheet, on one page to quiz and print.
  • PDFs. Download this sheet and My sheet, laid out for A4, with every question a link to its answer.
  • The rest of Pro. Every answer in the question library, and 1,000 AI credits a month.