skip to content

Name-based reflective dispatch in a Go agent shipped to edge devices — do you allow it, and who decides?

level: principalimportance: nice to knowfreq 16%

answer

  1. the artifact owner owns the ceiling
  2. a failing build beats a written guideline
  3. measure before allowing, not after shipping
  4. static registration keeps the linker pruning
  5. the lost compile-time check costs more than bytes

basics

~20 s

Treat it as a policy owned by whoever owns the shipped artifact, not as a per-review taste question. Require a measurement before it lands, prefer explicit registration tables that keep the linker pruning, and enforce an artifact-size ceiling in the build so the cost is visible rather than argued about.

solid answer

~50 s

The decision is not really about reflection; it is about who pays for the artifact and who can overrule the author of a convenient registry. I would set it as an explicit rule with a measured basis: whoever owns the device image sets a size ceiling enforced by the build, and any change that crosses it needs their agreement, not a reviewer's opinion. Inside that rule, default to static registration — each type's own package adds an entry to a table of function values — because it keeps the linker's dead-code elimination intact, is checked by the compiler, and survives renames. Allow name-based lookup where the alternative is genuinely unmaintainable, where the size cost has been measured on the real target rather than assumed, and preferably behind a boundary that the constrained build does not reach. Also weigh the non-size costs: a string key turns a compile-time error into a run-time one and hides the call from every tool that reasons about reachability.

go deeper

for a junior

Know that looking a method up by a name string works at run time but gives up the compiler's checking, and that on a shipped binary it can also make the file considerably larger.

for a middle

Be ready to describe the cheaper alternative concretely: a table mapping string keys to function values, populated by each package, so every reference stays static and checkable.

for a senior

Show how you would prove the cost on the real target and how you would confine dynamic lookup to code the constrained artifact does not reach.

for a principal

Own the policy end to end: name who sets the size ceiling, make it a build failure rather than a guideline, require a measurement with every request, and keep an exception path so nobody hides the mechanism.

## What the decision actually is A team proposes a registry: types register themselves by name, and at run time the agent looks up a name that arrives in a message and dispatches to it. It is elegant, it removes a switch statement, and it makes adding a type a one-line change. It also silently changes the contract with the linker, because a method name computed at run time cannot be enumerated at build time, so the linker retains exported methods and their metadata that it would otherwise have discarded — along with everything those methods reach. On a service behind a load balancer, few people would notice. On a single binary pushed to constrained devices over a slow or metered link, the artifact is a product constraint, and the person who owns that constraint is not the person writing the registry. That asymmetry is what makes this a policy question rather than a code-review question. ## Set the rule where the cost lands - **Name the owner.** Whoever owns the device image and the rollout owns the size ceiling. They set the number; they can veto a change that crosses it. - **Make the ceiling mechanical.** Record the artifact size on every build and fail the pipeline above the agreed limit. A number in a document is a suggestion; a failing build is a decision. - **Require evidence, not argument.** Any change proposing dynamic dispatch arrives with a before-and-after measurement of the real artifact for the real target. This converts a debate about elegance into a number people can weigh, and it is often surprising in both directions. - **Keep the exception path short.** There must be a way to say yes — a named approver and a recorded justification — or the rule gets routed around. ## Default to the boring alternative Explicit registration achieves nearly everything the reflective registry does: a map from a string key to a function or constructor value, with each package registering its own entries. Every reference is static, so the linker prunes normally; the compiler checks the wiring, so a rename is a build failure instead of a run-time one; and the table is greppable, so the next reader can find every implementation. If the ergonomics of writing those entries is the objection, generate them — the generated file is still statically wired. Reserve name-based lookup for cases where the set of names genuinely is not known at build time, which is a much smaller set than it first appears. ## Weigh the costs that are not bytes Size is the visible cost and rarely the worst one: - **Refactoring safety.** A method reached only by string is invisible to the compiler and to rename tooling. The failure appears in the field. - **Reachability reasoning.** Every tool that answers *is this code used* is now wrong, which affects dead-code cleanup, coverage interpretation and security review of what actually ships. - **Blast radius.** Retention is not local. One reachable lookup changes what the linker keeps across the whole program. Against that, the honest case for allowing it: a plugin surface whose implementations genuinely arrive from outside the build, a decoder over shapes that are not known until run time, or a migration where writing the static table now would block a delivery that matters more than the megabytes. ## Where to draw the line for a constrained target My default position: name-based dispatch is not permitted in the code paths reachable from the device artifact; it is permitted in tooling, tests and server-side components, where the artifact is not the constraint. If a device feature needs it, the request goes to the artifact owner with a measurement attached, and the answer is a number, not a principle. That is deliberately a *default with an override*, not a ban. A ban invites people to hide the mechanism, and a mechanism you cannot see is worse than one you priced. ## What interviewers are checking That you separate the technical fact (dynamic lookup defeats dead-code elimination) from the organisational decision (who sets and enforces the size budget); that you propose a mechanical guardrail rather than relying on reviewer vigilance; that you can name a cheaper default and say when the expensive option is still right; and that you are honest that the non-size costs — lost compile-time checking and lost reachability reasoning — often outweigh the megabytes.

  • A team argues their registry is essential and the size cost is small. How do you respond?
    Ask for the number on the real target before deciding. If it is genuinely small, approve it and record the new baseline; if it is not, the same measurement makes the case for the static table without anyone having to win an argument about taste.
  • What is the strongest non-size argument against dispatching by name?
    It moves a class of error from build time to run time. A rename or a deleted method no longer fails to compile; it fails on a device in the field, and no rename tool or reachability analysis can warn you.
  • When would you accept name-based dispatch even on a constrained target?
    When the set of names truly is not known at build time — implementations arriving from outside the build, or shapes decoded from data — and the measured cost fits the budget the artifact owner has agreed to spend.

saying these in an interview costs you the question

  • Bans reflection outright with no measurement or exception path
  • Leaves the decision to individual reviewers' judgment
  • Argues only about bytes and ignores lost compile-time checking
  • Assumes the feature team, not the artifact owner, sets the budget
  • Approves the cost without measuring the real target