Membership Prices Increasing in January.

58 Golang Interview Questions & Answers

Jayson Lennon
Jayson Lennon
hero image

So you've:

You should be ready to land that job right... ❓

Maybe!

But here’s the thing. Some interviewers may ask you some more specific questions about Go (Golang) before they get into the actual coding and technical problem-solving.

Think of it like a gatekeeper. It's easy to put "Golang" as a skill on your resume but they want to make sure you actually know what you're talking about before they move on to more technical stuff.

Don't worry though. As long as you've been using Golang and you read through this post, then you should be fine. Often these questions are just basic concepts related to Golang, but the last thing you want, is to be surprised by these during your interview!

So what do you need to know?

Well, rather than make you sit down and try to learn 150+ Golang questions, I've put together the 58 most essential questions and answers that cover everything from fundamental concepts to advanced programming techniques with Go.

Understand these and you'll be able to answer almost any Golang based question they throw at you.

Huzzah!

Sidenote: If you find that you’re struggling with the questions in this guide, or perhaps feel that that you could use some more training and want to build some more impressive projects for your portfolio, then come and check out my complete Golang Developer course.

It’ll not only set you on the fast track to getting hired, but you’ll also blow away your interviewers with the level of work and knowledge you have.

learn golang from scratch

With that out of the way, let’s get into the questions.

Part 1: The basic Go questions

These questions test your basic knowledge of the Go programming language.

They are intentionally simple and the interviewer will use these questions to gauge if you have programmed with Go before.

If you are having trouble with these questions, then you should write some more Go code until you are confident in your ability to get through this section.

#1. What is Golang?

Go (otherwise known as Golang) is a programming language developed by Google, with built-in language support for creating concurrent programs using goroutines.

It was originally designed to be familiar to programmers who have learned the C programming language. Thanks to this, the simplicity of the language makes for a faster learning experience.

#2. What are some advantages of using Golang over other programming languages?

Go has multiple advantages:

  • Go compiles to machine code, making Go programs run faster than interpreted languages
  • It has a garbage collector, so memory doesn't need management by the developer. This makes development faster since it frees the developer from having to track down memory-related bugs
  • The simplicity of the language makes it easy to learn. This enables developers to become productive much quicker than other programming languages
  • The builtin concurrency provided by Go makes development of concurrent systems easier
  • Go uses static linking when compiling a program. This enables shipping a program using a single file instead of needing to package extra runtime files

#3. Explain the differences between Go packages and Go modules

A Go package is a collection of source files contained within a single directory, and all Go code exists within a package. Whereas a Go module is a collection of packages.

An example of a package named P would be a directory P containing a.go and b.go.

An example of a module would be a directory containing at least one package, and a go.mod file must exist in the root module directory to delineate the directory as a module. The go.mod file contains extra module information such as the dependencies in use and the name of the module.

#4. How does Go manage memory?

Go has a garbage collector which periodically deletes allocated objects when they are no longer needed. This happens automatically without any input from the developer.

#5. Why does Go have a fast execution speed?

Go compiles to native machine code and does not require interpretation while running.

Since native machine code gets executed by the CPU directly without any translation layer, it executes faster than interpreted code.

golang is fast

#6. What's the difference between GOPATH and GOROOT?

They are both environment variables which serve different purposes:

GOROOT is the path to the location of the Go tools.

GOPATH is the path to the Go workspace where Go code gets stored.

Go modules are superseding GOPATH usage and modern Go projects should use modules instead of relying on GOPATH. Although GOPATH is still used for storing code downloaded as dependencies.

#7. What's a structure in Go?

A structure is a way to group data into a single unit. Structures have fields, and each field has a data type. Here is some sample code to define a new structure:

type Person struct {
 age  int
 name string
}

A structure can get instantiated like this:

person := Person{}

This will create a new instance of Person having default values. To set the initial values during creation, include them within the curly braces:

person := Person{
  age:  22,
  name: "Mike",
}

#8. What's a Go for and what are the different forms?

A for loop is the standard way to perform repetitive operations in Go.

It has three components:

  1. an initialization component which allows creation of a variable to control the loop count,
  2. a condition component which gets ran on each iteration of the loop to determine if the loop should continue,
  3. and a post statement which gets evaluated at the end of each iteration.

Combining these three components allows for fine-grained control over how the loop executes.

The first form uses all three components similar to a C-style loop:

for i := 0; i < 10; i++ {
  //...
}

This loop starts at 0, loops as long as i is less than 10, and increments the value of i at the end of each iteration.

The second form of a for loop forgoes the initialization statement and post statement, instead making exclusive use of the condition component. The developer needs to manage loop control manually to ensure that the condition will cause loop termination at some point during iteration:

for i < 10 {
  // ...
}

The third form of a for loop is an infinite loop and doesn't use any components. It solely uses the for keyword and the loop body:

for {
    // ...
}

Since the loop is infinite, terminating the loop requires using the break or return keywords (or a program crash).

#9. What is a switch in Go, and what are the different forms?

A switch is like a list of if..else if blocks that instead uses case statements where each case is a possible value of a variable.

This makes it easy to check a variable against different values. Switches in Go can also evaluate a condition instead of checking for variable equality.

The basic form of a switch checks to see if a variable is equal to some value:

switch n {
case 1:
  // ...
case 2:
  // ...
case 3, 4, 5:
  // ...
default:
  // ...
}

The "conditional" form of a switch will evaluate an expression for each case:

switch n := someFunc() {
case n < 10:
  // ...
case n == 10:
  // ...
case n > 10:
  // ...
}

#10. What is the purpose of using Go's range keyword in a for loop?

Using range in a for loop allows iterating through a collection without the need to check the length of the collection.

range can also provide copies of elements in the collection which allows the developer to access data without needing to use an index.

Prefer using range because it makes improperly accessing a collection with an out-of-bounds index more difficult.

#11. How are errors handled in Go?

Errors are value types in Go and they get returned from functions using multiple return values.

The caller of the function should check the error returned from a function to determine if an error occurred. If an error occurred, then the developer should write code to handle this situation.

Go has the "comma,ok" idiom which gets used for error checking:

val, err := someFunc()
if err != nil {
    // error occurred
}

#12. How can you return multiple values from a function in Go?

Go supports multiple return values from functions using this syntax:

func multi() (int, int) {
 return 1, 2
}

a, b := multi()

#13. Why would you want to return multiple values from a function?

Since Go doesn't have exceptions, returning multiple values from a function allows Go to return an error if one occurs. This makes working with fallible functions easier.

For example

Instead of returning nil from a function and having it mean "some ambiguous error occurred", we can instead return nil, error and gain access to extra error information.

#14. How do you declare multiple variables in a single line of Go code?

Using commas between variable names allow the declaration of more than one variable in a single line of code.

Taking advantage of shorthand syntax allows more than one variable declaration in a single line of code:

a, b := 1, 2

Declaring multiple variables using optional initialization is possible in one line as well:

var a, b, c int

#15. In Go, what's the Rune datatype?

A rune is an alias for int32 that contains a single Unicode code point.

Runes are useful when working with text. An important thing to remember with runes is that they get presented as int32 in code. This means that attempting to display a rune will show a number instead of the expected Unicode character.

To get the visual representation of a rune, the %c formatting token gets used.

#16. Explain Go's map data type

A map is a data structure that stores data in a key/value pair.

Accessing a value in the map requires knowing the key associated with the value you wish to access. Maps use random ordering so they are not appropriate to use when the order of the data is important.

#17. What operations are available to perform on a Go map? What's the syntax for each?

Creating maps requires knowing the types of both the key and value.

Creating a map uses the var keyword and then a function call to make must follow to make the map usable:

var m map[string]int
m = make(map[string]int)

Using shorthand initialization syntax rolls this into a single line:

m := make(map[string]int)

We can also create a new map and then populate it with some initial values:

m := map[string]int{
  "a": 1,
  "b": 2,
  "c": 3,
}

To write data to a map, we need to provide the key and the value:

m["key"] = 1

To read data from a map, we need to provide the key:

value := m["key"]

If you aren't sure if the key exists in the map, you can check by attempting to read it:

value, exists := m["key"]

If exists is true, then the key is present in the map, and if exists is false, then the key was not present in the map. It's important to check this boolean value because Go will return a default value if the key doesn't exist.

Using the delete function will delete a key from a map:

delete(m, "key")

delete doesn't have any return value and doesn't provide the status of deletion.

If you want to ensure that you delete a key that already exists, you'll need to check if the key exists first by trying to read it, and then run the delete function.

#18. What's an array and how do you use arrays in Go?

An array is a contiguous chunk of memory.

It can contain a specified number of elements and accessing each element uses an offset to a memory location. Go handles the addressing automatically, so the offset (index) gets used.

To create an uninitialized array in Go with the capacity for 2 elements:

var names [2]string

To create an array with initial values:

names := [2]string{"Alice", "Bob"}

Reading data from an array and writing data to an array both use indexing:

names := [2]string{"Alice", "Bob"}

alice := names[0]

// replace "Bob" with "Carol"
names[1] = "Carol"

fmt.Println(names[1])  // prints "Carol"
fmt.Println(alice)     // prints "Alice"

#19. Is it possible to resize a Go array?

Arrays are a fixed size and cannot get resized once created. Using a slice provides a way to "resize" an array by copying the data to an array of different size.

#20. What's a slice in Go?

A slice is a view into a backing array.

Slices make it more convenient to work with arrays, such as providing the option to resize the slice or perform common operations such as append or pop in an efficient way.

#21. In Go, how can you convert between numeric types?

Go has type casting functions to convert between numeric types. For example, to convert an int to a float:

i := 99
var f float32
f = float32(i)

Part 2: Digging deeper into some harder Go questions

If you've been able to answer a few of the initial set of questions with confidence and ease, the interviewer may move on to more advanced questions about the language.

Their goal here is establish your current skill level.

The more correct answers you can give here, the better your chance at getting hired (or at least moving onto the next stage).

#22. What are function literals in Go?

Function literals are also known as anonymous functions, or closures.

Function literals allow you to write a function within a function, or to assign a function to a variable.

#23. Does Go have variadic functions? What are they?

Variadic functions are functions which accept a variable number of arguments.

When a variadic function processes the arguments to the function, it does so using a slice. Each argument is an element in the slice, and iterating the slice will produce all arguments passed to the function.

To create a variadic function, use three dots in the function parameters:

func sum(nums ...int) int {
  fmt.Println(nums)
  total := 0
  for _, n := range nums {
    total += n
  }
  return total
}

func main() {
  fmt.Println(sum(1, 2, 3))  // 6
}

#24. When would you use a variadic function in Go?

Variadic functions are useful when you don't know the number of inputs that will get provided to the function, or if you want to offer a more convenient API where the developer provides any amount of arguments they want.

#25. What does the iota Go keyword do, and why would you use it?

The iota keyword offers a way to increment a value when creating constants.

Instead of assigning each constant a value, using the iota keyword will take care of this automatically. Using iota instead of entering numbers by hand allows the developer to update or add constants without having to worry about what values get assigned to them.

#26. What is a receiver function in Go?

Receiver functions are functions defined on a specified data type. They enable the use of dot notation when calling functions and provide a way to organize functionality per type.

Receiver functions are also required when implementing an interface.

#27. What's a pointer?

A pointer is a variable that contains a memory address. The data at the memory address is what the pointer "points" to.

Pointers increase the efficiency of the program. Instead of making multiple copies of data, a pointer instead points to a single copy of the data. The pointer itself can then get copied using minimal resources while still maintaining the ability to access the data it points to.

#28. What happens when a pointer in Go gets dereferenced?

When a pointer gets dereferenced, the data which exists at the memory address stored in the pointer will get accessed. This allows developers to access data using an indirection: First the pointer gets accessed to discover the memory address where the data exists, and then the data gets accessed using that address.

#29. Does Go support pointer arithmetic?

No, Go does not support pointer arithmetic. Go guarantees memory safety and allowing pointer arithmetic would break this guarantee because manipulating pointers can result in pointing to invalid data. This has the possibility of causing security issues.

#30. What is a goroutine?

A goroutine is a lightweight thread of execution managed by the Go runtime. They execute concurrently with the main thread and their purpose is to enable simplified development of efficient concurrent systems.

#31. How do you spawn a goroutine?

Goroutines get spawned using the go keyword followed by a function call.

#32. What is a mutex?

A mutex is a mutually exclusive lock.

It provides a way to synchronize access to a piece of data by ensuring that data gets accessed by one goroutine at a time. This prevents undefined behavior because whenever a goroutine writes data, other goroutines have to wait until the write completes before they can read.

A mutex works using a locking system where the lock is either "locked" or "unlocked". When setting a mutex to "locked", no other goroutine can lock it again. If they try to, the operation will block until the first goroutine unlocks it. This locking system prevents any two goroutines from obtaining the lock at the same time.

#33. How can you stop a goroutine after spawning it?

There are two ways to stop a goroutine:

  1. Terminate the entire program. Since goroutines get managed by the main program, terminating the program also stops all goroutines
  2. Send a message to the goroutine requesting it to stop. Goroutines are independent and their instructions get executed by the CPU without going through an intermediary. Because of this, there must be code present in the goroutine to accept requests such as stop and resume

#34. Can your program terminate if there are still goroutines running?

Yes, a Go program can terminate at any time, and any goroutine may also terminate the entire program.

#35. What happens to goroutines when the program ends?

When the program ends, any goroutines still running will also end.

Since goroutines are independent, there is nothing stopping the main program from doing whatever it needs to do, and then ending. This means the programmer must track the status of goroutines and design a way to end the program while also allowing goroutines to finish their work (or abandon it, if acceptable).

#36. What's a Go interface?

An interface is a way to declare that some group of behaviors (functions) exist. A type can then implement the interface and provide the implementation for the behaviors expected by the interface. This enables functions to operate on the interface instead of a specified type.

When a function uses an interface as a parameter, the compiler will check all calls to this function and ensure that each type passed to the function implements the interface. This makes it impossible to call functions that weren't implemented on the type, increasing the reliability of Go programs.

#37. What's a channel in Go?

A channel is a bidirectional communication pipe. Channel permit data access through "reading" and "writing", or "sending" and "receiving".

#38. Provide an example using Go to read and write data to a channel

Writing data to a channel uses the arrow operator.

The arrow operator "points" to the direction of data flow. So when the channel is in the left operand, data flows from the right operand into the channel:

ch := make(chan int, 1)
ch <- 1

Reading from a channel uses the arrow operator as well. This time, the channel is the right operand:

ch := make(chan int, 1)
ch <- 1
one := <-ch

#39. What's the difference between Go's buffered and unbuffered channels?

Channels come in two forms: buffered and unbuffered.

A buffered channel has space for a specified amount of data and as long as the buffer is not full, more data can get written to the channel. Once the buffer fills, then write operations to the channel will block until space becomes available.

An unbuffered channel has space for one piece of data. When the data gets written to an unbuffered channel, the operation will block until the data gets read out of the channel by some other code (such as code running in a goroutine).

#40. Can you read data from a closed channel in Go? Why or why not?

Yes, it's possible to read data from a channel after it closes. Channels act as a message queue and preserve all data that exists in the channel as long as the channel is still in scope in some part of the program.

After closing a channel, the remaining messages will stay in the channel until either they get read out or the channel gets dropped.

#41. What's a type assertion in Go? When would you need to use it?

Type assertions get used when working with interfaces.

An interface hides the underlying type behind an interface, but a type assertion allows the developer to determine what this underlying type is. This is useful in situations where you need access to the original type that implemented an interface.

Here is an example of a type assertion that checks if a variable is a string:

var msg interface{} = "hi"

if s, ok := msg.(string); ok {
  fmt.Printf("msg is a string: %s\n", s)
}

#42. Provide an example of Go's type switch. When would you use one?

A type switch is a type assertion that uses a switch statement. Since it uses switch, multiple cases can check for different types:

var msg interface{} = "hi"

switch msg.(type) {
case string:
  fmt.Println("msg is a string")
case int:
  fmt.Println("msg is an int")
default:
  fmt.Println("msg is something else")
}

Type switches are useful when you are working with an interface and there are more than one possible concrete types that you need to access.

#43. What are some uses for an empty struct in a Go program?

An empty struct is useful if you need to change a map into a set. If you use the key of a map as the critical piece of information, then you can to ignore the value by using an empty struct.

Channels don't always need to transmit useful data. Sometimes, the act of transmitting empty data is enough to trigger an outcome. Empty structs are perfect for this situation because we can write them to the channel and then read them from the other end without worrying about what data should get sent.

#44. In Go, can nil get assigned to variables?

nil can get assigned to variables when the variable's type is a pointer.

Since pointers can be nil, this is OK, but nil cannot get assigned to a variable that stores a value.

#45. How can you copy a slice using Go?

Copying slices uses the copy function:

a := []int{1, 2, 3}
b := make([]int, 3)
copy(b, a)

#46. How can you copy a map with Go?

To copy a map, a loop can iterate the entire map and copy the values from one map to the other:

a := map[string]int{
  "a": 1,
  "b": 2,
  "c": 3,
}

b := make(map[string]int)

for k, v := range a {
  b[k] = v
}

#47. Explain Go's "type embedding" feature

Embedding is a technique that allows one type to exist within another type.

All the methods and fields in the embedded type become accessible at the root of the parent type. More than one type can get embedded into another type, granting access to all the fields and methods of the initial type and the embedded types.

#48. How would you access embedded fields or methods in Go?

Embedded fields and methods exist at the root level of the type in which they get embedded. They can get accessed using parent.embeddedField or parent.embeddedTypeName.field.

#49. To create a test in Go, what do you need to do?

To create a test in Go, a test file must get created having the same name as the file under test and appended with _test. For example, to create a test file for sample.go, the test file must have the name sample_test.go.

The testing package must get imported in the test file to access the Go testing framework. Here is an example:

package main

import "testing"

func testSample(t *testing.T) {
  t.Errorf("test failed")
}

#50. What is Go's init() function and why would you use it?

init() is a special function that gets ran the first time a package gets imported and is for situations when the package needs some sort of initialization.

Part 3: The final stretch (problem solving and advanced questions)

If you get this far, then the interviewer is confident that you know how to write programs in Go.

They will now test your problem-solving ability by asking open-ended questions related to the job or to projects the company has worked on. They may also ask more technical or advanced questions about Go to determine if you understand how the language works at a finer level of detail.

#51. In Go, how would you design a thread-safe map?

Creating a structure that contains both a mutex and map can serve as a starting point for creating a thread-safe map. Receiver functions created on the map can expose read, write, and delete operations. Each operation must take out a lock on the mutex, which will provide thread-safety.

#52. How can you gracefully shutdown a Go program that is using goroutines for job processing?

To gracefully shutdown a program which is processing multiple jobs, there needs to be a way to communicate with the job processors or the goroutine managing the jobs.

To implement this, we will need:

  • A goroutine responsible to spawning extra goroutines for job processing. We'll refer to this goroutine as manager
  • A channel connecting the main thread to manager. This channel gets used to communicate shutdown status. We'll refer to this channel as term
  • Any method for manager to receive jobs, where manager is able to stop receiving jobs on demand, such as closing a channel

The main thread would spawn manager and connect term to it. The main thread would also need to intercept termination signals.

When a termination signal gets received, the main thread sends a message (it can be an empty struct) on term. When manager receives this message, it stops accepting new jobs.

Once all the jobs complete, manager then sends another signal back on term. The main thread will block attempting to read from term while it waits for the signal from manager. Once received, the main thread can end the program.

#53. How would you write a Go program to serialize multiple event sources for writing to a single file?

Since it isn't safe to have multiple goroutines writing to a single file, the log ingestion process will need to serialize all the events by using a channel.

A single goroutine responsible for reading data from the channel can then write the data serially to the log file.

#54. How can you fix a Go program using WaitGroup that won't terminate?

WaitGroup operates using a counter.

When the counter is greater than zero, calls to .Wait() will block, and when the counter is zero, .Wait() will stop blocking.

When used with goroutines, each goroutine spawned should have a corresponding .Add() which increments the counter. Each goroutine should call .Done() when they complete, causing the WaitGroup counter to decrement by one.

The .Done() call should use defer at the beginning of the goroutine, ensuring that it gets called regardless of how the goroutine ends.

#55. Can you use a loop initialization variable in a goroutine? Why or why not?

Loop initialization variables should never get used directly in a goroutine.

When using the loop initialization variable within the loop, it points to the value. This means that using it in a goroutine will cause the value within the goroutine to change. This happens because the variable in the goroutine is pointing to the value that exists in the for loop outside the goroutine.

If a goroutine requires the value of the loop initialization variable, then make a copy before sending it to a goroutine:

for i := 0; i < 10; i++ {
  value := i
  go func() {
    // use `value` here.
    // `i` will change as the loop iterates
  }()
}

#56. When would you use Go's recover function?

The recover function prevents a panic from terminating the program.

It's useful when building high-availability servers with minimal crashes. Using recover allows the developer to stop the propagation of program termination and can allow the server to continue running.

#57. Why can't you write to data in a goroutine while another is reading that data?

Reading data with one goroutine while another is writing to the same memory location results in undefined behavior.

When a goroutine writes to the same memory that another goroutine is reading from, the write may be in any part of memory. This means the reading goroutine might read the first half of the data in one state, and the second half of the data in another state (altered by the goroutine performing the write).

This would lead to data corruption.

#58. Can you read data from a single variable using goroutines? Why or why not?

Yes, it's possible to read data from a single variable using goroutines.

If any goroutine needs to write data, then the data should get protected with a mutex. If the goroutines solely read data, then a mutex isn't required.

So, how did you do?

So there you have it. 58 of the most common Golang questions and answers that you might encounter in an interview with a company using Go.

How did you do? Did you get all 58 correct? If so, I'd say you should stop studying and start interviewing!

Didn't get them all? Got tripped up on some? Don't worry about it because I'm here to help.

If you want to fast-track your Golang knowledge and interview prep, and get as much hands-on practice as you can, check out my complete developer’s guide to Golang.

Not only can you follow it from start to finish and work on fundamentals to advanced concepts, but you can also ask questions in the private Discord community.

Golang Discord Channel

More from Zero To Mastery

Top 5 Reasons Why You Should Learn Golang preview
Top 5 Reasons Why You Should Learn Golang

Golang's high performance characteristics and ease of use are 2 key factors that have made it an in-demand programming language for both new and experienced software developers. What are the other 3?

Top 9 Golang Practice Projects: From Beginner To Advanced preview
Top 9 Golang Practice Projects: From Beginner To Advanced

Looking to upgrade your Golang skills and impress potential employers? In this guide, we break down 9 Golang practice projects (with 3 you can't miss!).

How To Become A Web Developer (From Complete Beginner to Hired) preview
How To Become A Web Developer (From Complete Beginner to Hired)

Want to become a Web Developer but not sure how? Our step-by-step guide shows you how to go from beginner to hired (without wasting $1,000s on a bootcamp or degree). And we answer all your FAQ.