juniorDDD

What is the difference between an entity and a value object in DDD?

Updated Feb 20, 2026

Short answer

An entity has an identity that persists through change — two entities with identical attributes are still different things, and the same entity remains itself after every attribute changes. A value object has no identity and is defined entirely by its attributes; two with equal values are interchangeable. The practical consequence is that value objects should be immutable and compared by value, entities by ID.

Deep explanation

The test is: if every attribute changed, would it still be the same thing?

A person who changes name, address, and email is still the same person — that is an entity. A £50 amount that becomes £60 is not a changed fifty pounds, it is a different amount — that is a value object.

Python
class Money: # VALUE OBJECT — immutable, compared by value
def __init__(self, amount, currency):
self._amount, self._currency = amount, currency
def __eq__(self, other):
return (self._amount, self._currency) == (other._amount, other._currency)
def add(self, other):
if self._currency != other._currency:
raise ValueError('currency mismatch')
return Money(self._amount + other._amount, self._currency) # NEW instance
class Customer: # ENTITY — identity survives change
def __init__(self, customer_id, name, address):
self.id, self.name, self.address = customer_id, name, address
def __eq__(self, other):
return self.id == other.id # ONLY the id matters

Why value objects should be immutable. They are freely shared, so an in-place mutation would surprise every other holder. Returning a new instance from add rather than mutating is what makes them safe to pass around without defensive copying.

Why this classification pays off. Value objects are where domain rules naturally live. A raw float for money accepts currency mixing, negative totals, and rounding errors; a Money type rejects all three at construction. The same applies to EmailAddress, DateRange, Percentage, PostalCode — each replaces a primitive and a scattering of validation with one type that cannot hold an invalid value.

That is the primitive obsession smell: a codebase full of String email and decimal price has its domain rules spread across every method that touches them, instead of concentrated in a type.

Most models end up with far more value objects than entities. Entities are the few things the business tracks by identity — Customer, Order, Account — while nearly everything they contain is a value.

Real-world example

An order line that mixes both:

Python
class OrderLine: # ENTITY inside the Order aggregate
def __init__(self, line_id, sku, quantity, unit_price: Money):
self.id = line_id # identity: this specific line
self.sku = sku
self.quantity = quantity
self.unit_price = unit_price # VALUE: £9.99 is £9.99
def total(self) -> Money:
return Money(self.unit_price._amount * self.quantity,
self.unit_price._currency)

Two lines both for 3 units at £9.99 are still distinct lines — one might later be cancelled. But the two £9.99 amounts are the same value and could be swapped with no consequence.

Common mistakes

  • - Giving every class an ID out of habit, so value objects gain a meaningless identity and equality comparisons stop making sense.
  • - Making value objects mutable, so a shared instance changes under holders who did not expect it.
  • - Comparing entities by attributes rather than identity, which reports a renamed customer as a different customer.
  • - Primitive obsession — using `string` and `decimal` where a domain type would enforce the rules, scattering validation across the codebase.
  • - Forgetting to implement hashing consistently with equality, so value objects behave incorrectly as dictionary keys or in sets.
  • - Modelling something as an entity because it is stored in its own database table
  • persistence structure is not the same question as domain identity.

Follow-up questions

  • How do you decide whether something is an entity or a value object?
  • Why must value objects be immutable?
  • What is an aggregate root and how does it relate?
  • What is primitive obsession and why does it matter here?

More DDD interview questions

View all →