Representative interview topic

Coding interview: How does Go 1.25 hostport analysis fix IPv6 address construction?

CodingMedium
Offer.cc Editorial TeamPublished Updated

Question

Code builds a net.Dial address with fmt.Sprintf("%s:%d", host, port). Explain why it fails for IPv6, fix it with net.JoinHostPort, and design tests for IPv4, IPv6, zones, and invalid ports.

Prompt and context

A service reads host and port from configuration and passes fmt.Sprintf("%s:%d", host, port) to net.Dial. IPv4 tests pass, but production exposes IPv6, link-local addresses, and malformed-port parsing. Fix the implementation in Go 1.25, explain net.JoinHostPort, SplitHostPort, zone identifiers, and input-validation boundaries, and discuss the new go vet hostport analyzer. The core skill is portable network programming, so this is a coding question.

What the interviewer evaluates

First, whether you recognize that IPv6 text contains multiple colons, making a bare concatenation such as 2001:db8::1:443 ambiguous.

Second, whether you use the standard library to add brackets according to network-address syntax instead of hand-written branches.

Third, whether you distinguish a host string, zone identifier, port string, and complete authority so brackets and parsing are not duplicated.

Fourth, whether tests and errors cover IPv4, IPv6, %zone, empty hosts, and invalid ports.

Fifth, whether go vet is used as a signal while code review and real connection tests verify the fix.

Questions to clarify first

  • Is host a bare IP, DNS name, bracketed authority, or a mixture?
  • Is the port an integer, string, or service name? Is 0 allowed?
  • Must IPv6 link-local addresses and zones such as %eth0 be supported?
  • Is the network TCP, UDP, or a Unix socket?
  • Should brackets and control characters in user input be rejected?
  • Can CI bind an IPv6 loopback listener?

A 30-second answer

“Bare concatenation cannot express the boundary between an IPv6 host and its port, so 2001:db8::1:443 is ambiguous. I validate the port, then call net.JoinHostPort(host, strconv.Itoa(port)); the standard library emits [host]:port for IPv6 and preserves zones. I do not add brackets manually. If complete authorities are accepted, I parse them with net.SplitHostPort before rebuilding. Tests cover IPv4, IPv6, %zone, DNS, empty input, invalid ports, and round-trip parsing. Go 1.25’s go vet hostport analyzer is a static gate, not a substitute for network tests.”

Detailed solution

Step 1: Explain the failure

An IPv4 address has one colon separating host and port; an IPv6 address contains colons itself. IPv6 network text therefore brackets the host, as in [2001:db8::1]:443. Bare concatenation provides no boundary, so a parser cannot reliably tell whether the last colon is the port separator.

Step 2: Construct with the standard library

Convert the port to a string and call net.JoinHostPort. When the host contains a colon, it adds brackets; DNS names and IPv4 remain in the ordinary form. It also preserves a zone identifier, so do not add brackets or escape text before calling it.

go
func dialAddress(host string, port int) (string, error) {
  if port < 1 || port > 65535 {
    return "", fmt.Errorf("port out of range: %d", port)
  }
  return net.JoinHostPort(host, strconv.Itoa(port)), nil
}

Step 3: Define the input shape

The API should accept a bare host, not an authority that already contains a port. If complete addresses must be supported, first call net.SplitHostPort, normalize the pieces, and rebuild with JoinHostPort. String replacement is unsafe for IPv6 and zones.

Step 4: Handle zone identifiers

An IPv6 link-local address may be fe80::1%eth0, producing [fe80::1%eth0]:443. The zone carries interface scope and must not be dropped. Validate its allowed characters and length so control characters or unexpected authority text cannot enter logs or downstream configuration.

Step 5: Validate and parse complete addresses

After construction, call net.SplitHostPort to check reversibility and assert that host and port match the inputs. It requires a port-bearing network address; missing ports, extra colons, and mismatched brackets should return actionable errors.

Step 6: Understand go vet hostport

Go 1.25 adds the hostport analyzer, which flags fmt.Sprintf("%s:%d", host, port) patterns used to construct addresses for net.Dial. It catches a common mistake, but it cannot replace configuration contracts, zone validation, port bounds, or real network-stack tests.

Step 7: Build a test matrix

Cover 127.0.0.1:443, [2001:db8::1]:443, [fe80::1%eth0]:443, DNS names, an empty host, ports 0, 65536, negative values, and already bracketed input. Where supported, connect to IPv4 and IPv6 loopback listeners; otherwise retain pure construction and split tests and document the CI capability.

A high-quality sample answer

“The defect is address syntax, not Dial: an IPv6 host contains colons, so bare concatenation cannot delimit the port. I define the API as bare host plus integer port, check 1..65535, and call net.JoinHostPort(host, strconv.Itoa(port)); I never hand-write brackets. If a complete authority is accepted, I first split it and rebuild from normalized host and port. I preserve and validate %eth0 for link-local addresses. Tests cover IPv4, IPv6, zones, DNS, invalid ports, and reversible parsing. Go 1.25 go vet hostport is a static gate, while IPv4/IPv6 loopback connections provide integration coverage.”

Common mistakes

  • Keep using fmt.Sprintf("%s:%d", ...) IPv6 host and port boundaries are ambiguous → use net.JoinHostPort.
  • Add brackets to every host manually → IPv4 and DNS formats become wrong → let the standard library decide.
  • Drop %zone link-local IPv6 may not route → preserve and validate the zone.
  • Treat a complete authority as a bare host → ports or brackets are duplicated → define the input contract and split when needed.
  • Test only IPv4 → the production failure path remains uncovered → include IPv6, zones, and loopback tests.
  • Rely only on go vet → custom input and runtime failures remain → combine unit, split, and connection tests.
  • Skip port validation → negative or overflowing values enter configuration → check the range before construction.
  • Log unvalidated raw addresses → control characters or sensitive configuration may leak → normalize and log structured fields.

Follow-up questions

Follow-up 1: Does JoinHostPort verify that host is a valid IP?

It combines host and port according to network-address syntax; it is not DNS resolution or full business validation. Whether domains, zones, or special characters are allowed belongs to the input contract and resolver path.

Follow-up 2: Why not simply use the last colon?

IPv6 compression, zones, and missing ports make string rules fragile. The standard Join/Split pair follows address syntax and reports bracket or port errors explicitly.

Follow-up 3: Can a port string be passed directly?

Yes, JoinHostPort accepts a string port. When the business input is an integer, validate its range and convert with strconv.Itoa so negative or overflowing values are not formatted into an address.

Follow-up 4: How should a zone be sanitized?

Preserve the zone semantics required by the interface, but constrain its character set and length. Do not double-encode %; log host and zone as separate structured fields when possible.

Follow-up 5: When should url.URL be used instead?

Use url.URL when constructing HTTP or HTTPS URIs with schemes, authorities, and paths. Use JoinHostPort for the host:port passed to net.Dial; do not hand-concatenate a URI.

Follow-up 6: How do you prove the fix works with real IPv6?

In an IPv6-capable CI or dedicated environment, bind a [::1]:0 listener, read its assigned port, and connect through the same constructor. Keep pure-function tests for environments without IPv6 and report the distinction.

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