skip to content

Specialization

When generic code becomes one shared body or many generated ones: per-argument compilation, hand-written overrides, and unboxed element layouts. Interviewers check you know both meanings of generic.

on this pageshow

questions

16

A decoder body is written once against a placeholder sample type: which two strategies can a toolchain use to make it run?

level: middleimportance: must knowfreq 58%

answer

  1. two ways to make generic code run
  2. many copies or one body
  3. who supplies the type's operations
  4. direct inlinable call versus table entry
  5. build-time copies versus run-time indirection

basics

~20 s

A toolchain can generate a separate copy of the body for each type argument, with every operation resolved to a direct call, or compile one shared copy that receives the type's operations as a passed-in table and calls them indirectly.

solid answer

~40 s

Two strategies. **Generate a body per type argument**: the build emits one copy of the decoder for every sample type it sees, substitutes that type throughout, and each copy knows the layout, so every operation becomes a direct call the optimiser can inline. **Keep one shared body**: the decoder is compiled once against a representation it does not know and receives the type's operations as a passed-in table, so each operation is an indirect call through that table. The first pays at build time — one compilation and one emitted copy per argument — and the second pays once per operation while running. The same source compiles either way: which one you get is a property of the platform, not of the program text.

code

pseudocode · 20 lines
pseudocode
// one generic body, written once
function decodeAll(window, T):
    out = empty list
    for each slot in window:
        out.append(T.read(slot))        // an operation of the placeholder type
    return out

// strategy A - the build emits one copy per type argument
function decodeAll_for_status(window):
    out = empty list
    for each slot in window:
        out.append(read_status(slot))   // direct call, can be inlined
    return out

// strategy B - one body, operations arrive in a table
function decodeAll_shared(window, ops):
    out = empty list
    for each slot in window:
        out.append(ops.read(slot))      // indirect call through ops
    return out

go deeper

for a junior

Recall that the same generic body can end up as many compiled copies or as one shared copy, and that something has to tell a shared copy how to operate on the value it is handed.

for a middle

Explain how each strategy reaches the type's operations: substitution and direct calls inside a generated copy, an entry in a passed-in table in a shared one. Say where each strategy sends the bill.

for a senior

Show you can work out which strategy a platform used from what is observable: build time scaling with the number of argument types, duplicated near-identical bodies, or an argument type accepted that the build never saw.

for a principal

Treat the choice as a distribution decision. Generated copies bind consumers to compiling your generic bodies; a shared body fixes a per-operation indirection into every artifact you publish.

A telemetry decoder on a field controller is written once: it walks a byte window and turns each slot into a sample of some placeholder type `T`, calling a small set of operations on `T` — read a value out of a slot, report how wide it is, decide whether it is valid. The body names no concrete sample type. Machine instructions cannot be that vague: they need a width, a field offset, and the address of the code implementing each operation. Something between the source and the running controller has to supply what the body left open, and there are two families of answer. Real platforms also mix them. ## Generating a body per type argument The build finds every distinct type argument the program uses the decoder with and emits a separate compiled body for each, with the placeholder replaced throughout. This is **monomorphisation**: many single-type bodies replace one many-type body. - Inside each copy the argument is concrete, so widths, offsets and alignments become constants baked into instructions. - Every operation has a known target, so the call is direct and the optimiser may inline it, fold its result, hoist an invariant check out of the loop, or vectorise the loop around it. - Each copy can be optimised differently — the copy for a one-byte status word and the copy for a timestamped pair need not end up looking alike. - The set of copies is fixed by what the build could see. A type argument that shows up later has no copy, and none appears without another compiler run. ## Keeping one shared body The alternative compiles the decoder exactly once, against a representation it does not know, and hands it the type's behaviour as data: a **table of operations** for the argument in use — read, width, validity. The body calls through that table. - One body is emitted no matter how many argument types exist. - The body either handles values through a uniform, fixed-size representation it can copy without understanding, or receives their size and layout alongside the operations. What it cannot do is bake a width into its instructions. - Each operation is an indirect call: load the entry, jump to whatever address is there. - The table is chosen **outside** the body. Where a call site knows the concrete argument, the compiler selects the table there and passes it in; where the argument is only known later, the table travels with the value. - A type argument invented after the body was compiled works, because nothing in the body was ever specialised to a type. ## Where each one pays | | one body per argument | one shared body | |---|---|---| | work at build time | one compilation per distinct argument | one compilation in total | | emitted code | one copy per argument | one copy | | cost per operation | direct call, inlinable | indirect call through the table | | new argument type after the build | needs another copy generated | works unchanged | | what a distribution must contain | the body in re-compilable form | a finished binary | Neither column is a verdict. On a controller with a hot per-sample loop and three known sample types, the left column is close to free and buys real speed; on one that must accept sample types from modules loaded in the field, the right column is the only column that works. ## The strategy belongs to the platform, not the source The same decoder source compiles either way. Some platforms pick one for everything; some let an individual declaration be marked for generation while the rest stay shared; some emit a shared body and let a run-time optimiser produce a specialised copy after observing that the same table keeps arriving. That is why the question is usually phrased as *which one did your platform choose* — the answer is not visible in the program text. You can usually tell from outside: 1. Build time that grows with the number of distinct argument types rather than with the amount of source points to generation. 2. Many near-identical bodies in the emitted image, differing only in baked-in widths, point to generation. 3. One body serving every argument, with small tables of operations near the call sites, points to the shared strategy. 4. A type argument accepted that the build could not have seen settles it: only a shared body can do that. ## Why this gets asked It is the cleanest test of whether a candidate understands that *generic* is a source-level idea with more than one realisation underneath. Someone who knows one platform well will often assert that generic code simply *is* copies, or simply *is* one shared body, and be surprised the other exists. The answer that lands names both, says who supplies the type's operations under each, and puts the bill in the right place: at build time and in image size on one side, at every operation on the other.

  • Where does the operations table come from in the shared-body strategy?
    From outside the body. When the call site knows the concrete argument type, the compiler selects the table there and passes it in beside the value. When the type is only known later — a value handed in by a module loaded at run time, say — the table travels with the value. The body never picks a table; it only calls through the one it was given.
  • Can a single program use both strategies at once?
    Yes. Several toolchains generate copies for a marked or heavily used set of arguments and fall back to one shared body for the rest, and a run-time optimiser can emit a specialised copy of a hot shared body after the fact. The two are ends of a range, not an either/or that must be settled program-wide.
  • Does the shared body need to know how wide a value of the placeholder type is?
    It needs that from somewhere. Either every value travels in a uniform fixed-size representation the body can move without understanding it, or the size and layout are passed in alongside the operations. What the shared body cannot do is bake a width into its instructions — which is precisely what each generated copy does.

A publisher can print a separate edition for every language it anticipates — fast to read, but useless for a language nobody printed — or send one interpreter who works from whichever phrasebook is handed over at the door, which covers any language at the price of a lookup per sentence.

saying these in an interview costs you the question

  • Says there is only one way: generic code always becomes copies
  • Thinks a shared body still knows the concrete argument type inside it
  • Believes the shared body finds its own operations table by inspecting the value
  • Says the compilation strategy is stated in the program's source
  • Assumes generating copies is free because it happens at build time
open as a page

An archiver's generic record writer also ships a hand-written body for one chosen type argument - what decides which body runs?

level: middleimportance: must knowfreq 58%

basics

~20 s

The most specific matching definition wins. Where the type argument becomes known, every candidate whose pattern that argument satisfies is gathered, and the narrowest is selected; the general body is the fallback for arguments nothing narrower claims.

open as a page

A tile renderer stores coordinate points in a generic container; why does each point cost an allocation and an extra memory hop?

level: middleimportance: must knowfreq 58%

basics

~20 s

A shared container body is compiled once against one uniform slot shape — a reference — so an unboxed point cannot sit in the buffer directly. Each point becomes a separate heap object and the slot holds that object's address.

open as a page

Compiling a separate body for each type argument makes calls faster - what does the build pay for that speed?

level: middleimportance: must knowfreq 52%

basics

~20 s

Per-argument body generation costs build time (a fresh compile per distinct argument), artifact size (every generated body ships), and instruction-cache pressure (more distinct code competing for the same cache). The gain is a direct, inlinable call with no run-time indirection.

open as a page

In your archiver, the hand-written body for one record type drifted from the general body - why does the bug appear only at some call sites?

level: seniorimportance: must knowfreq 48%

basics

~20 s

Because which body runs is decided by what each call site statically knew about the type, not by the value. A site that fixed the argument takes the drifted body; a site still holding it generically can keep running the general one.

open as a page

Inside one shared generic body that reaches its type's operations through a passed-in table, what does each operation cost?

level: middleimportance: should knowfreq 45%

basics

~10 s

Each operation becomes an indirect call: the body loads the entry from the table and jumps to it, so the optimiser cannot inline the target, fold its result, or specialise the loop around it.

open as a page

Two specialized bodies of an archiver's writer both match one call and neither is narrower - what does the compiler do?

level: middleimportance: should knowfreq 40%

basics

~20 s

It rejects that call as ambiguous. Specificity is a partial order, so when each matching body claims arguments the other does not, no candidate is narrowest and there is nothing to select - the error lands at the call, not at either declaration.

open as a page

What can a container body generated for one unboxed element width do with its buffer that a shared body cannot?

level: middleimportance: should knowfreq 46%

basics

~20 s

It can store the values themselves back to back in one block. Knowing the element's exact width lets element i live at index times width, so there is no wrapper, no header and no address to follow.

open as a page

A precompiled generic decoder ships as a binary: under which strategy can a caller use a sample type the library's build never saw?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Only the shared-body strategy: one compiled body plus a table supplied at the call serves any argument. Generating a body per argument needs a compiler run for each new type, so the generic body must ship in re-compilable form.

open as a page

Before flattening a renderer's point buffer, how do you establish that wrapping each point is what costs the time?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Build the flat baseline and compare against it under the real access pattern. Measure heap bytes per element, allocation rate and traversal time at several buffer sizes, because footprint and locality separate only when the working set outgrows cache.

open as a page

A hot loop specialised into many generated bodies runs slower than before - what could have eaten the win?

level: seniorimportance: should knowfreq 40%

basics

~20 s

More distinct code doing the same work. Each generated body adds instructions competing for a finite instruction cache, so a loop that once ran entirely from cache now misses on it, and those misses cost more than the removed indirection saved.

open as a page

Your controller platform can compile generic bodies either way, with one default for every team: what decides which default you set?

level: principalimportance: should knowfreq 28%

basics

~20 s

Whether the set of type arguments is closed at build time. A closed, known set favours a body generated per argument; arguments that arrive later force one shared body, because nothing can generate a copy after the build.

open as a page

Your platform offers no flattened generic storage; how do you decide whether the team maintains hand-duplicated point buffers per element kind?

level: principalimportance: should knowfreq 30%

basics

~20 s

Weigh a measured gap on a real hot path against owning the same algorithm N times forever. Duplicate only the element kinds that are actually hot, generate the copies from one source rather than typing them, and write down the condition for deleting them.

open as a page

Your client app ships under a hard download-size budget - how do you decide where per-argument body generation is worth it?

level: principalimportance: should knowfreq 35%

basics

~20 s

Per hot path, with measurements, rather than once for the codebase. Budget the artifact, attribute its bytes to instantiations, generate bodies only where a measured hot path pays for them, and re-check both the size and the speed every release.

open as a page

Ten type arguments each generate a body, yet the artifact grows far less - which toolchain step explains that?

level: seniorimportance: nice to knowfreq 28%

basics

~20 s

Deduplication of identical generated bodies. Where two arguments compile to the same instructions, the toolchain can emit the code once and point both instantiations at it, so the artifact carries one copy instead of ten.

open as a page

Other teams want to register specialized bodies for your shared archiver writer - what policy do you set?

level: principalimportance: nice to knowfreq 28%

basics

~20 s

Decide first whether specialization is part of the published contract or an internal optimisation. If outside teams may add bodies, publish the observable behaviour every body must reproduce, restrict overrides to cost and layout, and make a conformance suite the price of admission.

open as a page