Data Clumps: when the same values travel together, make them an object
When the same two or three values appear together in every function, they want to be an object. A tested DateRange example shows how.

Data Clumps are groups of values that always appear together: start and end, latitude and longitude, street, city and zip. They are passed to function after function, stored side by side as fields, and checked again and again. The code and output below were run with Python 3 before publishing.
How to recognise it
- The same two or three parameter names show up in many signatures.
- A class has several fields with a common prefix (
billing_street,billing_city,billing_zip). - If you delete one value from the group, the others stop making sense.
- The same validation ("end must not be before start") is written in several places.
A good test: delete one of the values. If the rest lose their meaning, they belong together in one object.
The fix: make the clump an object
Create a small class for the group, move the rules into it, and pass it around. This often fixes the Long Parameter List at the same time, and gives you a home for behaviour that was scattered across functions.
from dataclasses import dataclass
from datetime import date
# ---- BEFORE: the same two values travel together through every function ----
def days_between_before(start, end):
if end < start: raise ValueError("end before start")
return (end - start).days + 1
def overlaps_before(s1, e1, s2, e2):
return s1 <= e2 and s2 <= e1
def label_before(start, end):
return f"{start:%d %b %Y} to {end:%d %b %Y}"
# ---- AFTER: the clump becomes one object that owns the rules ----
@dataclass(frozen=True)
class DateRange:
start: date
end: date
def __post_init__(self):
if self.end < self.start:
raise ValueError("end before start")
@property
def days(self): return (self.end - self.start).days + 1
def overlaps(self, other): return self.start <= other.end and other.start <= self.end
def __str__(self): return f"{self.start:%d %b %Y} to {self.end:%d %b %Y}"
DateRange now checks that the end is not before the start once, when it is created, so an invalid range can never exist. The functions that took start, end became a property (days), a method (overlaps) and a string form.
Proof that the answers are the same
a = DateRange(date(2026, 11, 3), date(2026, 11, 9))
b = DateRange(date(2026, 11, 8), date(2026, 11, 12))
print(a, "|", a.days, "days |", "overlaps b:", a.overlaps(b))
assert a.days == days_between_before(a.start, a.end)
assert a.overlaps(b) == overlaps_before(a.start, a.end, b.start, b.end)
assert str(a) == label_before(a.start, a.end)
try: DateRange(date(2026, 11, 9), date(2026, 11, 3))
except ValueError as e: print("error:", e)
print("same answers, and the rule lives in one place")
03 Nov 2026 to 09 Nov 2026 | 7 days | overlaps b: True
error: end before start
same answers, and the rule lives in one place
The new object gives the same day count, overlap result and label as the old functions, and it refuses an invalid range with one clear error. Before, a function like overlaps_before(s1, e1, s2, e2) took four loose dates in an order you had to remember.
How to refactor safely
- Find the group: look for the same names in several signatures or fields.
- Create a frozen dataclass with those fields, and add the validation in
__post_init__. - Change one function to take the object. Run the tests.
- Repeat for the others, and move any logic that only uses these fields into the new class.
- Look for new behaviour that now has an obvious home (like
overlaps).
When to leave it alone
- Two values that happen to be used together once are not a clump.
- A group of values that change independently does not need a shared object.
- In hot loops over millions of items, a plain tuple may be a better trade-off than a class.
Common mistakes
- Creating the class but leaving the logic in the old functions, so the object stays an empty bag (the next smell, a Data Class).
- Making it mutable, so one function changes a range that another still uses.
- Not validating in the constructor.
- Giving it a vague name (
Info,Data) instead of the real concept.
Main idea: values that always travel together are one concept in hiding. Name it, make it an object with its rules, and the signatures get shorter while the behaviour finds a home.
Code smells and refactoring series, Bloaters: 5 of 5. Previous: Long Parameter List: group related arguments into objects.


