Coincidental Duplication Costs Really Add Up
In any team, different roles bring different concerns - engineers, testers, designers, and product owners. Each group approaches problems through the lens of its own priorities and assumptions. When this diversity works well, it fosters specialisation and ensures a range of perspectives are heard when discussing new functionality. However, it can also lead to a subtle trap: shared language masking differing understanding, as each group views the topic through its own lens.
I've fallen into the unspoken-shared-common-understanding trap numerous times. While this failure in shared understanding can manifest in many ways, one subtle trap that seems especially built for engineers to fall into is: Coincidental Duplication
.
Coincidental Duplication
is when we mistake surface similarity between two features for intended commonality between them - duplicated style or behaviour that looks aligned but isn't. As engineers, we are trained to identify commonalities. We then spend time creating an abstraction to share that commonality, only to watch those abstractions break as one feature evolves independently - because, to the other party, those features are not and never were connected.
You can find the completed project with the colour example below here.
Where Coincidental duplication hides
Coincidental duplication often emerges at the edges of a project - where we collaborate across specialisations and the code we produce is effectively shared. In these areas, subtle differences in understanding can blur the line between surface similarity and intentional commonality. A familiar example is the colour palette: created to consolidate styling choices and make it easy to update colours across the app with a single change.
The example below is an extension of UIColor
that adds custom colours.
extension UIColor {
//MARK: - ColorPalette
class func brandGreen() -> UIColor {
return UIColor(red: 153/255, green: 204/255, blue: 0, alpha: 1)
}
class func brandBlue() -> UIColor {
return UIColor(red: 51/255, green: 136/255, blue: 255/255, alpha: 1)
}
class func brandRed() -> UIColor {
return UIColor(red: 255/255, green: 68/255, blue: 68/255, alpha: 1)
}
class func brandGrey() -> UIColor {
return UIColor(red: 173/255, green: 173/255, blue: 173/255, alpha: 1)
}
class func brandPurple() -> UIColor {
return UIColor(red: 170/255, green: 102/255, blue: 255/255, alpha: 1)
}
}
As the app evolves, designers introduce new colours to support emerging features - the palette changes, eventually looking something like this:
extension UIColor {
// MARK: - Green
class func brandDarkestGreen() -> UIColor {
return UIColor(red: 68/255, green: 119/255, blue: 0, alpha: 1)
}
class func brandDarkGreen() -> UIColor {
return UIColor(red: 68/255, green: 119/255, blue: 0, alpha: 1)
}
class func brandStandardGreen() -> UIColor {
return UIColor(red: 153/255, green: 204/255, blue: 0, alpha: 1)
}
class func brandLightGreen() -> UIColor {
return UIColor(red: 221/255, green: 238/255, blue: 153/255, alpha: 1)
}
class func brandLightestGreen() -> UIColor {
return UIColor(red: 238/255, green: 238/255, blue: 204/255, alpha: 1)
}
// Omitted other methods
}
In the above code snippet, we have added a few more shades to our colours with the following scheme:
- darkest
- dark
- standard
- light
- lightest
A slightly longer list of colours than before, and some gaps are missing between the different colours, but still just about sticking to an abstract colour palette.
But the designers aren't done yet 😡. They keep adding shades, chasing nuance and clarity in the user experience - blissfully unaware they're dismantling our beautifully crafted colour palette!
Next, you find yourself adding in names like: lightDark
and darkLight
. But one of the reasons for abstracting is to make things easier to read and reason about. Do brandLightDarkGreen
and brandDarkLightGreen
help readability - or just make you question your own sanity? Rather than ploughing on and adding those new colour abstractions, and so continuing the pattern drift, we need to question whether the colour palette concept we have been building is actually shared across the team.
Abstract with Alignment
"to assume is to make an ass
out of u
and me
"
Any competent designer will endeavour to create a consistent UI/UX that allows the end-user to ground themselves as they move between different features. But breaking consistency between features can be a powerful tool in any designer's toolbox and is often used to draw attention to a particular aspect of that feature. Before investing time in creating an abstraction based on someone else's work, we need to align on which aspects of their work will remain consistent and which are free to evolve. If we assume without alignment, we invite rework, confusion, and complexity.
The cost of that assumption:
- Development overhead - building unnecessary abstractions.
- Maintenance friction - bending abstractions to fit ever-diverging needs.
- Logic fragmentation - branching paths and edge cases that obscure the original intent of the abstraction.
- Deletion risk - removing shared code that silently affects unrelated features.
What began as elegant reuse can quickly become a brittle entanglement.
While it's common to share functionality between features, we need to ensure that what we are sharing is intentionally common and not just coincidentally common. If that common functionality is intentional, we can then make smart abstraction choices; otherwise, we can duplicate that functionality. Duplication isn't the enemy - misunderstood duplication is. No one likes to invest time in building something elegant only for someone else to destroy it. Save yourself the heartache - avoid coincidental duplication. When you spot duplication in a shared ownership area, don't assume it's intentional - seek alignment first, then abstract.