You inherit an orders table that stores the customer's address on every order row, and rows for the same customer now disagree with each other. How would you assess the damage and fix the design without losing information?
answer
- snapshot or stale copy, decide first
- mutability is the test
- count distinct per customer to size drift
- name an authority, flag the unresolvable
- close every write path, verify after
basics
~20 sFirst decide whether the column is a point-in-time snapshot of where the order shipped, which is legitimate, or a stale copy of the current address, which is a defect. Quantify the drift, pick an authoritative source, extract addresses to their own table with a foreign key, and close the write paths that let copies diverge.
solid answer
~60 sStart with intent, because the same column can be correct or broken depending on meaning. If it records where this order actually shipped, disagreement between orders is expected and correct, and the fix is naming and documentation, for instance ship_to_address_at_order, plus making it immutable. If it is meant to be the customer's current address, every copy is redundant and the disagreement is corruption. Then measure: group by customer and count distinct addresses to size the problem, and check whether drift correlates with a particular code path or import. Then restructure: a customer_address table holding each address once, orders referencing it by foreign key, and, if the business needs history, an explicit immutable snapshot column populated at order creation. Backfill by choosing an authoritative value, usually the customer record or the most recent order, and flag rows you cannot resolve for manual review rather than guessing. Finally close the loop: remove or fix every write path that could set one copy independently, and verify with a query that no customer has conflicting current addresses.
code
sql · 8 linesSELECT customer_id,
count(DISTINCT address) AS variants,
min(created_at) AS first_seen,
max(created_at) AS last_seen
FROM orders
GROUP BY customer_id
HAVING count(DISTINCT address) > 1
ORDER BY variants DESC;go deeper
Recognise the duplication and propose extracting addresses into their own table referenced by a foreign key.
Add the measurement step and the backfill authority, and note that a historical shipping address is a legitimate immutable copy.
Lead with intent and mutability, sequence assess, decide, migrate, close write paths, verify, and refuse to guess on ambiguous rows.
Frame it as ownership of an invariant plus a staged migration with reversibility, explicit sign-off on the backfill rule, and monitoring so regressions surface immediately.
## Step one: is this redundancy at all? The critical distinction, and the thing interviewers are testing, is between a duplicated fact and a recorded historical fact. If the column answers where did we ship order 1234, then it is not a copy of anything. It is an independent fact about the order, captured at a moment in time, and it must not change when the customer moves. Two orders from one customer disagreeing is then correct data, not drift. If the column answers where does this customer live, then it is a copy of a fact owned by the customer, stored once per order. That is redundancy, and disagreement means some copies were updated and others were not. So the first action is not a query, it is a conversation, plus reading the code paths that write the column. Do any of them update the address on historical orders when the customer edits their profile? If yes, someone intended it as a current-address copy. If nothing ever updates it after insert, it is behaving as a snapshot. ## Step two: quantify Before changing anything, size the problem. Count customers with more than one distinct address, look at whether the variants are genuine relocations, formatting differences, or partial-update residue, and check whether the customer table has an address of its own that agrees or disagrees with the order copies. Also look at when the divergent rows were written; drift usually clusters around a specific release or import. This matters because the remediation differs. Formatting variance is a normalization-of-values problem. Genuine relocation history means the column really was a snapshot. Partial-update residue means a broken write path exists and will keep producing bad data until you fix it. ## Step three: target design The general shape: one home per fact. - customer_address(address_id, customer_id, line1, city, postcode) holds each address once. Orders that need the current address reference the customer and read through. - If the business genuinely needs to know where each order went, keep an explicit snapshot on the order, but make its name say so, ship_to_address_id or a set of denormalized ship_to columns, and make it write-once. A snapshot that is never updated after insert is not a redundancy defect, it is an immutable record, and this is worth stating explicitly because it is the one legitimate reason for the duplication. The deciding question is mutability. A copy that is expected to track its source is a liability. A copy that is expected to freeze is a fact. ## Step four: backfill and reconcile Pick an authority. Usually the customer master record if one exists, otherwise the most recent order, otherwise an upstream system. Whatever you choose, record the rule, because it determines what history you are asserting. Rows that cannot be resolved, for instance a customer whose orders show two addresses with no timestamp ordering that explains it, should be flagged for manual review rather than silently collapsed. Collapsing ambiguous data is how a data-quality problem becomes an invisible data-loss problem. Keep the original values in a staging or audit table for the duration of the migration so any decision can be revisited. ## Step five: close the write paths A schema change alone does not stop drift; the code that wrote inconsistent copies must go. Enumerate every writer, including background jobs, admin tooling, and imports. Then make the invariant enforceable: a foreign key so the address cannot be free text, and, where a snapshot is legitimate, no UPDATE path at all so it cannot drift after creation. Add a verification query to the migration and, if the cutover is staged, to monitoring, so a regression is detected rather than discovered in a report months later. ## What good answers include Intent before action, measurement before migration, an explicit position on snapshots versus copies, a named authority for the backfill, refusal to guess on ambiguous rows, and closing the write paths. Answers that jump straight to add a foreign key miss that the column may have been legitimate all along.
- How do you decide whether the duplicated address was a defect or an intentional snapshot?Look at mutability and at the question the column answers. If any code path updates it on historical orders when the customer profile changes, it was intended as a copy of the current address and is redundant. If it is only ever written at order creation and the business needs to know where each order shipped, it is an immutable fact about the order and should stay, renamed so the intent is obvious.
- What do you do with rows whose correct value cannot be determined?Flag them rather than guess. Keep the original values in an audit or staging table, mark the affected orders for manual review, and let the owning team resolve them against an external source. Silently collapsing ambiguous variants converts a visible data-quality problem into invisible data loss, and it destroys the evidence needed to reconstruct the truth later.
saying these in an interview costs you the question
- Adding a foreign key without first establishing whether the column was a deliberate point-in-time snapshot
- Picking a winning address arbitrarily, for example MAX(address), and discarding the alternatives
- Fixing the schema but leaving the write path that produced the divergence in place
- Proposing a synchronization trigger to keep copies aligned instead of removing the duplication
- Skipping measurement and migrating blind