A shortlist in 72 hours, contributing inside two weeks, and a 30-day guarantee if the fit is wrong.
72-hour shortlist · 30-day replacement guarantee · Month-to-month
Go was designed so somebody new to a codebase could read unfamiliar code and follow it within an hour. It succeeds. That success is also the hiring trap.
Because the syntax is small and the standard library covers most of what a service needs, a competent developer from any language is productive in Go inside a fortnight. Their code compiles, passes review from anyone at the same level, and behaves correctly under light load. The problems concentrate in concurrency and error handling — the two areas where Go hands you sharp tools and almost no guard rails.
A goroutine costs almost nothing to start and nothing at all to forget about. Nothing in the toolchain warns you that one is still parked on a channel nobody will ever write to. The compiler is satisfied, the tests pass, and the process grows.
Interviewing on Go syntax therefore separates nobody, because everybody passes. What separates candidates is whether they can explain, precisely, how a goroutine exits.
Everybody passes a Go syntax round. These are the questions that actually separate people.
Who starts it, what stops it, and what happens if the reader disappears. The single most useful Go screening question there is.
Whether context.Context is threaded through the call chain and genuinely honoured, or accepted as a parameter and then ignored.
Use of %w, errors.Is and errors.As, and whether the errors they produce tell you which call actually failed.
Whether go test -race is part of how they work or something they have only heard of. We ask what it last caught for them.
Reaching for a mutex, or for nothing at all, when that is the simpler answer. Restraint is the senior signal in this language.
A live conversation in English, every time. Concurrency bugs get fixed faster when somebody can describe the failure precisely.
The signature Go failure is a service that is completely healthy right up until it is not.
A handler starts a goroutine per request to do some background work and sends the result on an unbuffered channel. On the normal path a receiver is waiting and everything is fine. Then a client disconnects, or a timeout fires, and the receiver goes away. The goroutine blocks on that send forever. It holds its stack, everything it closed over, and the request context. One per failed request. Memory climbs at a rate proportional to your error rate, which is precisely why it never appears in load testing, where nothing fails.
The naive version is caught instantly — send on an unbuffered channel with no receiver anywhere and the runtime prints fatal error: all goroutines are asleep - deadlock!. But it only detects that when every goroutine is blocked. In a real server there is always an active listener, so the detector never fires and the leak stays silent until the pod is OOM-killed.
Error handling degrades the same way, more slowly. if err != nil { return err } repeated up eight frames produces a production log line reading connection reset by peer with no indication of which call, which host, or which request. Wrapping with fmt.Errorf("fetching user %s: %w", id, err) costs one line and is the difference between a five-minute diagnosis and a lost afternoon. Teams that skipped it early almost never go back and add it.
Then there is the nil interface trap: a *MyError that happens to be nil, assigned into an error interface, produces a value that is not nil. The check passes, the caller takes the failure path, and the message is empty. It still reaches production regularly.
Mid-level (3–5 years). Writes services and handlers against an established structure. Comfortable with the standard library, HTTP, and a database driver. Uses goroutines correctly in the patterns already present in the codebase, and is less sure outside them.
Senior (5–8 years). Designs the concurrency. Decides what runs in a goroutine, how it gets cancelled, and what the service does under back pressure. Sets the error-wrapping convention and enforces it in review. Reaches for go test -race without being asked to.
Lead (8+ years). Owns service boundaries and the shared internal libraries, makes the build-versus-import calls that determine your dependency surface, and has the standing to say a particular problem does not need concurrency at all.
Backend services and APIs. The bulk of Go hiring. gRPC or HTTP, usually behind a gateway, usually with Postgres underneath.
Migrating a service off Python or Node for throughput. Worth being honest about: the win is often real and often smaller than forecast, because the database turns out to be the constraint.
Platform and internal tooling. CLIs, Kubernetes operators, controllers. This overlaps heavily with DevOps engineers, and the two are routinely confused in job descriptions.
Concurrency rescue. A service leaking memory or goroutines that nobody internally can pin down. Bounded work, usually one to two months rather than an ongoing seat.
Go alongside an existing Java or .NET estate. Common in teams breaking up a monolith. See Java and .NET.
Week one. They read the concurrency before they read the business logic. Expect questions about which goroutines are long-lived, what cancels them, and whether context.Context is threaded through the call chain or dropped at the first layer.
Weeks two to four. They own a service or a substantial feature. Somewhere in that window, expect them to run the race detector across the test suite and report what came back. On a codebase that has never had it enabled, it finds something. Expect a view on error wrapping too — it is cheap to establish early and unpleasant to retrofit.
The warning sign is enthusiasm for channels. Go's own guidance is to use a mutex when a mutex is what you mean. Reaching for a channel to protect a counter, or building a four-stage pipeline where a loop would do, produces code that is harder to reason about for no gain. Senior Go tends to look boring.
Interviewing on syntax. Go's surface area is small enough that a strong developer from any language passes a syntax round after a week of preparation. Ask instead how a goroutine gets cancelled, or what happens to a send on a channel whose reader has gone. That cannot be revised for.
Assuming Go experience implies concurrency experience. A large share of production Go is request in, query the database, response out, with no concurrency beyond what net/http does for you. That is legitimate work and it teaches none of the things that break under load. Ask what they have actually run concurrently and what went wrong when they did.
Skipping the race detector because the tests are green. Data races are timing-dependent by definition, so a passing suite tells you nothing. go test -race is a single flag and catches a class of bug otherwise discovered in production at three in the morning. A candidate who has never run it has told you something.
Ignoring the loop-variable change. Before Go 1.22, a range loop reused one variable across iterations, so a goroutine closing over it usually observed the final value. Go 1.22 made loop variables per-iteration and that entire class of bug went away for new code. Ask about it: developers who learned earlier still write the workaround reflexively, and developers who learned after often have no idea why it litters your codebase.
Rates depend on seniority, systems experience, and engagement length. Tell us the role and we will give you a firm number.
Tell us the role and we will send a shortlist within 72 hours.
Tell Us the RoleTell us the role. Three to five profiles within 72 hours.