Code Quality Principle · SSOT

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?

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:

  1. 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.
  2. 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.
  3. Simplified validation: Validation rules enforced at the single source protect the entire system. A constraint on the users.email column prevents invalid emails regardless of which code path performed the insert.
  4. 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.
  5. 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.
PrincipleScopeCore StatementKey Mechanism
SSOTData and stateOne canonical source for each factNormalized stores, derived views, code generation
DRYKnowledge (broad)One authoritative representation for each piece of knowledgeAbstraction, constants, configuration
OAOOCode behaviorEach behavior implemented onceExtract function, shared library
WETCode (anti-pattern)Knowledge written multiple timesCopy-paste — what SSOT and DRY prevent
NormalizationDatabase designEach fact stored once, dependencies explicit1NF, 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:

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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_total column 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 @property for derived values instead of storing them as fields; centralize configuration in a single config.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 const enums prevent string literal duplication
  • Java: Use foreign keys and joins instead of denormalized fields; @Transient for computed properties; Spring Data @Query named 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; avoid useState for 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 computed attributes 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: impl computed methods instead of stored derived fields; OnceLock for 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.watch derives, never copies; const constructors for design tokens; copyWith for immutable single-source updates
  • Swift: @Published var only for base data; var computed properties for derived values — never @Published for derived state; ObservableObject centralizes state so all views derive from it
  • Kotlin: Sealed class state instead of multiple boolean flags; copy() for immutable single-source updates; StateFlow with map operators 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:

  1. Never store what can be computed: Derived values belong in properties and selectors, not in stored fields
  2. Use foreign keys, not copies: Database joins are SSOT; copying a field from a referenced table is a SSOT violation
  3. Generate types from schemas: API types, database model types, and validation schemas should all be generated from one schema definition
  4. Centralize configuration: Every environment-specific value lives in one config file — nothing hardcoded in application logic
  5. Label your caches: Any derived store that could be stale should be explicitly labeled as a cache and have a defined invalidation strategy
  6. Make derived state impossible to be wrong: A @property or computed that 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.