
Go, the language, was designed so that a competent engineer coming from Java or Python can read it on day one and write it in week one. That is a genuine achievement and it wrecks your interview.
Because the things that go wrong in production Go are not things you can see by reading a page of it. A goroutine that never returns looks exactly like a goroutine that returns. A context that is not threaded through looks tidier than one that is. A mutex protecting the wrong thing compiles, passes review and is fine for eleven months.
So a Go assessment that asks somebody to write a function is testing the part of Go that takes a week. Here is how to test the part that takes two years.
Concurrency lifecycle, not concurrency syntax. Anybody can write go doThing(). The question is who stops it, what happens to it when the request is cancelled, and where its error goes. Most real Go bugs live here: leaked goroutines, a WaitGroup counted wrong, a send on a channel nobody is receiving from any more, a worker pool that deadlocks only when the queue is full.
Context discipline. Whether context.Context is threaded to the places that do IO or dropped at the first convenient boundary, whether cancellation actually cancels anything, whether a timeout on the handler means anything downstream. This is the difference between a service that sheds load and one that falls over holding every request it ever accepted.
Error handling as design. Not "do they check err". Whether errors are wrapped with something a person on call could use, whether a sentinel is compared with errors.Is rather than a string match, whether an error crossing a package boundary still means anything. Go pushes this decision onto the author more than most languages, which is exactly why it separates people.
Reading and interfaces. Small interfaces defined by the consumer, accepting an interface and returning a struct, and above all whether they can navigate somebody else's package layout. Most of a Go job is reading Go somebody else wrote in a hurry.
Notice that none of these four are visible in a function-writing exercise, and all four are visible within twenty minutes on a running service.
These are built to run on a real machine with a real service, because every one of them depends on the program actually executing.
1. The service that gets slower and never recovers. Give them an HTTP service that handles requests fine, then degrades under load and never comes back, because each request spawns a goroutine that blocks on a channel read after the client has gone. pprof is available. Ask them to find out why memory climbs.
What you learn: whether they reach for the goroutine profile at all. Somebody who has run Go in production types /debug/pprof/goroutine?debug=1 almost reflexively and reads the stack counts. Somebody who has only written Go starts reading the handler code top to bottom. Both may get there. The order tells you which one they are.
2. The cancellation that does not cancel. A handler with a five second timeout that calls a downstream service, and the downstream call ignores the context. Load it and watch the goroutine count climb even though every request has returned. Ask why the timeouts are not helping.
What you learn: whether they have a real model of what a context does. A surprising number of engineers believe a timeout on a handler stops the work. Watching someone discover that it does not, live, is very informative.
3. The race that only shows under -race. A cache with a read path that looks safe and is not. It passes tests. Ask them to make the tests trustworthy.
What you learn: whether -race is in their fingers, and what they do with the report. The report is easy to read and points at two lines. The judgment is in the fix: a mutex, a sync.Map, a channel, or restructuring so the shared state is not shared. All four are defensible and the reasoning separates them.
4. Read and extend an unfamiliar package. Hand them a repository of a few thousand lines and a small feature request that touches three files. No bug, no trick.
What you learn: how they navigate, whether they follow the existing idioms or import their previous language's patterns, and whether they notice the codebase's conventions before overwriting them. This is the closest thing to the actual job.
Every one of those four scenarios requires running the program: profiling it, loading it, racing it, building it. In a browser-based code pad the candidate can describe what they would do, and describing is a different skill that correlates with interview practice rather than with the work.
In EasyEnv the candidate gets a real box with the service, the toolchain, pprof, a load generator and the repository already there. They work the way they would on a Tuesday, and the session is recorded, so the review is over what they actually ran and in what order. For Go specifically that record is the assessment, because the interesting part is the search: how quickly the goroutine profile occurs to somebody is a much stronger signal than whether they eventually explain leaks correctly.
It also lets you allow AI assistance honestly. A model will explain goroutine leaks well in general and cannot tell you which of the seventeen goroutines in this dump is the one that matters. Watch which they check first.
Has written Go. Correct syntax, err != nil everywhere, goroutines started and never accounted for, context taken as a parameter and not passed on. Reads code linearly. This is a competent engineer who has not yet run Go under load, and that is a training gap, not a no.
Has shipped Go. Reaches for the goroutine profile early. Talks about who owns a channel's close. Wraps errors with %w without being prompted. Notices a missing defer cancel() while reading past it. Has a preference between a mutex and a channel and can say what it depends on.
Has been on call for Go. Everything above, plus asking what the deploy looks like, whether there is a readiness probe, what happens on SIGTERM, and whether the pool size is tuned for the downstream service or copied from a blog post. These questions are the tell, and no assessment can prompt them without giving them away.
Generics, unless the job has them. Micro-optimisation of allocations, unless the job is that. Anything about the runtime scheduler's internals, which is trivia unless somebody is writing a profiler.
And do not ask them to implement a data structure. It is a fine exercise in some languages and in Go it mostly tests whether they enjoy typing.
Reading legacy code live covers the fourth scenario in more depth, and how to assess debugging skill covers the search behaviour that the first three are really measuring.
When your Go service last fell over, would the version of your interview you run today have found the person who fixed it?
Run live coding sessions and take-home challenges in real production environments. Watch sessions back, score consistently, and hire with confidence.
More posts you might like
Anyone can draw the migration diagram. Migrations fail at the cutover, at 2am, when half the traffic is on the new platform and the old database is still taking writes. Here are four tasks that test the parts that actually go wrong.
Hiring someone to build with models is not the same as hiring someone who codes with one. Here are four tasks on a running LLM service, output that fails to parse, a change nobody can prove is an improvement, a context window that grows until it costs real money, and a provider having a bad afternoon.
Read moreYou cannot ship an app in a 60 minute interview, so mobile hiring falls back on algorithm puzzles and a portfolio walkthrough. Here are four tasks inside a real project, a leak that survives the screen, state lost on rotation, a slow network, and a crash only on resume.
Read more