Representative interview topic

Coding interview: How do you preserve Go error-check order after Go 1.25 fixed delayed nil checks?

CodingHard
Offer.cc Editorial TeamPublished Updated

Question

Go 1.25 fixed a compiler bug that could delay a nil-pointer check. Explain why the error handling below is unsafe, how you would rewrite it, how you would test an upgrade, and why old compiler behavior is not a contract.

Prompt and when it applies

A service receives a pointer and an error from a file or network API. An older compiler could delay a nil check around member access, so an error path did not always fail immediately. After upgrading to Go 1.25, the same code exposes the problem according to language semantics. Explain the correct check order, implicit dereference boundaries, migration strategy, and verification.

This fits Go backend, infrastructure, and compiler-tool roles. It tests whether you connect the language specification, error-handling convention, and upgrade risk. Go 1.25 records the fix; the Go specification says evaluating a field selector through a nil pointer panics; Go compatibility guidance warns that code depending on compiler bugs can break when the bug is fixed.

File names, result combinations, test counts, and version ranges below are placeholders. Replace them with facts from a project you can defend.

What the interviewer is testing

First, do you check the operation that produced an error before using a possibly nil result?

Second, can you distinguish code that violates the specification from an old compiler that happened not to expose the error? The latter is not compatibility behavior.

Third, can you explain field selectors, pointer dereferences, method calls, and nil interfaces without collapsing their rules?

Fourth, can you design upgrade verification using static search, unit tests, integration tests, canaries, and runtime metrics together?

Fifth, can you define scope and rollback? Downgrading the compiler does not repair the error path; it can defer the failure.

Questions to clarify before answering

  • What is the returned pointer type, and can a non-nil error coexist with a non-nil value?
  • Is the access a field, method, or interface call? Nil behavior differs.
  • Which Go versions build the program, and is there a version matrix?
  • Is the old behavior observed in tests or production, or merely assumed?
  • Should failure return an error, skip work, or panic? The API contract decides.
  • Which paths are most likely to execute after the upgrade? Rank by error rate, traffic, and data type.

A 30-second answer frame

“The code uses a possibly nil result before checking the error, so the failure path is unsafe. The Go specification makes nil pointer field evaluation panic, and Go 1.25 fixed an old compiler bug that delayed the check. I would check the error immediately after the call, then access the object; add tests for nil and non-nil combinations, field access, and method calls; and use multi-version CI plus canary metrics. If historical tests rely on the old behavior, I would fix the code and tests rather than treating a compiler downgrade as a repair.”

Step-by-step deep answer

Step 1: Recover the return contract

Read the called function's documentation and implementation. Confirm whether a non-nil error permits using the object. If the contract is unclear, treat the object as unusable instead of relying on “usually non-nil.”

Step 2: Handle the error before dereferencing

Use the shape: call, immediately check the error, then read fields or call methods. This aligns control flow with the contract and makes static review effective.

go
f, err := os.Open(name)
if err != nil {
    return err
}
defer f.Close()
info, err := f.Stat()
if err != nil {
    return err
}
use(info.Name())

If an error intentionally carries a usable partial result, document the rule in the API and expose an explicit result type or comment; do not make callers guess.

Step 3: Distinguish implicit and explicit nil

A field selector may implicitly dereference a pointer, while explicit dereference also panics on nil. Pointer-receiver methods, nil dynamic values in interfaces, and nil interfaces have different rules. Name the expression before stating its runtime result.

Step 4: Assess upgrade impact

Search for object use before error checks, prioritizing file, network, parsing, database, and cache paths. Build the same test suite with Go 1.24 and 1.25, and record new panics, error rates, and request paths. Compilation alone is not semantic verification.

Step 5: Design a reversible release

Fix check order first, then canary the Go version. Monitor panics, error codes, retries, latency, and resource leaks. If the new version exposes many real defects, roll back the image to limit damage, but keep the code fixes and defect list.

Step 6: Put the rule into the toolchain

Require “check the error immediately after return” in review and static-analysis rules. Add fault-injection tests for nil results, non-nil errors, short reads, and close failures. Record which behavior comes from the specification and which was only an old implementation accident.

High-quality sample answer

The example is fictional practice material.

go
f, err := os.Open("missing")
name := f.Name()
if err != nil {
    return err
}
fmt.Println(name)

“The code accesses f.Name() before checking the error. A failed open can return a nil file object, so the field or method access is unsafe. Go 1.25 fixed a compiler defect that delayed this nil check in some older versions; the fact that old code did not immediately fail is not a guarantee. The correct form checks the error first, then uses f, and defers f.Close() on success.

I would scan similar calls, use fault injection for error and object combinations, and run race, integration, and multi-version CI. During a canary I would correlate panics, errors, retries, and latency; if a gate is crossed, roll back the runtime image while retaining the code fix. Finally, I would encode the specification rule in review checks so a compiler fix is not mistaken for a business behavior change.”

Common mistakes

  • Using the result before checking the error: treating an accident as a contract.
  • Saying only “Go 1.25 is stricter”: omitting the specification and control flow.
  • Recovering panics to hide the bug: recovery does not replace a correct error path.
  • Running only a build: semantic changes need fault injection, runtime metrics, and a canary.
  • Downgrading immediately: restoring the defect can defer the incident.
  • Mixing field, method, and interface nil rules: name the exact expression.

Follow-up questions and answers

What if an API returns a non-nil value with a non-nil error?

Follow the explicit contract. Without one, return the error and do not consume the value. For partial success, define an explicit result type and test every state.

Why did old versions not panic immediately?

The release notes attribute it to a compiler bug that delayed the nil check. A program cannot treat a bug manifestation as a language guarantee.

How do you prove the fix did not widen the outage?

Run the same fault-injection and integration tests on old and new versions, then canary while monitoring panics, errors, retries, latency, and resource closure.

When is a panic acceptable?

Only when a process-level invariant is broken and no recoverable contract exists. Ordinary I/O, parsing, and dependency failures should return structured errors.

What if the team wants the old behavior?

Explain that it still depends on an implementation defect. Fix the call order, document compatibility risk, and use a rollback only for short-term containment.

Public sources

Related questions

Related interview tool

Use Screenshot for a coding prompt

Capture the problem, then work through the constraints, solution, code, edge cases, and complexity in order.

View the tool