100 Golang interview questions with detailed answers, sorted from basic to advanced. Free to practice, with progress saved automatically.
Go is a statically typed, compiled language with built-in concurrency primitives, often used for backend services and infrastructure tooling. Topics include types and interfaces, idiomatic error handling, goroutines and channels, mutexes, context cancellation, goroutine leaks and deadlocks, writing HTTP servers, testing, and performance considerations such as string and byte slice conversions.
and 25 more topics.
Go is a compiled, statically typed programming language created by Google in 2009, with a clean and simple syntax. Strengths: near C/C++ performance; easy concurrency through goroutines (concurrently running functions started with just the go keyword; each starts with only a few KB of stack,…
Go has 3 ways to declare a variable: var with a type, var letting Go infer the type, and the short declaration :=. Besides variables, Go has const for constants. What happens here: - var name type = value: states the type explicitly, used when…
Go's basic types fall into four groups: - Integers: int, int8/16/32/64 and the unsigned uint, uint8/16/32/64, uintptr. int and uint are platform-sized: 32 bits on 32-bit systems, 64 bits on 64-bit systems. - Floating-point and complex: float32/64, complex64/128. - Text: string (immutable, UTF-8 encoded). -…
The zero value is the default value assigned to a variable when declared without explicit initialization. Go guarantees every variable has a valid value — unlike JS's undefined or Python's None. Specifically: - int/float → 0 - string → "" - bool → false -…
Function declaration uses the func keyword. Go supports multiple return values and named returns. Functions in Go are first-class citizens: they can be assigned to variables, passed as parameters or returned from other functions.
A Go function can return multiple values at once. The most common use is the (result, error) pair: the function returns a result plus an error, and a non-nil error means something failed. What happens here: - minMax returns two numbers; when you only need…
In Go, a package is a directory of .go files that all declare package <name on their first line. Package main with func main() is where the program starts; other packages are libraries you import. An import path is the module path declared in go.mod…
Go has NO classes. A struct is a composite data type; methods are attached via receivers. Go favors composition over inheritance using struct embedding. Embedding is not inheritance: User's methods are promoted to Admin, so admin.Hello() works, but an Admin is not a User. Passing…
A pointer is a variable that holds the address of another variable. &x takes the address of x, and p reads or writes the value at the address p points to. A pointer to an int has type int. Go passes arguments by value. addOne…
An array in Go has a fixed length, and the length is part of the type: [3]int differs from [4]int. A slice ([]int) holds no data itself: it is a pointer to an underlying array, len (elements in use) and cap (room left to the…
A map is Go's built-in key-value type, declared with m := map[string]int{"a": 1} or make(map[string]int). Underneath it is a hash table: Go hashes the key to find the slot holding the value, so lookups, inserts and deletes take constant time on average, and the map…
range is the most concise way to iterate over a collection in Go (a classic for i := 0; i < len(s); i++ loop still works), with different semantics per collection type: - Slice / array: for i, v := range slice { } —…
Go has no try/catch/throw. An error is an ordinary value: a function returns error as its last result and the caller checks it right after the call. Reading the code shows exactly where things can fail and how each failure is handled; the price is…
defer postpones a function call until the surrounding function returns, including when it returns because of a panic. panic stops the normal flow of the current goroutine: the defers on the call stack run one by one from the innermost function outward, and if nobody…
An interface is a set of method signatures. Implementation is implicit: a type automatically satisfies an interface if it has all required methods with matching signatures — no implements keyword. save knows nothing about ConsoleWriter or os.File; it only needs something with a Write method.…
any (alias for interface{}) accepts any type, but you still need a type assertion to use the concrete value. Go 1.18+ generics replace many of these use cases. Use any when the type is only known at runtime: JSON without a fixed schema (JSON numbers…
A type assertion x.(T) extracts the concrete value of type T from an interface value. A type switch is a switch on the dynamic type of an interface, handling several Ts in one place. Both are used when you receive an any or a wide…
The two receiver forms differ in copy and mutation semantics: - Value receiver func (u User) Name() — receives a copy of the struct; cannot modify the original. - Pointer receiver func (u User) SetName(n string) — modifies the original struct directly; no copy cost.…
Generics let you write functions and types that work with multiple data types without code duplication. Type parameters are declared in square brackets [] with constraints that restrict accepted types: any, comparable, or your own interface. Map accepts a slice of any type and returns…
A goroutine is a function running concurrently, managed by the Go runtime rather than the operating system. Writing go f() runs f alongside the current code, and the caller continues immediately without waiting. The three fetch calls run at the same time, so the total…
A channel is a typed pipe goroutines use to pass data to each other. One goroutine sends with ch <- v, another receives with <-ch. Each send in produce waits until main has received the value. When done it calls close, and the range loop…
select is like a switch where every case is a send or receive on a channel. It blocks until one of the cases can proceed, then runs that case. This lets one goroutine wait on several sources at once: a result, a timeout or a…
Use it when you need to wait for several goroutines to finish before continuing. WaitGroup does one job, counting and waiting for zero, so it is lighter than a channel when you do not need to pass data between goroutines: no channel to create, no…
Go builds HTTP servers with the standard net/http package; no framework required. Two main pieces: a handler is a function taking an http.ResponseWriter (to write the response) and an http.Request (to read the request); http.ServeMux is the router that sends each request to the right…
Gin is the most popular HTTP web framework in the Go ecosystem, built for performance with radix-tree routing. Compared to stdlib net/http, Gin provides out-of-the-box: middleware chaining, automatic JSON binding and validation, centralized error handling, and route groups for clean API organization. Basic usage: r…
Middleware in Go is a wrapper function that receives a handler and returns a new handler, inserting logic before or after request processing. Gin middleware has a different signature from net/http: it is a gin.HandlerFunc taking gin.Context, with no next parameter. Instead of calling next.ServeHTTP,…
The encoding/json package works in both directions: json.Marshal turns a struct into JSON bytes and json.Unmarshal does the reverse. Struct tags json:"..." control the key name and how each field is handled. The Marshal output contains only name: Age is 0 so omitempty drops it,…
GORM is the most popular ORM in Go. Go structs map to tables, and CRUD is written as method chains: db.Create(&user) to insert, db.First(&user, 1) to query by primary key, db.Where("age ?", 18).Find(&users) for conditional queries. Three places where bugs hide: - Updates with a…
sql.Open does not open a connection, it creates a connection pool (sql.DB). The pool is safe to share across goroutines, so the app calls sql.Open once at startup, passes db around, and only calls Close at shutdown. The 25/5/5-minute values are not a standard; understand…
Go modules are Go's official dependency management system. Initialize with go mod init module-name to create go.mod, which tracks the module name and dependencies. Add libraries with go get package@version; run go mod tidy to clean up unused dependencies and add missing ones. The versions…
Go ships with the testing package and the go test command, so no framework is needed. There are three conventions: test files are named xxxtest.go and live in the same package, test functions are named TestXxx and take t testing.T, and go test ./... runs…
A Go benchmark is a function BenchmarkXxx(b testing.B) in a test.go file, run with go test -bench. You only write the code to measure inside a loop; Go keeps increasing the iteration count until the measurement is stable, then divides to get the average time…
Go encourages interface-based mocking: define a small interface for the dependency you want to mock, then create a mock struct implementing it with custom behavior. Go's implicit interfaces mean no production code changes are needed — just ensure the mock has the required methods. The…
Installing Go gives you a set of subcommands under go that covers most of a project's lifecycle: building, running, testing, formatting, checking for mistakes and managing dependencies. Two commands interviewers tend to probe: - go fmt: the whole community shares one format, so code review…
Go ships with pprof for profiling: it samples a running program to show which functions burn the most CPU, where memory gets allocated, and which goroutines are stuck. Collect a profile from a benchmark when optimizing one function, or from a running service. Importing net/http/pprof…
Since Go 1.21 the standard library has log/slog for structured logging: each log line is a message plus key-value pairs, instead of a hand-assembled text string. New projects usually use slog directly; before it, the community used zap (Uber) or zerolog. The first line shows…
Go fits high-load services that need every CPU core or do real computation; Node.js fits when the team already knows JavaScript, needs to ship fast, and the service spends most of its time waiting on I/O (database calls, other APIs). The core difference is the…
A struct tag is a metadata string placed after a field's type in a struct. Go itself does nothing with it; packages like encoding/json or a validator read the tag at runtime via reflection to decide how to handle that field. What happens here: -…
Both traverse the error chain (errors wrapped with %w), but serve different purposes. errors.Is(err, target) — checks identity: is err (or any wrapped error) equal to target? Use for sentinel errors. errors.As(err, &target) — checks type: does the chain contain an error of a specific…
t.Helper() marks the current function as a test helper. When a test fails, the testing package reports the file:line of the caller of the helper instead of the t.Errorf line inside the helper, so you jump straight to the assertion that failed. With assertNoHelper, every…
The race detector (-race flag) is built into the Go runtime and detects data races — when two goroutines access the same memory concurrently with at least one write, without synchronization. It instruments the binary at compile time to track every memory access. Overhead: per…
byte is an alias for uint8 (one raw byte); rune is an alias for int32 and represents one Unicode code point. A Go string is an immutable sequence of UTF-8 bytes, not an array of characters. Consequences: - s[i] returns a byte, and len(s) counts…
A function's final parameter can be declared ...T to accept a variable number of arguments. Inside the function, that parameter is an ordinary []T. ... works two ways: - Calling with separate arguments: sum(1, 2, 3). - Spreading an existing slice: sum(xs...) — passes the…
Both allocate, but for different purposes: - new(T) — works for any type. It allocates a zeroed T and returns a pointer T. It initializes nothing further. - make(T, ...) — only for slices, maps, channels. It initializes the internal structure (backing array, hash table,…
When a package is loaded, Go initializes it in this order: 1. Imported packages are initialized first (recursively) — each exactly once. 2. Package-level variables are assigned in dependency order (not source order): if one variable needs another, that one finishes first. 3. All init()…
iota is a compile-time counter: it resets to 0 at the start of each const block and increments by 1 after every ConstSpec line. Paired with a named type, this is how Go builds enums. - Assign a type (Weekday int) for a type-safe enum…
struct{} is a struct with no fields → it occupies zero bytes. The Go spec only says that zero-size variables may share the same address (in practice the runtime usually gives them one), so never write code that relies on comparing their addresses. What matters…
A slice is a view of (pointer, len, cap) into a backing array. Multiple slices can share the same array → mutating through one is visible through the other. The trap is in append: - With spare cap → append writes in place onto the…
sort.Slice(s, less) sorts a slice in place using a comparison function less(i, j) bool: it returns true if the element at index i must come before the element at index j. You do not need to implement sort.Interface. less receives indexes, not elements, so you…
Unbuffered (make(chan int)): every send needs a receiver ready at that exact moment — send and receive form a synchronization point. Buffered (make(chan int, 3)): a send blocks only when the buffer is full, a receive only when it is empty. The runtime detects that…
close signals "no more values will be sent". It is not a memory-release operation — channels are garbage collected. After closing: - Receive: returns immediately, never blocks. Buffered values are drained first, then it yields the zero value indefinitely. - Comma-ok tells the two cases…
A select without default blocks until one case is ready. Adding default turns it into a non-blocking operation: if no case is ready right now, default runs immediately. The main use is try-send / try-receive — shedding work under load instead of making the caller…
Because cancel is what releases the resources tied to that context, even when the work finished normally. Every child context is registered in its parent's children list, and WithTimeout / WithDeadline additionally arm a time.Timer. If you never call cancel: - The child stays attached…
A goroutine leak is a goroutine that starts but never finishes — it blocks forever on an operation that never completes. It holds on to its stack, everything it references, and any connection it owns, so the GC can reclaim none of it. Four common…
Every send or receive on a nil channel blocks forever — no panic, no error, just a permanent stall. This is defined behavior in the spec, not a bug. The consequence inside select: a case whose channel is nil is never chosen. That property is…
The problem with pipelines is that the downstream may stop early (enough data, cancelled request, error) while the upstream is still blocked on a send. To shut down cleanly, every send needs a paired exit case. A done channel is the raw mechanism: a chan…
Spawning 10,000 goroutines technically works, but it opens 10,000 connections — the upstream starts returning 429, or your own service runs out of file descriptors. You need to bound the concurrency. Option 1 — a semaphore built from a buffered channel. The buffer size is…
sync.Once guarantees an initialization block runs exactly once, no matter how many goroutines call it. A plain bool check is wrong: two goroutines can both observe false and both initialize. The second important property: Do blocks until the first call returns. Every later goroutine therefore…
The context docs are explicit: WithValue is only for request-scoped data that transits process and API boundaries — request ID, trace ID, authenticated identity, locale. Do not use it to pass function parameters, and do not use it as dependency injection: a parameter hidden in…
Mistake 1: Calling Add inside the goroutine. The docs require Add to happen before Wait. With Add(1) inside the goroutine, Wait may run while the counter is still 0 and return immediately, skipping all the work. Mistake 2: Passing the WaitGroup by value. func worker(wg…
http.Handler is an interface with a single method: http.HandlerFunc is a function type that already has a ServeHTTP method calling itself — so a plain function can act as a Handler without declaring a struct: In practice: Why it matters: everything in net/http (routers, middleware,…
Both print the same text, but %w keeps the original error in the unwrap chain while %v merely concatenates strings and breaks the link. Rules of thumb: - Wrap with %w when an upper layer must distinguish the error kind to decide (404 vs 500,…
Embedding declares a field with no name, only a type. The embedded type's fields and methods are promoted to the outer struct and can be called as if they were its own. It is not inheritance. It is composition plus syntactic shorthand: - No parent-type…
defer runs when the function returns, not at the end of a loop iteration. Inside a loop it means every resource is held until the function exits — iterating over a few thousand files will hit the file-descriptor limit. Fix 1 — extract the loop…
Because http.Server defaults every timeout to 0, meaning no limit. A slow (or deliberately stalling) client can hold goroutines and file descriptors indefinitely — the Slowloris attack shape. Follow-up points: - WriteTimeout also covers handler execution time, so large-download or streaming endpoints need a separate…
Middleware wraps inside-out, but at request time it runs outside-in, and the code after the next call unwinds in reverse — like a stack. Practical consequences: - Timing and panic-recovery middleware must sit outermost to cover everything. - The authentication middleware must sit outside any…
Because the method set of T contains only methods with a value receiver, while the method set of T contains both. If the interface requires a method declared on T, only the pointer satisfies it. The deeper reason: calling r.Save() on an addressable variable makes…
No. The spec states map iteration order is unspecified, and the runtime deliberately randomises the starting point on every run so nobody accidentally depends on it. For stable output, sort the keys and iterate those: Common real-world traps: - Tests comparing text built from a…
t.Run turns each case into an independent subtest: it gets its own name in the output, can be run alone with go test -run TestX/casename, and one failing case does not stop the rest. Traps to know: - Before Go 1.22 the loop variable tc…
omitempty drops a field when its value is an empty value as defined by encoding/json: false, 0, a nil pointer or interface, and a string/array/slice/map of length 0. That list does not include structs. Consequences you hit building APIs: - Page: 0 and "page not…
A Go string is immutable while []byte is not, so []byte(s) and string(b) normally allocate and copy the whole payload. On a hot request path that adds noticeable GC pressure. Cases the compiler optimizes to no allocation: - m[string(b)] — map lookup keyed by a…
Go wins on leanness: a static binary that starts in milliseconds, low RAM, cheap goroutines, and a small language so the whole team writes similar code. Java wins on ecosystem: Spring, JPA, Kafka clients, tooling and libraries for nearly every enterprise problem. Neither is "better"…
Goroutines can read/write shared variables, but without synchronization that is a data race — randomly wrong results, and go run -race will flag it. There are two ways to synchronize: a mutex locks the shared memory, a channel moves data (and ownership of it) from…