Skip to content

Boarding pass · Go Intermediate

Go · Intermediate interview

An AI mock interview for Go developers with a couple of years' experience. Go deeper into interfaces and generics, concurrency patterns and the sync package, error wrapping, and idiomatic Go.

20–30 minEstimated6 questionsTailored live12 model answersTo study
  • Voice interview
  • Live feedback
  • Browser-only
  • No installation required

Your report · sample

AI Readiness Score

/100
Content score English score

Finish the mock and get this report — scored on content and English, with concrete tips and a model answer for every question, plus a study plan.

Your flight path

Five steps to interview-ready.

From a quick warm-up to a personalised study plan — here's the whole loop, and what you get at each gate.

  1. Step 01

    Preparation

    Skim the topics and warm up — you're briefed before takeoff.

  2. Step 02

    AI Interview

    A realistic AI interviewer asks tailored questions — answer by voice or text.

  3. Step 03

    Instant Score

    Finish and get a 0–100 readiness score across content and English.

  4. Step 04

    AI Feedback

    Concrete tips and a rewritten model answer for every response.

  5. Step 05

    Study Plan

    A personalised plan targets your weakest areas — so the next run scores higher.

Preview the AI demo

See one answer scored, end to end.

The whole loop on sample data — question, your answer, instant feedback and a study plan. Hover to pause.

aevrofy.com/session · live demoPlaying

Interviewer · Question 1

“Tell me about yourself.”

Who it's for

  • Developers writing Go services
  • Engineers comfortable with goroutines
  • Anyone targeting mid-level Go roles
  • Devs sharpening before interviews

What you'll practice

  • Interfaces, type switches and generics
  • Channels, select and the sync package
  • Error wrapping and custom errors
  • context and idiomatic patterns

Topics covered

What this level expects.

Interfaces & types

  • any & type switches
  • Embedding
  • Generics

Concurrency

  • Buffered channels
  • select
  • sync primitives

Errors

  • Error wrapping
  • Sentinel vs custom
  • panic vs error

Idioms

  • context.Context
  • defer gotchas
  • Struct tags

Practice questions

12 questions, with model answers.

Read them, then run the live mock to answer out loud and get scored. Tap any question to reveal a model answer.

01How do the empty interface (any) and type assertions/switches work?

any (alias for interface{}) holds a value of any type, which you recover with a type assertion v, ok := x.(string) or a type switch to branch on the dynamic type. It's how you write generic-ish code pre-generics, but it loses static typing, so you prefer concrete types or generics where possible. The ok form avoids a panic when the assertion fails.

switch v := x.(type) {
case string: useString(v)
case int: useInt(v)
}
02What is embedding, and how does it differ from inheritance?

Embedding includes one type inside another (a struct or interface) so the outer type promotes the embedded type's fields and methods as if they were its own. It's composition, not inheritance — there's no subtype polymorphism, just method promotion and delegation. It's how Go reuses behaviour and builds bigger interfaces from smaller ones.

03What do generics add to Go and when do you use them?

Generics (type parameters, added in Go 1.18) let you write functions and types that work over a set of types with compile-time safety, constrained by interfaces — e.g. a Map/Filter or a generic container. You use them to avoid duplicating code per type or resorting to any with assertions. The guidance is to use them when you'd otherwise copy-paste or lose type safety, not everywhere.

func Map[T, U any](s []T, f func(T) U) []U { ... }
04What's the difference between a buffered and unbuffered channel?

An unbuffered channel has no capacity: a send blocks until a receiver is ready, so it's a synchronization point (a handoff). A buffered channel holds up to N values; sends only block when the buffer is full and receives when it's empty. You use unbuffered for strict handoff/synchronization and buffered to decouple producer and consumer speeds up to a bound.

05What does the select statement do?

select waits on multiple channel operations and proceeds with whichever is ready, picking randomly among several ready cases. With a default case it becomes non-blocking; combined with a context's Done channel or a timeout, it implements cancellation and timeouts. It's the core tool for coordinating multiple channels.

select {
case v := <-ch: use(v)
case <-ctx.Done(): return ctx.Err()
}
06When would you use sync primitives instead of channels?

Channels are great for passing ownership of data and coordinating goroutines, but for simply protecting shared state a sync.Mutex is clearer and faster. sync.WaitGroup waits for a group of goroutines to finish; sync.Once runs initialization exactly once; atomic ops handle simple counters. The rule of thumb: channels for communication/ownership transfer, mutexes for guarding shared memory.

07How does error wrapping with %w work?

fmt.Errorf with %w wraps an underlying error while adding context, building a chain. errors.Is checks whether any error in the chain matches a target (e.g. sql.ErrNoRows), and errors.As extracts a specific error type from the chain. This lets you add context at each layer without losing the ability to inspect the original cause.

return fmt.Errorf("load user: %w", err)
if errors.Is(err, sql.ErrNoRows) { ... }
08What's the difference between sentinel errors and custom error types?

A sentinel error is a predefined error value (var ErrNotFound = errors.New(...)) you compare against with errors.Is — simple but limited to identity. A custom error type implements the error interface and can carry fields (codes, context), extracted with errors.As. You use sentinels for simple known conditions and custom types when callers need structured detail.

09When should you panic instead of returning an error?

Return an error for anything a caller might reasonably handle — missing files, bad input, network failures. Reserve panic for programmer errors and truly unrecoverable states (impossible conditions, failed required initialization at startup), or inside a package where you recover at the boundary. Libraries should almost always return errors, not panic, so callers stay in control.

10What is context.Context used for?

context carries cancellation signals, deadlines/timeouts, and request-scoped values across API boundaries and goroutines. You pass it as the first parameter, and downstream code selects on ctx.Done() to stop work when the request is cancelled or times out. It's how Go propagates 'stop' through a call tree — essential for servers to avoid leaking work on abandoned requests.

11What are common defer gotchas?

Deferred calls evaluate their arguments immediately but run at function return, so capturing a loop variable or a value that changes can surprise you. Deferring inside a loop accumulates calls until the function returns (a resource leak for long loops) — better to wrap the body in a function. And deferred functions can modify named return values, which is occasionally useful but easy to misuse.

12What are struct tags and how does JSON marshaling use them?

Struct tags are string annotations on fields that libraries read via reflection. encoding/json uses json:"name,omitempty" tags to control the JSON key, omit empty values, or skip a field with json:"-". They're how you map Go field names (exported, CamelCase) to external formats (snake_case JSON) without changing your code.

type User struct { Name string `json:"name,omitempty"` }

Preparation tips

Walk in ready.

  • Know when select with default avoids blocking
  • Practise errors.Is and errors.As
  • Be able to explain a common defer-in-loop bug
  • Pass context as the first argument, never store it

Ready for the real thing?

Run a 6-question Go mock interview, answer out loud, and get a readiness score with tips and model answers.

More intermediate interviews

Aevrofy · Go intermediate interview prep