The report came in late on a Wednesday: a CSV export was showing some orders a day early for customers in certain time zones. Classic. I wrote "I'll take a look at this" in the channel thread where it had been raised, opened the export code, and spent the rest of the afternoon finding the line that converted timestamps to dates before converting them to the customer's zone. By the time I logged off I had a fix half written and a test that failed in the right way.
The next morning there was a merged pull request for the same bug. A colleague eight hours ahead had picked it up from the ticket, which was unassigned, during her day while I slept. Her fix was cleaner than mine and came with two tests. My branch went in the bin.
Neither of us did anything wrong by the rules we had. My claim lived in a chat thread that had scrolled off her screen long before she started work. Her claim lived nowhere, because the ticket was open and nobody was on it. The tracker said the bug was free, the chat said I had it, and she looked at the tracker, which is what anyone sensible would do.
Duplicate work is quiet and it is expensive, and it almost never looks like a mistake from the inside.
That afternoon cost me about four hours. It also cost something harder to measure: the slightly awkward message where I said "oh, I was on this too", and the small, unfair doubt each of us had about whether the other one checked. Duplicate work is quiet and it is expensive, and it almost never looks like a mistake from the inside.
Screenshot
Screenshot · 16:9an unassigned bug ticket beside a chat claim that has already scrolled away, showing why two people fixed the same export overnight, product UI capture, ratio 16:9, screenshot
Why it feels like initiative
From where each person sits, duplicate work looks like diligence. You saw a problem, you picked it up, you moved fast. That is exactly what remote teams say they want. Nobody gets told off for fixing a bug.
The waste only shows up from above. Two people loaded the same context, two people wrote similar code, one reviewer read both, and the team ended up with one outcome for double the cost, sometimes with two slightly different ideas of how the thing should behave. On a co-located team, someone would have glanced over and said "oh, I'm on that". Distributed teams lose that glance completely. Nobody can see that you already have the file open.
So the fix cannot rely on noticing. It has to rely on writing things down where the next person will look before they start, and on treating silence as "unknown", never as "free".
A claim has to live where the work lives
Chat claims evaporate. A message in a thread is visible for an hour or so, to the people who happened to be online. Assignment on the ticket, a comment on it, or a draft pull request linked early all survive time zones, because they sit where the next person goes to find work.
A useful claim says four things: who, what scope, roughly when, and what is explicitly not included. Something like this:
"Taking ticket 8841, payment retry idempotency, through merge and a staging check by Thursday. Not touching webhook signatures in this pass."
The last sentence is the one people skip and it is often the most useful. It tells the colleague who was about to start on webhooks that they still can. A claim without a boundary tends to grow in other people's heads until it covers the whole area, and then work stalls because everyone assumes someone else has it.
What I do now: the moment I start actual work, I assign myself and leave a one-line comment on the ticket. If the ticket came up in chat, I reply in chat with a link to the ticket, not with "I'll take a look". It takes ten seconds. It would have saved that Wednesday.
Wide tickets are where people collide
"Fix payments" invites duplication. "Make POST /v2/charges idempotent when the client retries within 60 seconds" invites one owner, because it is small enough that two people working on it at once would be obviously silly.
Wide tickets are where good intentions run into each other. Two people both take a slice of "fix payments", each believes their slice is the important one, and they end up editing the same service from different ends. If a ticket is wide, split it before anyone starts. If you are about to start on something wide and you do not have time to split it, write the slice you are taking in the claim comment.
Thirty seconds of searching first
The cheapest prevention is a search. Before "I'll just fix it quickly", search the tracker for the error message, the endpoint, or the customer's name. Thirty seconds of searching beats two hours spent on a branch that already exists.
This sounds sensible until you actually try it on a busy day, when the bug is right there and the search feels like bureaucracy. That is the moment it matters most. Quick fixes are the ones people do not claim, which makes them the ones that get done twice.
Status that says where you are, not how far
Status updates help prevent duplication when they describe position instead of progress. "60% done on export bug" tells a colleague nothing useful. "Reproduced on staging with order 992, the date conversion happens before the zone conversion, fixing in ExportService next" tells them not to start the same reproduction from scratch, and might even tell them you have the cause wrong.
That is the real purpose of async status people actually read. It is less about reporting to a manager and more about making overlap visible while it is still cheap to fix.
The same goes for blockers. If you are waiting on API keys, say so plainly, with the person and the date. A blocker buried in a vague note lets someone else assume the work is moving, or worse, start building their own version against mock data that will later be thrown away. Reporting blockers without passive aggression is about the tone of that line, which matters when you have had to write it twice.
When you find the overlap halfway through
Sometimes you discover the duplication mid-flight: two draft pull requests, same files, same symptom. Stop and talk. This is one of the cases where a short live call earns its place, five minutes to divide the work rather than an hour of retelling how you each got there.
There are only three sensible outcomes. One person finishes and the other stops, ideally with notes on anything they found that the finisher missed. Or you split by path: one takes the backend change, the other takes the migration. Or you merge the best of both into one branch with one owner. What does not work is both carrying on politely and letting the reviewer choose.
Outcome
What you keep
One person finishes
Their branch, plus notes from the person who stops
Split by path
One owner for the backend change, one for the migration
One merged branch
The best of both drafts, with a single owner
Reviewers have a part in this. Two pull requests touching the same code for the same reason should trigger a pause, not two approvals. A reviewer who comments on naming while missing that the same fix is open twice has let the team down as much as the authors who did not claim.
Not just code
Code is the obvious surface, but the same thing happens to everything else.
Research spikes. "I'm investigating latency" with no end date lets three people profile the same endpoint. Give spikes a time limit, say forty-eight hours, and a defined output: a comment with findings and a recommendation, then either a build ticket or a closed ticket. The output should answer one question directly: should anyone else start this?
Docs. Two people writing onboarding notes or a runbook in parallel produce two pages with similar titles and slightly different instructions, which is worse than one page. One ticket for doc work, one owner.
Decisions. I watched three engineers each ask the designer privately what colour a button should be. She answered all three, in slightly different words. The product manager then heard about three decisions. One thread, one answer pasted into the ticket, one link shared once would have made it a single decision.
Incidents. This is the most expensive place to double up, because adrenaline outruns tickets. Two people pushing silent patches to production at once makes rollback a nightmare. Even in a hurry, open one incident note and give out roles: who investigates, who communicates, who writes the patch.
The contact form that sent everything twice
Freelancers get a version of this too, and it is stranger. A client once asked me to fix the contact form on her site, which had stopped sending enquiries. I fixed it on a Monday. On Tuesday she forwarded a test enquiry that had arrived twice. It turned out she had also asked another freelancer the week before, he had got round to it the same day I did, and between us we had wired the form to two mail handlers.
Nobody was hiding anything. She had simply asked two people because the first one had not replied fast enough. Since then I ask at every kickoff: "Is anyone else working on this area, now or recently?" I also put my scope in the first email, in a sentence, so if overlap appears later there is something in writing to compare against. When it does show up, I raise it early with options, rather than both of us competing quietly until invoice time.
After it happens, skip the courtroom
Duplicate work will still happen sometimes. When it does, the useful questions are about the merge path and the gap: which version do we keep, and what would have let the second person see the first? Usually the answer is small. An unassigned ticket, a claim in the wrong place, a ticket that was too wide.
Blame does the opposite of what you want. If people get told off for duplication, they start working in private branches until they are sure, which hides work even longer and makes the next duplicate more likely. The same goes for new teammates, who duplicate work most often because they have not learned where to search and are anxious not to look idle. Show them how claims work on this team before you show them the repo, and give them a claim comment they can copy for their first ticket.
Shared norms help here more than any single habit. Where decisions live, how fast people reply, which channel is for what: async communication for distributed teams is meant to be agreed as a team, not adopted by one keen person.
No tool replaces "I have this"
Boards, bots that announce new pull requests, automatic assignment: all useful, and none of them work if people update them after the work rather than before. My Wednesday export bug was on a perfectly good board. The board just did not know I was on it, because I told the chat instead.
The habit is one sentence, written in the place the next person will look, before you write any code: what you are taking, how far, and what you are leaving alone. Do that, keep tickets narrow enough that two owners would look silly, and search before you start. When two people fix the same thing, the team was usually keen enough. What it lacked was any record of who was doing what outside somebody's head.
End of this piece
Writer
PrimedEcho
Independent notes on remote work, focus, and freelance practice. About this site.