5 exercises — goroutines vs threads, channels and the Go concurrency model, defer/panic/recover, implicit interface satisfaction, and idiomatic error wrapping with %w.
0 / 30 completed
1 / 30
A Go developer says: "We use goroutines for all the network calls." A colleague unfamiliar with Go asks what a goroutine is. What is the correct explanation?
A goroutine is Go's fundamental concurrency primitive. Launched with go func().
Key differences from OS threads: • Size: A goroutine starts at ~2 KB stack (grows dynamically); an OS thread typically needs 1–8 MB • Cost: You can run 100,000+ goroutines; OS threads are limited by the OS (typically thousands) • Scheduling: The Go runtime's scheduler (M:N scheduler) multiplexes goroutines onto OS threads. M goroutines → N OS threads.
Go concurrency vocabulary: • goroutine — lightweight concurrent execution unit (go f()) • channel — typed conduit for communicating between goroutines • M:N scheduler — many goroutines mapped onto fewer OS threads • GOMAXPROCS — number of OS threads used; defaults to CPU core count • goroutine leak — goroutines that never exit (common bug: goroutine blocked waiting on a channel with no sender)
2 / 30
A code review comment says: "You should use a channel here instead of a shared variable." In Go, what is a channel and why does it avoid the need for a mutex in this case?
Go channels are typed, concurrent-safe communication pipelines between goroutines.
Syntax: • Create: ch := make(chan int) (unbuffered) or make(chan int, 100) (buffered, capacity 100) • Send: ch <- value — blocks until receiver is ready (unbuffered) • Receive: value := <-ch — blocks until sender sends • Close: close(ch) — signals no more values will be sent
Unbuffered vs buffered: • Unbuffered: sender and receiver synchronise (rendez-vous). Send blocks until receive is ready. • Buffered: sender can proceed until buffer is full. Receiver blocks only when buffer is empty.
The Go proverb: "Do not communicate by sharing memory; instead, share memory by communicating." → Instead of a shared variable + mutex, pass ownership of data through a channel. One goroutine owns the data at a time.
Channel direction type hints: chan<- int (send-only), <-chan int (receive-only) — used in function signatures.
3 / 30
A Go function contains: defer file.Close() right after opening the file. An intern asks what defer does. What is the correct explanation?
defer is Go's built-in cleanup mechanism. It pushes a function call onto a stack that is executed in LIFO (last-in, first-out) order when the surrounding function returns.
Why it matters: Without defer, you must remember to close files/connections at every return point. With defer, you declare cleanup right after setup:
f, err := os.Open("data.csv")
if err != nil { return err }
defer f.Close() // guaranteed to run when function returns
// ... use f ...
Even if a panic occurs, deferred functions run (enabling rollback or logging).
LIFO order: if you have multiple defers, last declared runs first: defer a() defer b() → b() runs, then a()
Common uses: • defer file.Close() — close files • defer mu.Unlock() — release mutex • defer rows.Close() — close SQL result sets • defer cancel() — cancel context • defer recover() — recover from panic (must be inside a deferred function)
panic and recover: • panic(v) — stops normal execution, runs deferred functions, propagates up call stack • recover() — inside a deferred function only, halts the panic and returns the panic value
4 / 30
A Go team's code review uses the phrase "this type satisfies the interface implicitly." What makes Go's interface system different from Java or C#?
Go's interfaces are implicit (also called structural typing or duck typing). A type satisfies an interface simply by having the required methods — no explicit declaration is needed.
Example:
type Writer interface {
Write(p []byte) (n int, err error)
}
// Any type with a Write method satisfies io.Writer
// No "implements" keyword needed
Why this matters: • You can define an interface in your package and have types from other packages satisfy it — without modifying those packages • Enables powerful dependency injection and testing (pass a mock that satisfies the interface) • Keeps coupling minimal: code depends on behaviour (interface), not concrete type
Key Go interface vocabulary: • interface satisfaction — a type "satisfies" or "implements" an interface • empty interface — interface{} or any (Go 1.18+) — every type satisfies it • type assertion — v, ok := x.(ConcreteType) — check and extract concrete type from interface • type switch — switch v := x.(type) { case int: ... } • io.Reader, io.Writer — Go's most important standard library interfaces
5 / 30
Go code returns errors like: if err != nil { return fmt.Errorf("process order: %w", err) }. A developer from a Python background asks: "Why wrap the error with %w instead of just returning it?" What is the idiomatic Go answer?
Go's idiomatic error handling uses explicit if err != nil checks and error wrapping to build a chain of context.
Why wrap errors: Without wrapping: return err — caller sees "connection refused" with no context With wrapping: return fmt.Errorf("create order: load user: query database: %w", err) — caller sees exactly where in the call chain it failed
The error chain: Using %w (Go 1.13+) embeds the original error in the new one. This enables: • errors.Is(err, target) — checks if any error in the chain matches target • errors.As(err, &target) — extracts a specific error type from the chain
Example: if errors.Is(err, sql.ErrNoRows) { return ErrNotFound }
Go error handling vocabulary: • sentinel error — a predefined error value to check against (io.EOF, sql.ErrNoRows) • error wrapping — adding context to an error using fmt.Errorf("context: %w", err) • error chain — the chain of wrapped errors traversed by errors.Is()/errors.As() • custom error type — struct implementing the error interface (Error() string) • panic vs error — errors are expected failure cases; panics are truly exceptional (programming errors, nil pointer deref)
6 / 30
Sarah (Senior Engineer): "I'm seeing a lot of panics in the logging service. We need to add more error handling—specifically, we should use `recover()` to catch those errors and log them gracefully."
While recover() *can* be part of a robust error handling strategy in Go, relying solely on it without addressing the underlying cause is insufficient. Panics often indicate serious problems—like nil pointer dereferences or out-of-bounds access—that recover() can't magically fix; you still need to find and resolve the initial source of the panic.
7 / 30
During a Slack conversation about optimizing database queries in a Go microservice, Mark (Junior Dev) asks: 'What's the difference between using `SELECT *` and specifying the columns I need?'
Go's database drivers are highly optimized. Using `SELECT *` retrieves all columns from a table, even if you don't need them, increasing network traffic and potentially slowing down your application. Selecting only the necessary columns directly reduces this overhead and improves performance significantly.
8 / 30
David (Code Reviewer) writes in a comment: 'Consider using a `context.WithCancel()` to manage the timeout for this long-running operation.' What is the primary reason for this recommendation?
The core benefit of `context.WithCancel()` is providing a cancellation signal to your Goroutine. This allows you to interrupt the long-running operation if it's taking too long or encountering issues—a critical aspect of robust Go application design and resource management. Without this, you'd have no direct way to stop the process.
9 / 30
Emily (Team Lead) is explaining a Pull Request description for a new API endpoint: 'This endpoint uses `errors.New()` to return a standard error message when the user ID is invalid.' What's the key advantage of using `errors.New()` in this scenario?
The primary benefit of `errors.New()` is that it creates a simple, standard error object—a string representing an error. This consistency makes it easier for clients (consuming your API) to reliably detect and handle errors without needing complex parsing or type checking; this aligns with Go's philosophy of simplicity.
10 / 30
Ben (DevOps Engineer) is discussing a monitoring dashboard and notices high latency on requests to a particular service. The logs show frequent calls to `time.Now()` within the service's code. What's the most likely cause of this latency?
Calling `time.Now()` frequently can introduce significant overhead, especially within tight loops or frequently executed code paths. This is because `time.Now()` involves acquiring and releasing system time resources—a relatively slow operation compared to most other Go operations. It's a common performance anti-pattern.
11 / 30
Sarah (Senior Engineer): "I'm seeing a lot of panics in the logging service. We need to add more error handling—specifically, we should use `recover()` to catch those errors and log them gracefully."
While recover() *can* be part of a robust error handling strategy in Go, relying solely on it without addressing the underlying cause is insufficient. Panics often indicate serious problems—like nil pointer dereferences or out-of-bounds access—that recover() can't magically fix; you still need to find and resolve the initial source of the panic.
12 / 30
During a Slack conversation about optimizing database queries in a Go microservice, Mark (Junior Dev) asks: 'What's the difference between using `SELECT *` and specifying the columns I need?'
Go's database drivers are highly optimized. Using `SELECT *` retrieves all columns from a table, even if you don't need them, increasing network traffic and potentially slowing down your application. Selecting only the necessary columns directly reduces this overhead and improves performance significantly.
13 / 30
David (Code Reviewer) writes in a comment: 'Consider using a `context.WithCancel()` to manage the timeout for this long-running operation.' What is the primary reason for this recommendation?
The core benefit of `context.WithCancel()` is providing a cancellation signal to your Goroutine. This allows you to interrupt the long-running operation if it's taking too long or encountering issues—a critical aspect of robust Go application design and resource management. Without this, you'd have no direct way to stop the process.
14 / 30
Emily (Team Lead) is explaining a Pull Request description for a new API endpoint: 'This endpoint uses `errors.New()` to return a standard error message when the user ID is invalid.' What's the key advantage of using `errors.New()` in this scenario?
The primary benefit of `errors.New()` is that it creates a simple, standard error object—a string representing an error. This consistency makes it easier for clients (consuming your API) to reliably detect and handle errors without needing complex parsing or type checking; this aligns with Go's philosophy of simplicity.
15 / 30
Ben (DevOps Engineer) is discussing a monitoring dashboard and notices high latency on requests to a particular service. The logs show frequent calls to `time.Now()` within the service's code. What's the most likely cause of this latency?
Calling `time.Now()` frequently can introduce significant overhead, especially within tight loops or frequently executed code paths. This is because `time.Now()` involves acquiring and releasing system time resources—a relatively slow operation compared to most other Go operations. It's a common performance anti-pattern.
16 / 30
Sarah (Senior Engineer): "I'm seeing a lot of panics in the logging service. We need to add more error handling—specifically, we should use `recover()` to catch those errors and log them gracefully."
While recover() *can* be part of a robust error handling strategy in Go, relying solely on it without addressing the underlying cause is insufficient. Panics often indicate serious problems—like nil pointer dereferences or out-of-bounds access—that recover() can't magically fix; you still need to find and resolve the initial source of the panic.
17 / 30
During a Slack conversation about optimizing database queries in a Go microservice, Mark (Junior Dev) asks: 'What's the difference between using `SELECT *` and specifying the columns I need?'
Go's database drivers are highly optimized. Using `SELECT *` retrieves all columns from a table, even if you don't need them, increasing network traffic and potentially slowing down your application. Selecting only the necessary columns directly reduces this overhead and improves performance significantly.
18 / 30
David (Code Reviewer) writes in a comment: 'Consider using a `context.WithCancel()` to manage the timeout for this long-running operation.' What is the primary reason for this recommendation?
The core benefit of `context.WithCancel()` is providing a cancellation signal to your Goroutine. This allows you to interrupt the long-running operation if it's taking too long or encountering issues—a critical aspect of robust Go application design and resource management. Without this, you'd have no direct way to stop the process.
19 / 30
Emily (Team Lead) is explaining a Pull Request description for a new API endpoint: 'This endpoint uses `errors.New()` to return a standard error message when the user ID is invalid.' What's the key advantage of using `errors.New()` in this scenario?
The primary benefit of `errors.New()` is that it creates a simple, standard error object—a string representing an error. This consistency makes it easier for clients (consuming your API) to reliably detect and handle errors without needing complex parsing or type checking; this aligns with Go's philosophy of simplicity.
20 / 30
Ben (DevOps Engineer) is discussing a monitoring dashboard and notices high latency on requests to a particular service. The logs show frequent calls to `time.Now()` within the service's code. What's the most likely cause of this latency?
Calling `time.Now()` frequently can introduce significant overhead, especially within tight loops or frequently executed code paths. This is because `time.Now()` involves acquiring and releasing system time resources—a relatively slow operation compared to most other Go operations. It's a common performance anti-pattern.
21 / 30
Sarah (Senior Engineer): "I'm seeing a lot of panics in the logging service. We need to add more error handling—specifically, we should use `recover()` to catch those errors and log them gracefully."
While recover() *can* be part of a robust error handling strategy in Go, relying solely on it without addressing the underlying cause is insufficient. Panics often indicate serious problems—like nil pointer dereferences or out-of-bounds access—that recover() can't magically fix; you still need to find and resolve the initial source of the panic.
22 / 30
During a Slack conversation about optimizing database queries in a Go microservice, Mark (Junior Dev) asks: 'What's the difference between using `SELECT *` and specifying the columns I need?'
Go's database drivers are highly optimized. Using `SELECT *` retrieves all columns from a table, even if you don't need them, increasing network traffic and potentially slowing down your application. Selecting only the necessary columns directly reduces this overhead and improves performance significantly.
23 / 30
David (Code Reviewer) writes in a comment: 'Consider using a `context.WithCancel()` to manage the timeout for this long-running operation.' What is the primary reason for this recommendation?
The core benefit of `context.WithCancel()` is providing a cancellation signal to your Goroutine. This allows you to interrupt the long-running operation if it's taking too long or encountering issues—a critical aspect of robust Go application design and resource management. Without this, you'd have no direct way to stop the process.
24 / 30
Emily (Team Lead) is explaining a Pull Request description for a new API endpoint: 'This endpoint uses `errors.New()` to return a standard error message when the user ID is invalid.' What's the key advantage of using `errors.New()` in this scenario?
The primary benefit of `errors.New()` is that it creates a simple, standard error object—a string representing an error. This consistency makes it easier for clients (consuming your API) to reliably detect and handle errors without needing complex parsing or type checking; this aligns with Go's philosophy of simplicity.
25 / 30
Ben (DevOps Engineer) is discussing a monitoring dashboard and notices high latency on requests to a particular service. The logs show frequent calls to `time.Now()` within the service's code. What's the most likely cause of this latency?
Calling `time.Now()` frequently can introduce significant overhead, especially within tight loops or frequently executed code paths. This is because `time.Now()` involves acquiring and releasing system time resources—a relatively slow operation compared to most other Go operations. It's a common performance anti-pattern.
26 / 30
Sarah (Senior Engineer): "I'm seeing a lot of panics in the logging service. We need to add more error handling—specifically, we should use `recover()` to catch those errors and log them gracefully."
While recover() *can* be part of a robust error handling strategy in Go, relying solely on it without addressing the underlying cause is insufficient. Panics often indicate serious problems—like nil pointer dereferences or out-of-bounds access—that recover() can't magically fix; you still need to find and resolve the initial source of the panic.
27 / 30
During a Slack conversation about optimizing database queries in a Go microservice, Mark (Junior Dev) asks: 'What's the difference between using `SELECT *` and specifying the columns I need?'
Go's database drivers are highly optimized. Using `SELECT *` retrieves all columns from a table, even if you don't need them, increasing network traffic and potentially slowing down your application. Selecting only the necessary columns directly reduces this overhead and improves performance significantly.
28 / 30
David (Code Reviewer) writes in a comment: 'Consider using a `context.WithCancel()` to manage the timeout for this long-running operation.' What is the primary reason for this recommendation?
The core benefit of `context.WithCancel()` is providing a cancellation signal to your Goroutine. This allows you to interrupt the long-running operation if it's taking too long or encountering issues—a critical aspect of robust Go application design and resource management. Without this, you'd have no direct way to stop the process.
29 / 30
Emily (Team Lead) is explaining a Pull Request description for a new API endpoint: 'This endpoint uses `errors.New()` to return a standard error message when the user ID is invalid.' What's the key advantage of using `errors.New()` in this scenario?
The primary benefit of `errors.New()` is that it creates a simple, standard error object—a string representing an error. This consistency makes it easier for clients (consuming your API) to reliably detect and handle errors without needing complex parsing or type checking; this aligns with Go's philosophy of simplicity.
30 / 30
Ben (DevOps Engineer) is discussing a monitoring dashboard and notices high latency on requests to a particular service. The logs show frequent calls to `time.Now()` within the service's code. What's the most likely cause of this latency?
Calling `time.Now()` frequently can introduce significant overhead, especially within tight loops or frequently executed code paths. This is because `time.Now()` involves acquiring and releasing system time resources—a relatively slow operation compared to most other Go operations. It's a common performance anti-pattern.
What does the "Go (Golang) Vocabulary" vocabulary exercise cover?
This exercise tests real IT vocabulary related to go (golang) vocabulary through 30 multiple-choice questions, each built from realistic workplace sentences rather than abstract definitions.
Is this vocabulary exercise free to use?
Yes. Every exercise on CoderSlingo, including this one, is completely free — no account, sign-up, or payment required.
How many questions does this exercise have?
This exercise has 30 questions. Each one shows a real-world sentence or scenario with multiple-choice options and an explanation once you answer.
What happens after I answer a question?
You'll see immediate feedback showing whether your answer was correct, along with a short explanation of why — then a button to move to the next question, and a full results screen at the end.
Can I retry the exercise if I get questions wrong?
Yes. Once you reach the results screen, click "Try again" to reset your answers and go through the exercise from the start as many times as you like.
Do I need to create an account to take this exercise?
No account is needed. Your answers are scored in your browser during the session — nothing is saved to a server, so you can jump straight in.
Is my progress saved if I leave the page?
No — progress within an exercise resets if you navigate away or reload. Each exercise is short enough to complete in a few minutes in one sitting.
Are these vocabulary exercises connected to other topics?
Yes — browse the full vocabulary exercises hub to find related modules covering adjacent IT topics and roles.
How is this different from reading a glossary or blog article?
Exercises like this one are active recall drills — you have to choose the correct term or phrasing yourself, which builds retention faster than passively reading a definition.
Where can I find more vocabulary exercises?
Browse the full Vocabulary exercises hub for hundreds of modules covering Agile, DevOps, security, databases, architecture, and more — organised by IT role and skill.