Documentation Index
Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
Use this file to discover all available pages before exploring further.
Go Interview Preparation
This chapter covers the most common Go interview topics, coding challenges, and system design questions you will encounter. The key to Go interviews is demonstrating that you understand why Go makes certain design choices, not just how to use its features. Interviewers at companies like Google, Uber, and Cloudflare want to see that you think about trade-offs: “Go chose X because Y, and the cost is Z.”Language Fundamentals
Common Interview Questions
What are the zero values in Go?
What are the zero values in Go?
| Type | Zero Value |
|---|---|
int, float64 | 0 |
string | "" (empty string) |
bool | false |
pointer, slice, map, channel, function, interface | nil |
struct | All fields set to their zero values |
array | All elements set to their zero values |
What's the difference between arrays and slices?
What's the difference between arrays and slices?
- Fixed size, part of the type:
[5]int≠[10]int - Value type - copied when passed
- Size known at compile time
- Dynamic size, backed by an array
- Reference type - contains pointer to underlying array
- Has length and capacity
- Can grow with
append()
How does `defer` work?
How does `defer` work?
- Deferred calls are executed in LIFO order when the function returns
- Arguments are evaluated immediately, not when deferred call runs
- Commonly used for cleanup (closing files, unlocking mutexes)
Explain the difference between make and new
Explain the difference between make and new
new(T): Allocates zeroed storage for type T, returns*Tmake(T, args): Creates and initializes slices, maps, channels only
What are interface{} and any?
What are interface{} and any?
interface{} (or any since Go 1.18) is the empty interface that all types implement. Used for:- Generic containers before generics
- JSON unmarshaling
- Printf-style functions
How are methods different from functions?
How are methods different from functions?
- You need to modify the receiver
- The receiver is large (avoid copying)
- Consistency with other methods on the type
Explain Go's memory model
Explain Go's memory model
- Go uses garbage collection (concurrent, tri-color mark-and-sweep)
- Stack: Local variables, function calls (fast, automatic)
- Heap: Escaped variables, dynamically sized data (GC managed)
- Escape analysis determines stack vs heap allocation
Concurrency Questions
What are goroutines and how do they differ from threads?
What are goroutines and how do they differ from threads?
- Lightweight (2KB initial stack)
- Managed by Go runtime
- M:N scheduling (many goroutines on few OS threads)
- Fast context switching
- Can have thousands running
- Heavy (1-2MB stack)
- Managed by OS kernel
- Expensive context switches
- Limited by OS resources
How do channels work?
How do channels work?
What is a data race and how do you prevent it?
What is a data race and how do you prevent it?
Explain select statement
Explain select statement
select waits on multiple channel operations:- Blocks until one case can proceed
- Random selection if multiple ready
defaultmakes it non-blocking
What is context and how is it used?
What is context and how is it used?
How do you handle goroutine leaks?
How do you handle goroutine leaks?
Coding Challenges
Implement a LRU Cache
An LRU (Least Recently Used) cache is one of the most common interview coding challenges. The key insight is using a hash map for O(1) lookups combined with a doubly linked list for O(1) insertion, deletion, and reordering. EveryGet moves the accessed node to the front; when capacity is exceeded, the node at the back (least recently used) is evicted.
Implement Rate Limiter
Implement Worker Pool
Implement Concurrent Map
System Design Topics
Design a URL Shortener
- Distributed ID generation (Snowflake, UUID)
- Database sharding by short code prefix
- Caching (Redis) for hot URLs
- Rate limiting per user/IP
Design a Pub/Sub System
Performance Questions
How do you profile a Go application?
How do you profile a Go application?
How do you reduce memory allocations?
How do you reduce memory allocations?
- Pre-allocate slices:
make([]T, 0, expectedSize) - Use
sync.Poolfor temporary objects - Avoid string concatenation in loops (use
strings.Builder) - Reuse buffers
- Use value types instead of pointers when appropriate
- Align struct fields properly
What is escape analysis?
What is escape analysis?
Best Practices Checklist
Code Quality
- Use
go fmtandgo vet - Run
golangci-lint - Write table-driven tests
- Use meaningful variable names
- Keep functions small and focused
- Handle all errors
- Use interfaces for dependencies
Concurrency
- Prefer channels for communication
- Always use context for cancellation
- Run race detector (
-race) - Close channels from sender side
- Use
sync.WaitGroupfor goroutine synchronization
Performance
- Profile before optimizing
- Pre-allocate slices when size is known
- Use
sync.Poolfor frequent allocations - Avoid defer in hot loops
- Use buffered I/O
Production
- Structured logging
- Health checks
- Graceful shutdown
- Configuration management
- Metrics and monitoring
- Proper error handling with stack traces
Quick Reference Card
Summary
Mastering Go for interviews requires:- Strong fundamentals: Types, interfaces, error handling
- Concurrency expertise: Goroutines, channels, sync primitives
- Performance awareness: Profiling, memory management
- Production experience: Logging, config, deployment
- Coding practice: LeetCode-style problems in Go
- System design: Distributed systems patterns
Interview Deep-Dive
You are designing a concurrent URL shortener service in Go that needs to handle 100K requests per second. Walk me through your design decisions for the data structures, concurrency model, and potential bottlenecks.
You are designing a concurrent URL shortener service in Go that needs to handle 100K requests per second. Walk me through your design decisions for the data structures, concurrency model, and potential bottlenecks.
- The core data structure is a mapping from short codes to long URLs. At 100K RPS, a single mutex-protected map becomes a bottleneck due to lock contention. I would use a sharded concurrent map: 256 shards, each with its own
sync.RWMutex. The shard is selected by hashing the short code. Reads useRLock(multiple concurrent readers), writes useLock. This reduces contention by 256x. - For ID generation, I would use
atomic.Uint64.Add(1)to generate sequential IDs without locks, then encode to base62 for the short code. At 100K RPS, a uint64 counter would not overflow for millions of years. - The concurrency model: the HTTP server handles requests with the standard net/http server (one goroutine per connection, multiplexed by the Go runtime). For writes (creating short URLs), the sharded map handles the concurrency. For reads (resolving short codes), the sharded map with RLock allows full concurrent reads.
- Bottlenecks: at 100K RPS, the in-memory map is fast, but persistence becomes the bottleneck. I would write to a buffered channel and have a pool of database writer goroutines batch-insert to PostgreSQL asynchronously. For reads, I would put a Redis cache in front of the database, with the in-memory sharded map as an L1 cache.
- Edge cases: URL validation (reject malformed URLs), rate limiting per IP to prevent abuse, TTL for short URLs (periodic cleanup goroutine), and handling the case where two concurrent requests try to shorten the same long URL (use the long URL as a secondary key to deduplicate).
Implement a thread-safe LRU cache in Go. Explain your choice of data structures, how you handle concurrency, and what the time complexity is for each operation.
Implement a thread-safe LRU cache in Go. Explain your choice of data structures, how you handle concurrency, and what the time complexity is for each operation.
- The standard LRU cache uses a doubly-linked list (for O(1) move-to-front and remove-from-back) combined with a hash map (for O(1) key lookup). On
Get: look up the key in the map, if found, move the node to the front of the list and return the value. OnPut: if the key exists, update the value and move to front. If it does not exist, create a new node at the front. If capacity is exceeded, remove the tail node (least recently used) and delete it from the map. - For thread safety, I would wrap the entire data structure with a
sync.RWMutex. Reads (Get) useRLockand writes (Put) useLock. However, sinceGetalso modifies the list (move to front), a pureRLockis not sufficient forGet— you need aLockfor both read and write operations. This means all operations are mutually exclusive. - To improve concurrency, you could shard the cache: 16 independent LRU caches, each with its own lock. The shard is selected by hashing the key. This allows 16 concurrent operations on different shards.
- Time complexity: Get is O(1) (hash map lookup + linked list move). Put is O(1) (hash map insert + linked list insert + possible tail removal). Space complexity: O(capacity) for both the map and the list.
- In production, I would consider using
sync.Mapfor read-heavy workloads, or a purpose-built library likegithub.com/hashicorp/golang-lruwhich is battle-tested. For very high concurrency, a clock-based approximation (CLOCK or CLOCK-Pro) avoids the linked list manipulation on every access.
sync.RWMutex with RLock for Get and Lock for Put?Since Get moves the accessed node to the front of the doubly-linked list, it is a write operation on the list structure even though it is logically a “read” on the cache. If two goroutines call Get concurrently with RLock, they both try to modify the linked list pointers simultaneously — a data race. The list node’s prev and next pointers would be corrupted, potentially creating a cycle or dangling pointer. The fix is to use Lock (exclusive) for Get as well, accepting that all operations are serialized per shard. An alternative is a lock-free design: use an atomic counter for access frequency instead of a linked list for recency, approximating LRU with LFU-like behavior (as some high-performance caches like Ristretto do).A Go service has been running for 3 days and its memory usage has grown from 200MB to 2GB. The heap profile shows nothing unusual. What else could be causing this, and how do you investigate?
A Go service has been running for 3 days and its memory usage has grown from 200MB to 2GB. The heap profile shows nothing unusual. What else could be causing this, and how do you investigate?
- If the heap profile shows steady allocations but RSS keeps growing, there are several possibilities beyond heap leaks.
- First, goroutine leak: check
runtime.NumGoroutine(). Each goroutine consumes at least 2KB of stack, and stacks can grow. 100,000 leaked goroutines could consume 200MB+. Take a goroutine profile (/debug/pprof/goroutine?debug=2) and look for goroutines blocked on channel operations or sleeping forever. - Second, stack growth: goroutine stacks start at 2KB but grow dynamically (up to 1GB by default). If your code has deep call stacks or large stack-allocated arrays, a few thousand goroutines could use hundreds of MB. Check the goroutine profile for unusually deep stacks.
- Third, memory fragmentation: the Go runtime requests memory from the OS in chunks and may not return it promptly.
HeapInusemight be reasonable butHeapSys(memory obtained from OS) could be much higher due to fragmentation. Checkruntime.MemStats— ifHeapReleasedis low relative toHeapSys, the runtime is holding memory it does not actively use. Force return withdebug.FreeOSMemory()as a test. - Fourth, cgo memory: if any dependency uses cgo, memory allocated by C code is invisible to the Go heap profiler. It does not show up in pprof but does show up in RSS. Use OS-level tools (pmap on Linux) to see where the memory is allocated.
- Fifth, mmap’d files or shared memory: if the service memory-maps large files, these appear in RSS but not in the Go heap.
- Investigation order: check goroutine count, check
MemStats, compareHeapInusevsHeapSysvs RSS, check for cgo usage, and finally use OS-level memory analysis tools.
[]byte which the GC treats as a single opaque blob), or tune GOGC and GOMEMLIMIT to control GC frequency and memory budget.