Practical, high-leverage rules for writing fast code across the Code2b stack: Go services, Postgres, and the Next.js frontend. Each item is short on purpose. Apply the cheap wins first, measure, then optimize the hot path.
Rule zero: measure before you optimize. Profile, find the real bottleneck, fix that. Guessing wastes time and adds complexity for no gain.
General principles
The fastest work is work you don't do. Cache, skip, batch, or precompute before you micro-optimize.
Optimize the hot path, not the whole codebase. 5% of the code causes 95% of the slowness. Leave the rest readable.
Reduce round trips. One query, one request, one allocation beats N of them. N+1 is the number-one real-world killer.
Do work once. Compute at boot or write time, not on every read.
Keep data close. Memory beats cache beats disk beats network. Move work down that list.
Readable first, fast second, unless proven hot. A clever line that saves a nanosecond in cold code is a net loss.
Variables and memory
Tip
Why
Preallocate slices/maps with known size: make([]T, 0, n)
Avoids repeated reallocation and copying as they grow
Reuse buffers instead of allocating in a loop
Each allocation costs GC pressure
Pass large structs by pointer, small ones by value
Copying big structs is wasted work; tiny copies are free
Prefer value types for small, short-lived data
Stack allocation, no GC involvement
Avoid interface{} / any on hot paths
Boxing forces heap allocation and kills inlining
Scope variables tightly
Smaller scope helps the compiler keep values in registers
Use strings.Builder, never += in a loop
String concatenation allocates a new string every time
// Bad: reallocates on every append, unknown final sizevar ids []uuid.UUIDfor _, lead := range leads { ids = append(ids, lead.ID)}// Good: one allocationids := make([]uuid.UUID, 0, len(leads))for _, lead := range leads { ids = append(ids, lead.ID)}
Functions
Tip
Why
Keep hot functions small
Small functions get inlined, removing call overhead
Return early, avoid deep nesting
Skips unnecessary work and reads cleaner
Accept context.Context and honor cancellation
Stop work the caller no longer wants
Take slices/interfaces, return concrete types
Fewer allocations at call sites
Don't pass more than you use
Passing whole structs when you need one field couples and copies needlessly
Batch APIs over per-item APIs
SaveAll(items) beats calling Save(item) in a loop
// Bad: one DB round trip per leadfor _, lead := range leads { repo.UpdateStatus(ctx, lead.ID, "DONE")}// Good: one round triprepo.UpdateStatusBulk(ctx, ids, "DONE")
Loops and algorithms
Tip
Why
Pick the right data structure
A map lookup is O(1); scanning a slice is O(n)
Hoist invariant work out of loops
Don't recompute the same value every iteration
Break/continue early
Stop as soon as the answer is known
Avoid nested loops over the same data
O(n²) explodes; build a map/set once and look up
Watch hidden costs
len() is free; a regex compile or JSON marshal in a loop is not
// Bad: O(n*m), scans allowed list for every leadfor _, lead := range leads { for _, id := range allowedIDs { if lead.ID == id { keep(lead) } }}// Good: O(n+m), set lookupallowed := make(map[uuid.UUID]struct{}, len(allowedIDs))for _, id := range allowedIDs { allowed[id] = struct{}{} }for _, lead := range leads { if _, ok := allowed[lead.ID]; ok { keep(lead) }}
Concurrency (Go)
Tip
Why
Parallelize independent I/O
Waiting on 4 APIs serially wastes 3 API-times
Bound concurrency with a worker pool or semaphore
Unbounded goroutines exhaust memory, DB, and rate limits
Use errgroup for fan-out with error handling
Clean cancellation when one task fails
Never share a struct across goroutines without a lock or channel
Data races corrupt state and crash under -race
Don't spawn a goroutine for trivial work
Goroutines are cheap, not free; scheduling still costs
// Bound fan-out to LLM_CONCURRENCY (see docvalidation card)sem := make(chan struct{}, concurrency)g, ctx := errgroup.WithContext(ctx)for _, dim := range dimensions { dim := dim sem <- struct{}{} g.Go(func() error { defer func() { <-sem }() return audit(ctx, dim) })}if err := g.Wait(); err != nil { return err }
Database (Postgres)
The single biggest source of real slowness. Fix here first.
Tip
Why
Kill N+1 queries
Fetch related rows in one query with a join or WHERE id = ANY($1)
Index columns you filter, join, or sort on
A missing index turns a lookup into a full table scan
SELECT only the columns you need
SELECT * ships and parses data you throw away
Paginate; never fetch unbounded rows
One SELECT returning a million rows OOMs the service
Batch inserts/updates
One statement with many rows beats N statements
Use EXPLAIN ANALYZE on slow queries
Shows the real plan: seq scan vs index scan
Move filtering into SQL, not Go
The database filters faster and ships less data
Reuse a connection pool; set sane limits
Opening connections per request is expensive
-- Bad: N+1, one query per jobSELECT * FROM leads WHERE lead_job_id = $1; -- run once per job-- Good: one query for all jobsSELECT * FROM leads WHERE lead_job_id = ANY($1);
-- Index the columns you filter and join onCREATE INDEX IF NOT EXISTS idx_leads_job_id ON leads (lead_job_id);
Caching
Tip
Why
Cache expensive, rarely changing reads
Turns repeated work into one lookup
Load config/pricing once at boot
enrichabl loads its pricing cache at startup; restart after changes
Set a TTL or clear invalidation rule
A cache with no eviction becomes a stale-data bug
Cache the result, not the raw inputs
Store the computed answer so reads are free
Don't cache user-specific data in a shared key
One user sees another's data: correctness bug, not just perf
API and network
Tip
Why
Return only the fields the client needs
Smaller payloads parse and transfer faster
Paginate list endpoints
Protects both server and client from huge responses
Wrong keys force React to re-render and remount rows
Virtualize long lists
Render the visible rows, not 10,000 DOM nodes
Optimize images (next/image, right sizes)
Images are usually the largest bytes on a page
Debounce/throttle input handlers
Don't fire a search or fetch on every keystroke
Fetch in parallel, avoid request waterfalls
Sequential awaits stack latency
// Bad: recomputed on every renderconst sorted = leads.sort((a, b) => a.score - b.score)// Good: recomputed only when leads changeconst sorted = useMemo( () => [...leads].sort((a, b) => a.score - b.score), [leads],)
Measuring
You cannot fix what you cannot see. Reach for these before optimizing.
Tool
Use for
go test -bench + -benchmem
Micro-benchmark a function and see allocations
pprof (CPU + heap)
Find the real hot path and allocation sources
go test -race
Catch data races before they hit production
EXPLAIN ANALYZE
Diagnose a slow query's plan
Chrome DevTools Performance / Lighthouse
Frontend render, bundle size, and load timing
React DevTools Profiler
Find components that re-render too often
Checklist for a performance pass
Profiled and confirmed the actual bottleneck (not guessed)
No N+1 queries; related data fetched in one round trip
Filter, sort, and join columns are indexed
List endpoints and queries are paginated / bounded
Slices and maps preallocated where size is known
No allocations or expensive calls inside hot loops
Independent I/O runs in parallel, with bounded concurrency
Expensive, stable reads are cached with a clear invalidation rule
Frontend: heavy components lazy-loaded, long lists virtualized, expensive work memoized
Re-measured after the change to confirm a real gain
When in doubt, measure first and copy the pattern already used in enrichabl (pricing cache, bounded fan-out) or docvalidation (concurrent fan-out with LLM_CONCURRENCY).