skip to content

In a case repository that mirrors a defect's state from an issue tracker, what is the difference between a one-way mirror and a two-way sync, and what goes wrong when both sides may write the same value?

level: middleimportance: must knowfreq 66%

answer

  1. direction belongs to a field
  2. a cache can always be rebuilt
  3. write, announce, apply, write again
  4. compare before you write
  5. you need the last agreed value

basics

~20 s

A one-way mirror gives each shared field a single writer and copies it in one direction; a two-way sync lets both sides write. Without a declared owner per field, two-way sync produces echo loops and last-writer-wins races that silently overwrite real edits.

solid answer

~50 s

Direction is a property of a field, not of the integration. A one-way mirror names the tracker as the sole writer of, say, defect state, and the repository only reads it — the repository's copy is a cache and can be rebuilt from scratch. A two-way sync lets both stores accept edits to the same value, which introduces two failure modes a mirror does not have. The first is the **echo loop**: the sync writes to the tracker, the tracker announces the change, the sync applies it locally, that local change looks like a user edit, and it writes out again. The second is a genuine **conflict**, where both sides changed since the last agreement — which you can only detect by storing the last agreed value, because comparing the two current values cannot tell a conflict from an echo.

go deeper

for a junior

Know that a mirrored field has one writer while the other side only reads it, and that letting both sides write the same field is what creates conflicts in the first place.

for a middle

Explain the echo loop step by step, and name compare-before-write as the defence that terminates it no matter which account made the change.

for a senior

Demonstrate that detecting a true conflict needs the last agreed value stored per field, and say plainly why last-writer-wins hides lost edits instead of resolving them.

for a principal

Own the ownership table itself: argue for the smallest possible set of genuinely shared fields, and for a conflict rule a user can predict without reading a log.

Two stores hold a value that is supposed to mean the same thing — the state of a defect, its priority, the summary a reader sees next to a failing test case. Keeping them in agreement is easy in one direction and genuinely hard in two, and most sync incidents come from a product that was designed as the first and operated as the second. ## Direction is a property of a field, not of the integration "We sync with the tracker" is not a design. The unit that has a direction is the field: - **Tracker-owned, mirrored inbound.** The defect's state and priority live in the tracker; the repository holds a copy so it can render a list without a round trip per row. That copy is a **cache** — it can be thrown away and rebuilt from the tracker, and nothing is lost. - **Repository-owned, pushed outbound.** The run that produced the failure, the environment, who executed it. The tracker holds a copy so a developer reading the ticket sees it; there the tracker's copy is the cache. - **Genuinely shared.** Somebody has decided a value may be edited on either side. This is the only case that needs conflict handling, and it should be a deliberate, short list. Writing ownership down per field rather than per integration turns a vague fear — "the systems fight" — into a small table you can reason about, and makes "who wins?" a decision taken once rather than a race settled by whichever job happened to run last. ## When there is nothing to sync Not every product has this problem. A standalone case repository, a TestRail-class product living beside the tracker, has two stores and therefore a direction question for every shared value. A tracker-resident product — the Jira-resident tools such as Xray and Zephyr, which keep cases and executions inside the tracker itself — has one store for those values, so a defect's state is not mirrored anywhere. There is one record and one writer, and the whole class of echo and conflict problems simply does not arise for tracker-native fields. Choosing the resident model is choosing to have no sync at all for them, which is the strongest available answer to drift and is paid for by coupling the case data to that tracker's lifetime. ## The echo loop A two-way field creates a cycle in the data flow, and the cycle has no natural end: 1. A user edits the value in the repository. 2. The sync writes it to the tracker. 3. The tracker announces a change, because something changed. 4. The sync receives that announcement and applies the value locally. 5. Step 4 looks exactly like step 1 to the next pass. Whether this spins forever or stops after one lap depends entirely on whether something compares before it writes. Three mechanisms are used, usually together: - **Compare-and-skip.** Before writing, read the destination's current value; if it already equals what you were about to write, do nothing and emit no change. This alone terminates the loop, because the second lap has nothing to write. It is the cheapest and most robust defence and should be unconditional. - **Actor suppression.** The sync writes as its own account, and inbound changes attributed to that account are ignored. Effective and fragile in the same breath: it fails when a human acts through that account, and when a tracker attributes a batch of several people's edits to one actor. - **A pending-write marker.** The sync records "I am about to write X to item Y" and drops the first inbound announcement carrying X for Y. It covers what actor suppression misses, at the cost of state that must expire — a marker never cleared silently swallows a real user edit. ## A conflict is not an echo, and only a snapshot tells them apart With just two current values, "they differ" is ambiguous: the tracker may have changed, the repository may have changed, or both. The standard fix is to keep the **last agreed value** per synced field — what both sides held the last time the sync completed — and compare three ways: | Repository vs snapshot | Tracker vs snapshot | Meaning | Action | |---|---|---|---| | unchanged | unchanged | in agreement | nothing to do | | changed | unchanged | one side edited | push outbound | | unchanged | changed | one side edited | apply locally | | changed | changed | true conflict | apply the owner rule, and record it | Without the snapshot column, the middle two rows are indistinguishable from the last one, and the usual fallback is last-writer-wins. ## Why last-writer-wins is worse than it sounds Last-writer-wins sounds fair and is not. It depends on two clocks the sync does not control, on the timestamp granularity each store happens to record, and on the order jobs happened to run. Its failures are silent by construction: the losing edit is not rejected, it is overwritten, and the person who made it finds out later, if ever. Where a shared field must exist, prefer a rule a human can predict and say out loud — the tracker wins on state, because that is where the fix is worked; the repository wins on run details, because that is where they are produced; and a genuine collision on anything else raises a visible flag rather than choosing quietly.

  • Your integration writes as its own account and ignores inbound changes from it. Where does that break?
    It breaks when a human acts through that account — an administrator running a bulk edit with the integration's credentials — and when the tracker attributes a batch of several people's edits to whoever wrote last. Suppression by actor is a useful filter, not a correctness guarantee; comparing before writing is what actually makes a repeated apply harmless.
  • Which shared fields would you refuse to make two-way at all?
    Anything guarded by a workflow on the tracker side, such as a state transition bound to an approval, and anything derived rather than typed by a person. A derived value written back is a fact with no author: when it disagrees with the other side, nobody can say which is right, and no conflict rule rescues it. Mirror those inbound and keep the local copy read-only.

saying these in an interview costs you the question

  • Calling a whole integration two-way instead of naming fields
  • Relying on the sync's own account to break every loop
  • Settling conflicts by whichever timestamp looks later
  • Comparing only the two current values to detect a conflict
  • Treating a silently overwritten edit as acceptable