Single Source of Truth
Every piece of data or state must live in exactly one authoritative place.
SSOT (Single Source of Truth): A Complete Guide with Examples in 11 Programming Languages
SSOT — Single Source of Truth — is the principle that every piece of data, state, or configuration in a system should be stored in exactly one place, and all other representations of that data should be derived from that one authoritative source rather than independently maintained. The term originated in information theory and database design but now applies broadly across frontend architecture, distributed systems, API design, configuration management, and full-stack application development. SSOT is the data-layer manifestation of DRY: where DRY says every piece of knowledge should have one authoritative representation, SSOT says every piece of data or state should have one authoritative source.
In this comprehensive guide, we'll explore the SSOT principle with real-world examples in Python, TypeScript, Java, JavaScript, C#, PHP, Go, Rust, Dart, Swift, and Kotlin — complete with language-specific best practices, common pitfalls, and the patterns that produce truly single-source systems.
Table of Contents
- What is SSOT?
- Why Follow SSOT?
- SSOT vs. Related Principles
- SSOT in Depth
- Concept Diagram
- Beginner-Friendly Example
- Production-Ready Example
- Real-World Use Cases
- Language-Specific Mistakes and Anti-Patterns
- Frequently Asked Questions
- Key Takeaways
What is SSOT?
SSOT says: for any fact that your system knows, that fact should live in exactly one place. Every other place that needs that fact should derive it from the single source — reading it, transforming it, displaying it — but never storing an independent copy that can diverge.
The problem SSOT prevents is state inconsistency: when the same fact is stored in two places, those two places will eventually disagree. A user's email address stored in a users table and also in a billing_contacts table must be kept in sync every time the user updates their email. If the sync fails — due to a bug, a network error, a race condition, or a forgotten code path — the two tables disagree. The billing system sends invoices to the old address while the user interface shows the new one. The data is inconsistent because it was stored twice.
SSOT resolves this structurally: the email lives in one place (users.email). The billing system reads from that one place. There is no copy to go out of sync. The email can only ever be correct or incorrect — never inconsistent.
SSOT violations appear at every layer of a system:
Database layer: Denormalized tables that store the same fact in multiple columns or tables. Every write must update all copies; missed updates create inconsistency.
Application state: Frontend state stored redundantly — the same boolean flag derived from multiple observable sources, or the same list stored both in a global store and in a component's local state.
Configuration: The same environment-specific value (an API endpoint, a feature flag threshold) hardcoded in multiple configuration files that must be kept in sync.
Type definitions: The same data shape defined as a database schema, a backend model, and a frontend type — three independent representations of the same structure.
Caches: A cache is a copy of data from a canonical source. A cache that is populated correctly but never invalidated on source changes becomes a stale SSOT violation — the cache and the source disagree.
Why Follow SSOT?
SSOT produces concrete, measurable benefits:
- Structural consistency guarantee: When data lives in one place, consistency is structural — it cannot become inconsistent because there are no copies to diverge. This eliminates an entire class of bugs.
- Single update target: Changing a fact means changing one place. All readers automatically see the updated value. There is no sync logic to write, test, or maintain.
- Simplified validation: Validation rules enforced at the single source protect the entire system. A constraint on the
users.emailcolumn prevents invalid emails regardless of which code path performed the insert. - Auditable history: When changes to a fact flow through one place, that place can maintain a complete audit log. Distributed stores produce distributed logs that must be correlated — an expensive and error-prone process.
- Debugging confidence: When you read a value from the single source, you know it is the authoritative value. In a multi-source system, you must determine which source is "right" before you can debug.
SSOT vs. Related Principles
| Principle | Scope | Core Statement | Key Mechanism |
|---|---|---|---|
| SSOT | Data and state | One canonical source for each fact | Normalized stores, derived views, code generation |
| DRY | Knowledge (broad) | One authoritative representation for each piece of knowledge | Abstraction, constants, configuration |
| OAOO | Code behavior | Each behavior implemented once | Extract function, shared library |
| WET | Code (anti-pattern) | Knowledge written multiple times | Copy-paste — what SSOT and DRY prevent |
| Normalization | Database design | Each fact stored once, dependencies explicit | 1NF, 2NF, 3NF, BCNF |
SSOT vs. DRY: DRY is the broader umbrella. SSOT is the data-specific application. DRY says "the tax rate of 8% should be defined once as a constant." SSOT says "the user's current address should be stored in one place in the database, not in three tables." Both are about eliminating redundancy — DRY at the knowledge level, SSOT at the data/state level.
SSOT vs. Database Normalization: Normalization is the formal database technique for achieving SSOT in relational schemas. Third Normal Form (3NF) says that every non-key attribute must depend on the key, not on another non-key attribute — which is a formal statement of SSOT for relational data. SSOT is the principle; normalization is one of its implementations.
SSOT vs. Caching: Caching appears to violate SSOT — a cache stores a copy of data from the canonical source. The resolution: a cache is a derived, invalidatable copy, not an independent store. A cache that is always invalidated when the source changes is SSOT-compliant. A cache that can hold stale data indefinitely is a SSOT violation in practice.
SSOT in Depth
SSOT Application Levels:
-
Database SSOT: Each fact stored in exactly one column in one table. Foreign keys reference the single source; they don't copy data. Joins derive combined views from single sources.
-
Application state SSOT: In frontend applications, a single state store (Redux, Zustand, Pinia, a Context) is the canonical source for application state. Components read from it; they do not maintain local copies of the same data.
-
Configuration SSOT: Environment-specific values live in one configuration file or environment variable set. No value is hardcoded in multiple locations. Build pipelines derive environment-specific artifacts from the single configuration source.
-
Type/schema SSOT: The shape of a data structure — its fields, types, and constraints — is defined once (in a database schema, an OpenAPI spec, a Protobuf definition, a GraphQL SDL). Backend models, frontend types, validation schemas, and API clients are all generated from this single definition.
-
Computed state SSOT: Derived values (a total price computed from line items, a boolean "is premium" computed from membership data) should not be stored independently — they should be computed from the source data on demand. Storing derived state creates two sources that can disagree.
When Derived Storage Is Acceptable:
Pure SSOT would derive every computed value on demand, which is sometimes too expensive. Practical SSOT allows derived storage when:
- Performance requires it: A denormalized
order_totalcolumn avoids summing line items on every read. This is acceptable with an explicit sync mechanism (a trigger, an event handler, a recalculation job). - The derivation is explicit and audited: The derived store is labeled as a projection of the canonical source. Sync failures are detected and logged.
- The canonical source is always authoritative: In any conflict between the derived store and the canonical source, the canonical source wins — not the derived one.
Concept Diagram
Beginner-Friendly Example
The clearest SSOT illustration: a user profile system where a user's display name, email, and subscription tier are the facts. The SSOT violation stores these facts in multiple places that must be synchronized. The SSOT solution has one canonical source that all consumers derive from.
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Optional
from datetime import datetime, timedelta
from enum import Enum
# ════════════════════════════════════════════════════════════════
# SSOT VIOLATION — User data scattered across multiple stores
# ════════════════════════════════════════════════════════════════
class WetUserStore:
"""Multiple stores that each hold independent copies of user facts."""
# Store 1: Auth system
_auth: dict[str, dict] = {}
# Store 2: Profile system — holds a copy of display_name from auth
_profiles: dict[str, dict] = {}
# Store 3: Billing system — holds a copy of email from auth
_billing: dict[str, dict] = {}
def create_user(self, user_id: str, email: str, display_name: str, plan: str) -> None:
# Data written to THREE independent stores simultaneously
self._auth[user_id] = {"email": email, "display_name": display_name}
self._profiles[user_id] = {"display_name": display_name, "plan": plan} # ← copy
self._billing[user_id] = {"email": email, "plan": plan} # ← copy
def update_email(self, user_id: str, new_email: str) -> None:
# ⚠️ Developer remembers to update auth and billing, but forgets profile
self._auth[user_id]["email"] = new_email
self._billing[user_id]["email"] = new_email
# self._profiles is not updated — if it also stored email, it's now stale
def update_display_name(self, user_id: str, new_name: str) -> None:
# ⚠️ Developer updates auth but forgets profiles
self._auth[user_id]["display_name"] = new_name
# self._profiles[user_id]["display_name"] is now stale → inconsistency
def get_billing_email(self, user_id: str) -> str:
return self._billing[user_id]["email"] # ← May be out of sync with auth
def get_profile_name(self, user_id: str) -> str:
return self._profiles[user_id]["display_name"] # ← May differ from auth
# ════════════════════════════════════════════════════════════════
# SSOT COMPLIANT — One canonical store; all consumers derive
# ════════════════════════════════════════════════════════════════
class SubscriptionPlan(Enum):
FREE = "free"
PRO = "pro"
ENTERPRISE = "enterprise"
@dataclass
class User:
"""The single source of truth for all user data."""
user_id: str
email: str
display_name: str
plan: SubscriptionPlan
joined_at: datetime = field(default_factory=datetime.utcnow)
# ── Derived properties — computed from source, never stored separately ──
@property
def is_paid(self) -> bool:
"""Derived: never stored separately. Always consistent with plan."""
return self.plan != SubscriptionPlan.FREE
@property
def billing_email(self) -> str:
"""Derived: billing always uses the user's single canonical email."""
return self.email # ← No copy — directly from the source
@property
def notification_email(self) -> str:
"""Derived: notifications always use the same canonical email."""
return self.email # ← Same source — structurally impossible to diverge
@property
def display_label(self) -> str:
"""Derived: computed from single display_name source."""
plan_badge = f" [{self.plan.value.upper()}]" if self.is_paid else ""
return f"{self.display_name}{plan_badge}"
class UserRepository:
"""Single canonical store. All systems read from here — no copies."""
def __init__(self) -> None:
self._store: dict[str, User] = {}
def save(self, user: User) -> None:
self._store[user.user_id] = user
def get(self, user_id: str) -> Optional[User]:
return self._store.get(user_id)
def update_email(self, user_id: str, new_email: str) -> None:
"""Update in one place. All derived consumers (billing, notifications) see it immediately."""
if user := self._store.get(user_id):
# Replace with updated immutable-style value
self._store[user_id] = User(
user_id=user.user_id,
email=new_email, # ← single update
display_name=user.display_name,
plan=user.plan,
joined_at=user.joined_at,
)
def update_display_name(self, user_id: str, new_name: str) -> None:
if user := self._store.get(user_id):
self._store[user_id] = User(
user_id=user.user_id,
email=user.email,
display_name=new_name, # ← single update
plan=user.plan,
joined_at=user.joined_at,
)
# ── Consumers derive — they do not copy ───────────────────────
class BillingService:
def __init__(self, repo: UserRepository) -> None:
self._repo = repo
def get_invoice_email(self, user_id: str) -> Optional[str]:
"""Derives email from the SSOT — never stores its own copy."""
user = self._repo.get(user_id)
return user.billing_email if user else None # ← derived from single source
def is_billable(self, user_id: str) -> bool:
user = self._repo.get(user_id)
return user.is_paid if user else False # ← derived from single source
class NotificationService:
def __init__(self, repo: UserRepository) -> None:
self._repo = repo
def send_email(self, user_id: str, subject: str) -> str:
user = self._repo.get(user_id)
if not user:
return "User not found"
# Uses the same single source email — impossible to be out of sync
return f"Sending '{subject}' to {user.notification_email}"
class ProfileService:
def __init__(self, repo: UserRepository) -> None:
self._repo = repo
def get_display(self, user_id: str) -> Optional[str]:
user = self._repo.get(user_id)
return user.display_label if user else None # ← derived, never stored
# ── Client ─────────────────────────────────────────────────────
if __name__ == "__main__":
repo = UserRepository()
billing = BillingService(repo)
notification = NotificationService(repo)
profile = ProfileService(repo)
user = User("U001", "jane@example.com", "Jane Doe", SubscriptionPlan.PRO)
repo.save(user)
print("=== SSOT User Profile System ===\n")
print("Invoice email:", billing.get_invoice_email("U001"))
print("Notification:", notification.send_email("U001", "Welcome!"))
print("Display:", profile.get_display("U001"))
# Update email in ONE place — all consumers see it immediately
print("\n[Updating email in single canonical source...]\n")
repo.update_email("U001", "jane@newdomain.com")
# All services derive from the updated single source — no sync needed
print("Invoice email (after update):", billing.get_invoice_email("U001"))
print("Notification (after update):", notification.send_email("U001", "Email changed"))
print("Display (after update):", profile.get_display("U001"))
# No sync logic. No missed update. No inconsistency possible.
Production-Ready Example
The highest-stakes SSOT challenge in production is type/schema synchronization across stack layers — the same data structure defined independently in the database schema, the backend model, and the frontend type. The DRY/SSOT solution is to define the schema once and generate all other representations from it.
The Pattern: Schema-First Development
The canonical approach across all 11 languages is to define the data structure once in a schema language — OpenAPI YAML, GraphQL SDL, Protobuf, JSON Schema, or a TypeScript definition — and generate backend models, frontend types, validation schemas, and API clients from that single definition. The schema is the SSOT; everything else is a derived artifact.
# openapi.yaml — THE SINGLE SOURCE OF TRUTH for the User shape
components:
schemas:
User:
type: object
required: [userId, email, displayName, plan]
properties:
userId:
type: string
format: uuid
email:
type: string
format: email
maxLength: 255
displayName:
type: string
minLength: 1
maxLength: 100
plan:
type: string
enum: [free, pro, enterprise]
From this single definition, codegen tools produce:
- Python: Pydantic model via
datamodel-code-generator - TypeScript: Interface via
openapi-typescript - Java: Record/POJO via
openapi-generator - C#: Record via
NSwag - Go: Struct via
oapi-codegen
When a new field is added to the schema (say, timezone: string), the codegen runs once and all layers are updated simultaneously. The SSOT is the schema file; every other representation is derived, not hand-maintained.
This pattern eliminates the most common production SSOT violation: a backend adding a field to the API response that the frontend type doesn't know about, causing silent data loss or type errors that only appear at runtime.
Real-World Use Cases
SSOT applies at every layer of a production system.
User authentication state: A user's authentication status stored in a cookie, a global store, a component's local state, and a header value is a four-way SSOT violation. Modern frameworks resolve this with a single auth context (React Context, Vuex store, Redux slice) that is the canonical source — components read from it, never maintaining their own copy.
Shopping cart: A cart item count in a header badge, in a cart page component, and in a checkout summary, each maintaining its own count, is a SSOT violation. When items are added, all three must be updated. The SSOT solution is a single cart store that all three components derive from — the badge, page, and summary all read from one source and rerender when it changes.
Feature flags: A feature flag value hardcoded in multiple places is a SSOT violation. A feature flag service that all code paths query is the SSOT — enabling or disabling the flag in one place changes behavior everywhere.
Database normalization: A customers table and an orders table that both store the customer's shipping address are a SSOT violation. When the customer changes their address, both tables must be updated. The normalized solution: orders stores a customer_id foreign key and joins to customers.address at query time. The address is stored once.
Environment configuration: An API base URL hardcoded in three different service files is a SSOT violation. An .env file or a config service that all three read from is the SSOT — change the URL once, all services pick it up.
Generated documentation: API documentation maintained by hand alongside code is a SSOT violation — the code and the docs will diverge. Documentation generated from code annotations (JSDoc, Swagger annotations, docstrings) has one source: the code. The docs are a derived artifact.
Language-Specific Mistakes and Anti-Patterns
❌ Mistake #1: Storing Derived State Independently
# BAD: is_premium stored as a field — can diverge from the data it was computed from
@dataclass
class User:
membership_months: int
total_spent: float
is_premium: bool # ← derived from the above two fields, but stored separately
# If membership_months is updated without recomputing is_premium, they're inconsistent
# GOOD: Compute on demand — impossible to be out of sync
@dataclass
class User:
membership_months: int
total_spent: float
@property
def is_premium(self) -> bool:
return self.membership_months >= 12 and self.total_spent >= 500
❌ Mistake #2: Duplicating Config Across Files
# BAD: API base URL in three different service files
# services/user_service.py
BASE_URL = "https://api.example.com/v1"
# services/order_service.py
BASE_URL = "https://api.example.com/v1" # ← copy
# services/billing_service.py
BASE_URL = "https://api.example.com/v1" # ← copy
# GOOD: Single config module
# config.py
API_BASE_URL = "https://api.example.com/v1"
# All services import from config — one place to update
from config import API_BASE_URL
Frequently Asked Questions
Q1: Does SSOT mean I can never cache data?
No. Caching is compatible with SSOT when the cache is treated as a derived, invalidatable projection of the canonical source — not as an independent store. The rules are: (1) the canonical source is always authoritative in any conflict; (2) the cache is invalidated when the source changes; (3) the cache is always labeled as a cache, not as the source. A cache that can hold stale data indefinitely without invalidation is a practical SSOT violation — the cache and the source will disagree.
Q2: How does SSOT apply to microservices where data must be replicated?
Microservices frequently replicate data for performance and autonomy. The SSOT discipline still applies: designate one service as the owner (the canonical source) for each piece of data, and have all other services treat their local copies as projections derived from the owner's events. Event sourcing and CQRS provide formal patterns for this: the event log is the SSOT; all read models are derived projections. Conflicts are resolved by the owner's version, never by a consumer's copy.
Q3: What is the relationship between SSOT and database normalization?
Normalization is the formal SSOT technique for relational data. Third Normal Form (3NF) is a specific statement of SSOT: every non-key attribute depends on the whole key and nothing but the key. A table in 3NF has no facts stored redundantly — each fact lives in exactly one place and is derived by joining when needed. SSOT is the principle; normalization is the specific technique for relational schemas.
Q4: How do I handle SSOT when the same data must be displayed differently in different contexts?
Display transformation is not a SSOT violation — it is derivation. A User with one canonical email field can be displayed as "jane@..." in a compact view and "Jane Doe <jane@...>" in a full view. Both displays derive from the same source field; neither stores an independent copy. The rule: data lives in one place; presentations of that data are computed on demand.
Q5: Can SSOT be violated in frontend state management?
Yes — it is one of the most common application-level SSOT violations. Symptoms: the same data in the global store and in component local state; the same data in two different store slices; derived values stored as independent fields in the store that must be kept in sync with the base values. Solutions: read directly from the canonical store slice; use selectors to derive computed values on demand; avoid duplicating state across store slices.
Q6: What is the difference between SSOT and normalization in NoSQL?
NoSQL databases often use intentional denormalization for performance (embedding documents, duplicating fields). This is a deliberate SSOT tradeoff, not a violation of the principle — it is accepted with explicit acknowledgment of the sync cost. The SSOT discipline still applies: document the canonical source for each duplicated field, implement sync mechanisms (application-level, change data capture, or event-driven), and ensure conflicts always resolve in favor of the canonical source.
Key Takeaways
🎯 Core Concept: SSOT states that every fact — every piece of data, state, or configuration — should be stored in exactly one authoritative place. All other consumers of that fact derive it from the single source rather than maintaining independent copies. When data lives in one place, it cannot become inconsistent — consistency is structural, not maintained through sync logic.
🔑 Key Benefits:
- Structural consistency: Data stored once cannot disagree with itself — consistency is guaranteed by structure, not by sync
- Single update target: Change a fact in one place; all consumers see the updated value on their next read
- No sync logic: Eliminating copies eliminates the code, tests, and bugs associated with keeping copies in sync
- Auditable history: All changes flow through one canonical location — complete audit trail without correlation
- Debugging confidence: The single source is always correct; no need to determine which of several copies is authoritative
🌐 Language-Specific Highlights:
- Python: Use
@propertyfor derived values instead of storing them as fields; centralize configuration in a singleconfig.py; use@dataclass(frozen=True)for immutable value objects that derive all properties from stored fields - TypeScript: Share types between frontend and backend via a shared package or code generation from OpenAPI/GraphQL; use selectors in Redux/Zustand to derive computed state rather than storing it redundantly;
as constenums prevent string literal duplication - Java: Use foreign keys and joins instead of denormalized fields;
@Transientfor computed properties; Spring Data@Querynamed queries as the single source for complex conditions; record types for immutable value objects - JavaScript: Components read from a single global store — no local state copies; use framework reactivity (
watch,computed,selector) to derive values; avoiduseStatefor data that already lives in a global store - C#:
record with { }for immutable updates to a single source; EF Core computed columns for derived database values; centralize validation in a single validator class referenced by all layers - PHP: Config files as the single source for business constants; Eloquent
computedattributes for derived model properties; database migrations that read constants from config rather than hardcoding - Go: Single struct definition in the domain layer; package-level sentinel errors as the single source for error identity; config struct loaded once and passed via dependency injection — never re-read from the environment in each function
- Rust:
implcomputed methods instead of stored derived fields;OnceLockfor lazily-initialized single sources;Arc<T>for shared ownership of a single canonical object across threads - Dart: BLoC/Provider/Riverpod as the single state source —
context.watchderives, never copies;constconstructors for design tokens;copyWithfor immutable single-source updates - Swift:
@Published varonly for base data;varcomputed properties for derived values — never@Publishedfor derived state;ObservableObjectcentralizes state so all views derive from it - Kotlin: Sealed class state instead of multiple boolean flags;
copy()for immutable single-source updates;StateFlowwithmapoperators to derive secondary streams from a primary source
📋 When to Apply SSOT:
- Multiple places in the codebase read or write the same piece of data
- Sync logic exists to keep two representations of the same fact in agreement
- A bug was caused by updating one copy of data without updating another
- Multiple API endpoints return overlapping data that must be consistent
- Frontend state is being manually synchronized with a server response
⚠️ Practical SSOT Tradeoffs:
- Performance: Denormalization can be accepted for read-heavy workloads — document the canonical source explicitly and implement cache invalidation
- Microservice autonomy: Services may hold local projections of data owned by other services — event sourcing and CDC provide SSOT-compatible replication patterns
- Offline-first apps: Local device state may diverge from server state — a sync protocol with a defined conflict resolution strategy (server wins, last-write-wins, CRDTs) maintains the spirit of SSOT
🛠️ Best Practices:
- Never store what can be computed: Derived values belong in properties and selectors, not in stored fields
- Use foreign keys, not copies: Database joins are SSOT; copying a field from a referenced table is a SSOT violation
- Generate types from schemas: API types, database model types, and validation schemas should all be generated from one schema definition
- Centralize configuration: Every environment-specific value lives in one config file — nothing hardcoded in application logic
- Label your caches: Any derived store that could be stale should be explicitly labeled as a cache and have a defined invalidation strategy
- Make derived state impossible to be wrong: A
@propertyorcomputedthat calculates from stored data is structurally correct — it cannot diverge because it has no independent value to diverge from
SSOT is the data-layer commitment to DRY. Every stored copy of a fact is a liability — a future inconsistency waiting for a missed update to trigger it. The discipline of identifying canonical sources and deriving everything else from them produces systems that are not merely easier to keep consistent but structurally incapable of inconsistency within their own scope.
Explore the full DRY family of principles:
- DRY Principle (Don't Repeat Yourself) — The Core Principle
- WET Principle (Write Everything Twice) — Understanding DRY Violations
- OAOO (Once And Only Once) — Behavioral Implementation
Each guide includes examples in all 11 programming languages with language-specific best practices.