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.
Introduction to Go
Go (often referred to as Golang) is an open-source programming language supported by Google. It is statically typed, compiled, and designed for simplicity, concurrency, and performance. Think of Go as the power tool of programming languages: it does not try to be everything to everyone, but for the jobs it targets — networked services, CLI tools, infrastructure software — it is exceptionally good. While languages like Rust give you fine-grained memory control at the cost of complexity, and Python gives you expressiveness at the cost of speed, Go sits in a deliberate sweet spot: fast enough for production servers, simple enough to onboard a new team member in a week.History and Philosophy
Go was designed at Google in 2007 by Robert Griesemer, Rob Pike, and Ken Thompson — three engineers who had collectively built Unix, the B programming language, UTF-8, and the Java HotSpot compiler. It was announced in 2009 and reached version 1.0 in 2012. The language was born out of frustration with existing languages (C++, Java, Python) used for large-scale systems. At Google, C++ builds were taking 45 minutes, Java codebases had become tangled webs of inheritance hierarchies, and Python could not handle the throughput requirements. The designers wanted a language that combined:- Efficiency of C++ (static typing, compiled)
- Readability and Usability of Python/JavaScript
- High-performance networking and multiprocessing
Key Characteristics
- Simplicity: The specification is small enough to hold in your head.
- Fast Compilation: Designed to compile large projects in seconds.
- Garbage Collection: Automatic memory management.
- Built-in Concurrency: Goroutines and channels are core primitives.
- Static Typing: Type safety without the verbosity (type inference).
- No Inheritance: Composition over inheritance (structs and interfaces).
Installation
To get started with Go, download and install the latest version from the official website.- Visit go.dev/dl.
- Download the installer for your OS (Windows, macOS, Linux).
- Run the installer and follow the prompts.
Verify Installation
Open your terminal and run:go version go1.21.0 windows/amd64.
Your First Go Program
Let’s write the classic “Hello, World!” program.- Create a file named
main.go. - Add the following code:
Running the Program
You can run it directly using thego run command:
Code Breakdown
package main: Defines the package name. Themainpackage is special; it tells the Go compiler that this should compile as an executable program rather than a shared library.import "fmt": Imports thefmt(format) package, which contains functions for formatting text, including printing to the console.func main() { ... }: The entry point of the program. When you run the executable, this function executes first.
How Go Works: Compilation Deep Dive
Unlike interpreted languages (Python, JavaScript) that run code line-by-line, Go is a compiled language. Understanding the compilation process helps you write better code and debug issues.The Go Compilation Pipeline
| Stage | What Happens | Output |
|---|---|---|
| Lexing | Source code is broken into tokens (keywords, identifiers, operators) | Token stream |
| Parsing | Tokens are organized into an Abstract Syntax Tree (AST) | Parse tree structure |
| Type Checking | Types are verified, interfaces are checked | Type-annotated AST |
| SSA Generation | Code is converted to Static Single Assignment form | Intermediate representation |
| Code Generation | Platform-specific machine code is generated | Object files |
| Linking | Object files + runtime are combined | Final executable |
Why Go Compiles Fast
Go was designed for fast compilation. Large projects at Google compile in seconds, not minutes — a deliberate design decision, since slow builds were a primary motivation for creating Go in the first place. Key reasons:- No header files: Dependencies are resolved from packages directly
- Simple grammar: Easy to parse, no ambiguous syntax
- No circular imports: Dependency graph is always a DAG
- Package-level compilation: Only recompile changed packages
Go Runtime Architecture
Unlike C/C++, Go includes a runtime in every binary. This runtime provides:| Component | Purpose |
|---|---|
| Garbage Collector | Automatic memory management (concurrent, low-latency) |
| Goroutine Scheduler | M:N scheduler maps goroutines to OS threads (think of it as a dispatcher assigning thousands of lightweight tasks to a handful of workers) |
| Memory Allocator | Efficient allocation with TCMalloc-style design |
| Network Poller | Async I/O using epoll/kqueue/IOCP (the runtime handles waiting for network events so your code reads as synchronous) |
Cross-Compilation
Go makes cross-compilation trivially easy:Go Toolchain
Thego command is a powerful tool that manages source code, dependencies, and builds.
go run: Compiles and runs the Go program.go build: Compiles the program into an executable binary.go fmt: Automatically formats your code (standard style is enforced).go test: Runs tests.go mod: Manages modules and dependencies.go get: Downloads and installs packages.
Useful Build Flags
Interview Deep-Dive
Go includes a runtime in every binary. What does that runtime do, and what are the trade-offs compared to languages like C that have no runtime?
Go includes a runtime in every binary. What does that runtime do, and what are the trade-offs compared to languages like C that have no runtime?
- The Go runtime is embedded in every compiled binary and provides four major subsystems: the goroutine scheduler (M:N mapping of goroutines to OS threads), the garbage collector (concurrent, tri-color mark-and-sweep), the memory allocator (TCMalloc-style with per-P caches), and the network poller (epoll/kqueue/IOCP for async I/O that appears synchronous to the programmer).
- The trade-off is binary size — even a “Hello World” program is roughly 2MB because of the runtime overhead. In contrast, an equivalent C program might be a few kilobytes. However, you get automatic memory management, lightweight concurrency, and cross-platform network I/O without linking any external libraries.
- In practice, the binary size is rarely a problem for server-side applications. Where it matters is embedded systems or environments with strict storage constraints, where a language like C or Rust would be a better fit.
- A subtlety: because the runtime is statically linked, Go binaries are self-contained with zero external dependencies, which makes deployment dramatically simpler compared to languages that depend on a shared runtime (JVM, Python interpreter, .NET CLR).
Go is famous for fast compilation. Walk me through the specific design decisions that make this possible, and explain why languages like C++ cannot achieve the same speed.
Go is famous for fast compilation. Walk me through the specific design decisions that make this possible, and explain why languages like C++ cannot achieve the same speed.
- Four key design decisions enable fast compilation. First, Go has no header files — dependency information is read directly from compiled package objects, so there is no redundant re-parsing of the same declarations across translation units. In C++, a single header like
<iostream>can transitively include thousands of lines across dozens of headers, all re-parsed for every source file. - Second, Go’s grammar is deliberately simple and unambiguous. The parser never needs to backtrack or look ahead more than one token. C++ has an enormously complex grammar where parsing depends on semantic analysis (the “most vexing parse” problem), requiring multiple passes.
- Third, the no-circular-imports rule means the compiler can process packages in a strict topological order, never needing to revisit a package. C++ allows arbitrary include cycles through forward declarations and include guards, which complicates compilation ordering.
- Fourth, Go compiles at the package level and caches results. If you change one file in one package, only that package and its dependents need recompilation. Combined with Go’s module system, incremental builds on large codebases take seconds rather than minutes.
- The real-world impact is significant: at Google, the original motivation for Go was that C++ builds were taking 45+ minutes. The same-sized Go codebase compiles in under 10 seconds.
go build -race during development, and what is the runtime cost?The race detector instruments all memory accesses during compilation, adding roughly 5-10x CPU overhead and 5-10x memory overhead. You should use it during development and in CI test pipelines (via go test -race ./...), but never in production binaries because of the performance penalty. The race detector works by maintaining “shadow memory” that tracks which goroutine last accessed each memory location, and it reports a data race when two goroutines access the same location concurrently with at least one write. It is not a static analysis tool — it only detects races on code paths actually executed, so good test coverage is essential. In practice, making -race part of your CI pipeline catches the majority of concurrency bugs before they reach production.Explain Go's M:N goroutine scheduling model. What are G, M, and P, and why does this design matter for real-world performance?
Explain Go's M:N goroutine scheduling model. What are G, M, and P, and why does this design matter for real-world performance?
- Go uses an M:N scheduler where M goroutines are multiplexed onto N OS threads. The three key entities are: G (goroutine — a lightweight unit of work with its own stack), M (machine — an OS thread that executes Go code), and P (processor — a logical processor that holds a run queue of goroutines ready to execute). At any given moment, each M must be associated with a P to run goroutines.
- The number of P’s defaults to GOMAXPROCS, which defaults to the number of CPU cores. This means if you have 8 cores, you have 8 P’s, and at most 8 goroutines executing simultaneously. However, you can have thousands of G’s queued across those P’s.
- The scheduler uses work stealing: when a P’s local run queue is empty, it steals goroutines from another P’s queue. This provides automatic load balancing without programmer intervention. It also uses “hand-off”: when a goroutine makes a blocking syscall (like file I/O), the M is detached from its P, and the P picks up another M to continue running goroutines. This prevents a blocking syscall from stalling all goroutines on that P.
- Why this matters in practice: you can launch 100,000 goroutines for concurrent HTTP requests, and the scheduler efficiently maps them onto a handful of OS threads. Each goroutine starts with only 2KB of stack (dynamically growable), versus 1-2MB for an OS thread. This makes the “one goroutine per connection” pattern viable where “one thread per connection” would exhaust memory.
You need to deploy a Go service that runs on Linux ARM64 but you develop on macOS x86. How does Go's cross-compilation work under the hood, and what pitfalls should you watch for?
You need to deploy a Go service that runs on Linux ARM64 but you develop on macOS x86. How does Go's cross-compilation work under the hood, and what pitfalls should you watch for?
- Go makes cross-compilation trivial because the entire toolchain is self-contained. You set GOOS and GOARCH environment variables and the compiler generates code for the target platform:
GOOS=linux GOARCH=arm64 go build -o myservice. No cross-compilers, no SDK downloads, no Docker-based build environments needed. - Under the hood, the Go compiler includes code generators for all supported architectures. When you set GOARCH=arm64, the SSA backend emits ARM64 instructions instead of x86. The linker similarly knows how to produce ELF binaries for Linux regardless of which OS you are on.
- The main pitfall is CGO. If your code (or any dependency) uses cgo to call C code, cross-compilation becomes dramatically harder because you now need a C cross-compiler toolchain for the target platform. The fix is usually to set
CGO_ENABLED=0, which disables cgo entirely. Most pure-Go programs work fine without cgo, but some packages likenethave cgo variants for DNS resolution. WithCGO_ENABLED=0, Go falls back to a pure-Go DNS resolver, which behaves slightly differently (for example, it reads/etc/resolv.confdirectly rather than using libc’s resolver). - Another pitfall: if you use
-race, the race detector only works when the build platform matches the target platform. You cannot cross-compile a race-instrumented binary.
go build -ldflags="-s -w" reduce binary size, and when would you not want to use it?The -s flag strips the symbol table and the -w flag strips DWARF debugging information from the binary. Together they can reduce binary size by 20-30%. You would use them in production builds where you want smaller Docker images and faster deployments. You would NOT use them in development or staging builds because the stripped binary cannot be profiled with go tool pprof effectively, and stack traces from panics will show memory addresses instead of function names. A common pattern is to strip in your CI production build step but keep full symbols in development and staging. Some teams also keep an unstripped copy of the production binary archived alongside each release so they can match it against production crash reports using addr2line.