skip to content

A decade-old rostering codebase parameterizes its collections one module at a time - what does discarding the arguments let the team avoid?

level: seniorimportance: should knowfreq 42%

answer

  1. the old artifacts are already deployed
  2. both call directions must keep linking
  3. no flag day, no coordinated rebuild
  4. every seam is an unchecked inlet
  5. convert data owners before consumers

basics

~20 s

It avoids a coordinated rebuild. Because the compiled shape does not change, the untyped modules already in production keep calling the parameterized ones and being called by them, so each module can be converted on its own schedule.

solid answer

~40 s

The payoff is that every intermediate state of the migration is a program that builds, links and runs. A parameterized roster collection has the same run-time shape the old bidding module was compiled against, so that module needs no rebuild, no conversion layer and no flag day. The team converts one module, ships it, and converts the next. What the discard does **not** buy is safety at the seams: a collection handed in from an unparameterized module has been checked by nobody, and the compiler will happily believe its declared element type afterwards. So the sensible order is to parameterize inward from the modules that own their data, and to treat each remaining boundary as a place where the guarantee stops.

code

pseudocode · 12 lines
pseudocode
// converted module, compiled today
function assignNight(roster: Collection<Shift>)
    for each shift in roster
        shift.markNight()          // believes every element is a Shift

// unconverted module, compiled eight years ago
function legacyFill(roster)
    add(roster, "NIGHT")           // a text value, not a Shift

roster = newCollection<Shift>()
legacyFill(roster)                 // links and runs; nothing checks
assignNight(roster)                // fails here, far from the insertion

go deeper

for a junior

The point to remember is that converting one module does not force the others to change, because the compiled shape of a parameterized collection is the same as the old one.

for a middle

Explain both halves: why the old artifacts keep linking in both directions, and why a collection arriving from an unconverted module carries no verified element type.

for a senior

Demonstrate the sequencing judgment - convert data owners before consumers, make the seams explicit and few, and treat boundary warnings as the backlog that measures real progress.

for a principal

The lead's call is how much of the organisation this migration is allowed to touch, and whether a guarantee that is real inside modules and absent at the seams is worth shipping before the seams are closed.

## The situation A rostering system has run for a decade. Rosters, shifts, bids and swap requests all travel through unparameterized collections. The team wants declared element types, but the codebase is large, several modules are owned by other teams, and some artifacts are deployed on their own release trains. The question is whether the migration can be done in pieces, and the answer depends entirely on what the platform did with the type argument. ## What the discard makes possible Because the argument is verified and then left out, a parameterized collection has the same compiled shape as the unparameterized one it replaces. Three consequences follow, and together they are the whole reason the migration is tractable: - **No coordinated rebuild.** Modules nobody has touched keep linking against the converted ones. There is no release in which every artifact must change at once. - **Both call directions keep working.** The converted roster module can pass its collection into an unconverted routine, and that routine can hand a collection back. - **Every intermediate state ships.** The team is never holding a half-migrated tree that does not build, which is what makes the work interruptible. ## What it does not buy The guarantee the team is buying is static, and it stops at the edge of what the compiler saw: 1. A collection produced by an unconverted module and received as a declared parameterized type has had **nothing** verified about its contents. 2. From that point on the compiler reasons as though the declaration were true, and the code downstream is compiled on that belief. 3. The failure appears wherever an element is finally read and found to be something else - a different module, possibly a different team, certainly a different stack trace from the insertion that caused it. So the migration converts *checking*, not *contents*. Every remaining boundary is an unverified inlet, and the number of those inlets is the honest measure of how far along the migration really is. ## Sequencing it 1. **Convert the owners of data before the consumers.** A module that creates a collection and fills it can be made genuinely correct on its own; a consumer converted first merely declares a belief about what it is handed. 2. **Make the remaining boundaries explicit.** A small number of named entry points, each doing whatever validation is affordable, beats an unknown number of implicit ones. 3. **Treat the compiler's warnings at those seams as the migration backlog**, not as noise to silence - each one marks a place where the new guarantee has a hole behind it. 4. **Re-check assumptions when a boundary module is finally converted**, because a declared element type that was never true will only surface once something actually depends on it. ## The two boundary directions are not equally risky | Direction | What the compiler can say | Practical risk | |---|---|---| | Parameterized code calls unparameterized code | it can flag that its guarantee ends at this call | moderate - the call is visible in converted source | | Unparameterized code hands a collection back in | nothing about the contents was checked | higher - the belief is adopted silently and travels | ## The counterfactual If the platform had kept the arguments at run time instead, this migration would look different in kind. A parameterized collection would be a distinct run-time entity, so artifacts compiled before the feature existed could neither produce nor accept one. The team would need conversion code at every boundary, or a rebuild of every module in one release - and the modules owned by other teams would be on the critical path. That is precisely the bill a platform with a large installed base refuses to send its users, and it is why the compatibility choice and the migration story are the same decision seen from two ends. ## What a strong answer sounds like Name the avoided cost first - the flag-day rebuild - then show you know what you are still carrying: a guarantee that is real inside converted modules and absent at every seam, plus a sequencing plan that shrinks the number of seams over time rather than declaring victory when the last declaration is written.

  • Which call direction across the boundary is the riskier one?
    The inbound one. When converted code calls an unparameterized routine, the call is visible in source the team owns and the compiler can flag that its guarantee ends there. When a collection comes back the other way, the declared element type is adopted silently and travels downstream, so the mistake is found by whoever finally reads an element.
  • What would this migration have cost on a platform where the arguments survive to run time?
    Probably a coordinated rebuild. If a parameterized value is a distinct run-time entity, artifacts compiled before the feature can neither produce nor accept one, so every boundary needs conversion code or every module must be rebuilt in the same release - including the ones other teams own.
  • How would you measure progress on a migration like this?
    By the number of remaining unverified inlets, not by the number of declarations added. A module full of parameterized signatures that still receives its collections from unconverted code has changed what the compiler believes without changing what is true, so counting seams is the honest metric.

saying these in an interview costs you the question

  • Thinks every module must be converted in one release to link
  • Believes a declared element type is re-verified when the collection crosses the boundary
  • Treats compiler warnings at the seams as noise to silence
  • Says the untyped modules need a conversion layer at every call
  • Assumes parameterizing a module makes its inputs correct rather than its checking stricter