Junior Go preparation

Junior Go Backend Interview Questions

15 selected Junior Go interview questions for backend developers who need to explain fundamentals clearly and confidently.

Start a Junior Go AI InterviewNo credit card required. 1 free session available.
Technical interview practice in EnglishA mode where non-native speakers can practice passing technical interviews.

Type System

1Explain how Go's zero values work for built-in and reference-like types, and why they matter when declaring variables without explicit initialization.

In Go, a variable declared without an explicit initializer is automatically initialized to the zero value of its type. Numeric types become 0, bool becomes false, string becomes "", and arrays or structs are zeroed element by element or field by field. Pointer-like or reference-like types such as pointers, slices, maps, channels, functions, and interfaces have nil as their zero value. This matters because Go variables and omitted struct fields start in a deterministic state rather than containing garbage, and many APIs are designed so the zero value is a useful default, though some nil values still need initialization before certain operations.

Try answering this question with an AI coach

2How does Go handle equality for structs and what happens when a struct contains incomparable fields?

Struct values in Go can be compared with `==` and `!=` only when every field in the struct is comparable. Equality compares corresponding fields using each field's own equality rule. If a struct contains an incomparable field such as a slice, map, or function, the struct type is not comparable, and comparing two values of that struct type with `==` is a compile-time error. For such structs, use custom comparison logic or an appropriate deep-equality helper, especially in tests.

Try answering this question with an AI coach

3Explain string immutability in Go and the relationship between string, []byte, bytes.Buffer, and strings.Builder.

A Go `string` is an immutable sequence of bytes, often UTF-8 text but not required to be valid UTF-8. You cannot modify a string in place; to change contents you typically convert to `[]byte` for byte-level edits or `[]rune` for code-point edits, then convert back. Normal conversions between `string` and `[]byte` copy data and may allocate, so repeated conversions or repeated concatenation in loops can be expensive. `strings.Builder` is optimized for efficiently building strings, while `bytes.Buffer` is a mutable byte buffer useful for byte-oriented data and I/O and can also produce a string.

Try answering this question with an AI coach

4How do pointers work in Go, and what operations are intentionally disallowed compared to C?

A Go pointer is a typed value that holds the address of another value, such as *int pointing to an int. Use &x to take a value's address and *p to dereference a pointer to read or write the pointed-to value. Passing or storing a pointer lets multiple places observe or mutate the same underlying value, and a pointer can be nil. Unlike C, safe Go intentionally disallows pointer arithmetic and arbitrary raw-address manipulation; low-level exceptions require the unsafe package.

Try answering this question with an AI coach

5Explain how constants and iota work in Go, including typed versus untyped constants.

Go constants are compile-time values: boolean, string, or numeric. A typed constant has a specific type. An untyped constant has no fixed concrete type until it is used in a typed context, and numeric untyped constants are represented exactly/high precision until then; the value must be representable in the chosen type. `iota` is a predeclared identifier used in `const` declarations: it starts at 0 in each const block and increments for each constant specification, making it useful for enum-like constants and bit flags.

Try answering this question with an AI coach

6How do explicit conversions work in Go, and why does the language avoid many implicit conversions?

Go generally requires explicit conversions using `T(x)` when changing a value’s type, such as `int64(i)` or `MyID(n)`. This keeps type changes visible and avoids surprising implicit numeric, boolean, or string conversions. Conversions are only allowed by Go’s conversion rules and may change the value, for example through integer overflow/truncation, float-to-int truncation toward zero, or precision loss. Untyped constants are more flexible: they can be used in a typed context if the constant value is representable in that type.

Try answering this question with an AI coach

Data Structures

7Describe the difference between arrays and slices in Go, including how length, capacity, and underlying storage behave.

An array in Go has a fixed length that is part of its type, such as [3]int; it stores its elements directly, and assigning or passing an array copies the whole array value. A slice, such as []int, is a small descriptor over an underlying array: conceptually it contains a pointer to elements, a length, and a capacity. A slice’s length is the number of visible elements; its capacity is how many elements can be used from the slice start before reaching the end of the backing array. Slices are flexible: reslicing changes the descriptor, and append may reuse the same underlying array if capacity allows or allocate a new one if not.

Try answering this question with an AI coach

8How does Go's map type behave regarding key types, missing keys, nil maps, and iteration order?

Go map key types must be comparable; slices, maps, and functions cannot be used directly as keys. Looking up a key that is not present returns the element type's zero value, so the comma-ok form (`v, ok := m[k]`) is used to distinguish absence from a present zero value. A nil map can be read from and ranged over, but assigning to it panics; initialize it before writing. Map iteration order is unspecified and code must not depend on it.

Try answering this question with an AI coach

Memory Management

9When should you pass a struct or other value by value versus by pointer in Go?

Pass by value when the value is small, read-only for the call, or you want independent copy semantics. Pass by pointer when the function must mutate the caller's value, copying would be expensive, the type should not be copied, or nil/shared identity is part of the API. Do not assume pointers are always faster: they can add aliasing, heap escapes, GC work, and less cache-friendly access. Prefer clear semantics first and benchmark performance-sensitive choices.

Try answering this question with an AI coach

Language Semantics

10How does assignment and copying behave for structs, arrays, maps, and slices in Go?

Assignment in Go copies the value being assigned, but the effect depends on the type. Struct assignment copies the struct fields, and array assignment copies every element. Slice assignment copies the slice header—pointer, length, and capacity—so slices usually share the same backing array. Map assignment copies a reference-like map descriptor, so both variables refer to the same map data. For independent ownership, make a defensive copy, such as using copy or slices.Clone for slices and creating a new map and copying entries for maps.

Try answering this question with an AI coach

11How do closures capture variables in Go, and what bugs can arise around loops and goroutines?

A closure in Go is a function value that refers to variables from its surrounding lexical scope. It captures the variables themselves, so mutations can be observed by the closure, and captured variables may live longer than the creating function if the closure escapes. Bugs around loops and goroutines happen when closures share or observe a variable after it has changed, often leading goroutines or delayed callbacks to see an unintended value or causing data races on shared state. The usual fix is to pass the intended value as an argument to the closure or create a new local copy per iteration, and to synchronize concurrent access when needed. Since Go 1.22, loop variables declared by `for`/`range` are per-iteration, which removes many classic loop-variable capture bugs, but reused variables outside the loop and other shared mutable state can still cause problems.

Try answering this question with an AI coach

12Describe the semantics of range over arrays, slices, maps, strings, and channels.

`range` iterates according to the operand type. Over an array or slice it yields an index and an element value; the element value is a copy, so assigning to it does not modify the collection. Ranging over an array value copies the array for iteration, while ranging over a slice uses the slice header and indexes the underlying array. Over a map it yields key and value in an unspecified order, and the value is a copy. Over a string it yields the byte index and decoded Unicode code point (`rune`), not a rune-position index. Over a channel it receives values until the channel is closed and drained; a nil channel range blocks forever.

Try answering this question with an AI coach

Methods

13What is the difference between value receivers and pointer receivers, and how do receiver choices affect mutability, copying, and interface satisfaction?

A value receiver method receives a copy of the receiver, so it is suitable for read-only behavior and small immutable-style types; changes to the receiver copy are not visible to the caller. A pointer receiver method receives a copy of a pointer to the receiver, so it can mutate the original and avoid copying large or should-not-copy values. Receiver choice affects method sets: methods with value receivers are in the method set of both T and *T, while methods with pointer receivers are only in the method set of *T. Therefore, an interface requiring a pointer-receiver method is satisfied by *T, not T, even though an addressable T value may call the method using ordinary method-call syntax.

Try answering this question with an AI coach

Interfaces

14Describe how interfaces work in Go and what it means for a type to satisfy an interface implicitly.

A Go interface defines a set of required methods. A concrete type satisfies an interface implicitly when its method set contains those methods; there is no implements declaration. An interface variable can hold a concrete dynamic value whose type satisfies the interface, and interface method calls dispatch to that dynamic value's implementation. The empty interface, written interface{} or usually any, has no required methods, so every type satisfies it. Whether T, *T, or both satisfy an interface depends on their method sets and receiver choices.

Try answering this question with an AI coach

15How do type assertions and type switches work with interface values?

A type assertion checks the dynamic value stored in an interface: `v := x.(T)` succeeds if the interface value’s dynamic type is `T`, or if `T` is an interface implemented by the dynamic value. The single-result form panics if it fails. The comma-ok form, `v, ok := x.(T)`, reports success without panicking. A type switch, `switch v := x.(type)`, branches based on the interface value’s dynamic type, with `v` typed according to the matched case.

Try answering this question with an AI coach