2Brute2Best
πŸ”₯0
B2

Shared Exchange Config & Why Singleton Hurts Tests

High-frequency interview topic Pro OnlySingleton static local (thread-safe init)
⚑ 60-Second Architecture SummaryB2 · Singleton Pattern (and its Testing Cost)
Naive Bottleneck→Singleton Pattern (and its Testing Cost) Cure
The Naive Bottleneck: Initialization race condition

Unsynchronized global variables or naive pointer singletons cause race conditions across concurrent trading threads. Worse, public mutable setters (`setMarketOpen(false)`) turn the singleton into an untestable global dependency where concurrent unit tests clobber each other's state, resulting in flaky CI builds.

Naive if (!instance_) instance_ = new ... without synchronization leads to data races where concurrent threads allocate duplicate configuration instances.

The Pattern Cure: Singleton Pattern (and its Testing Cost)

Ensure a class has only one instance and provide a global point of access to it, while combining it with an interface and Dependency Injection so unit tests remain fully isolated.

We implement Meyer's Singleton using a static local variable inside instance(), which modern C++ guarantees is thread-safe on first access. We delete copy and move constructors. Crucially, ExchangeConfig implements a pure virtual IExchangeConfig interface, and components take const IExchangeConfig& in their constructors (defaulting to ExchangeConfig::instance()). In unit tests, we inject a lightweight MockExchangeConfig β€” production keeps its convenient singleton while tests remain 100% isolated.

Sprint 2 of 9 Β· Process-Wide Exchange Configuration & Market Clock
Story B β€” Low-Latency Trading System β€” Progressive System Build
1. Where our codebase left off
← B1

In Sprint 1 (B1 Β· Builder), we established immutable, fully validated TradeOrder objects. Now that orders can be created safely, our trading engine needs process-wide operational parameters (tick sizes, circuit-breaker price bands, market hours, and order size caps) to govern order validation across all components.

2. What we are building now

We are building ExchangeConfig, a centralized registry and market clock that provides consistent trading session parameters to RiskEngine, OrderBook, and gateway threads.

3. The pain point we hit

Unsynchronized global variables or naive pointer singletons cause race conditions across concurrent trading threads. Worse, public mutable setters (setMarketOpen(false)) turn the singleton into an untestable global dependency where concurrent unit tests clobber each other's state, resulting in flaky CI builds.

4. What this unlocks next
Next: B3 β†’

By implementing a thread-safe Meyers Singleton implementing an IExchangeConfig interface with constructor dependency injection, production retains safe static access while unit tests inject isolated mock configurations. Next in Sprint 3 (B3 Β· Chain of Responsibility), every incoming TradeOrder (from B1) evaluated against ExchangeConfig (from B2) must pass a sequential pre-trade risk pipeline before reaching the matching engine.

01

The product ticket

Ticket Β· B2TRADE-202 Β· Shared Exchange Config & The Hidden Testing Cost of Singletons

Across our trading system, multiple components (RiskEngine, OrderBook, MatchingGateway, ReportingEngine) require access to the active exchange trading session configuration: ExchangeConfig (market hours, minimum tick size 0.05, and max single-order value cap Rs. 10,000,000).

Requirements
  • β€’

    Provide a single, globally accessible, thread-safe configuration instance initialized lazily upon first access.

  • β€’

    Prevent accidental duplication, copying, or assignment across the codebase.

  • β€’

    CRITICAL DESIGN REQUIREMENT: Prevent the Singleton from becoming an un-testable global dependency that pollutes test suites with shared mutable state.

The next requirement that breaks naive code

A developer implemented ExchangeConfig with public mutable setters (setMarketOpen(bool)). A unit test simulating market closure ran concurrently in CI with order processing tests. Because both tests shared the same global instance, subsequent tests randomly failed, causing flaky builds that took days to debug.

02

Step 1 β€” The naive code & why it breaks

The naive code creates a raw pointer singleton with double-checked locking and public mutable setters, and trading components hardcode calls to ExchangeConfig::getInstance() throughout their logic.

Naive design (what not to ship) Β· naive.cpp
#include <print>
#include <string>

// Naive Singleton with raw pointers and public mutable setters.
// Flaw 1: Initialization race condition if two threads call getInstance() concurrently.
// Flaw 2: Mutable global state that leaks side effects across unit tests.
class NaiveExchangeConfig {
private:
    static NaiveExchangeConfig* instance_;
    bool marketOpen_{true};
    double tickSize_{0.05};
    double maxOrderValue_{10000000.0}; // Rs. 1 Crore

    NaiveExchangeConfig() = default;

public:
    static NaiveExchangeConfig* getInstance() {
        if (!instance_) { // Race condition without synchronization!
            instance_ = new NaiveExchangeConfig();
        }
        return instance_;
    }

    bool isMarketOpen() const { return marketOpen_; }
    void setMarketOpen(bool open) { marketOpen_ = open; } // MUTABLE GLOBAL STATE!

    double tickSize() const { return tickSize_; }
    void setTickSize(double t) { tickSize_ = t; }

    double maxOrderValue() const { return maxOrderValue_; }
};

NaiveExchangeConfig* NaiveExchangeConfig::instance_ = nullptr;

// A trading component that reaches directly into the global singleton.
class NaiveRiskEngine {
public:
    bool canPlaceOrder(double price, int qty) {
        auto* cfg = NaiveExchangeConfig::getInstance();
        if (!cfg->isMarketOpen()) return false;
        if (price * qty > cfg->maxOrderValue()) return false;
        return true;
    }
};

int main() {
    auto* cfg = NaiveExchangeConfig::getInstance();
    NaiveRiskEngine risk;

    // Normal operation
    std::println("Initial: marketOpen={}, canPlace={}",
                 cfg->isMarketOpen(), risk.canPlaceOrder(2500.0, 100));

    // Unit Test 1: Simulating market close by mutating global state
    cfg->setMarketOpen(false);
    std::println("Test 1 (closed): canPlace={}", risk.canPlaceOrder(2500.0, 100));

    // Unit Test 2: Expects default open market, but FAILS because Test 1 polluted the singleton!
    std::println("Test 2 (polluted!): canPlace={}", risk.canPlaceOrder(2500.0, 100));
}
Initialization race condition

Naive if (!instance_) instance_ = new ... without synchronization leads to data races where concurrent threads allocate duplicate configuration instances.

Shared mutable state corrupts test suites

When Test A calls cfg->setMarketOpen(false), Test B (which expects the default open market) fails intermittently. Unit tests lose isolation and cannot run in parallel.

Hidden dependency and tight coupling

RiskEngine directly reaches out to the global ExchangeConfig::getInstance(). Callers cannot test edge cases (such as custom tick sizes or foreign exchange limits) without modifying production global state.

03

Step 2 β€” The fix: Singleton Pattern (and its Testing Cost)

Core idea in one sentence

Ensure a class has only one instance and provide a global point of access to it, while combining it with an interface and Dependency Injection so unit tests remain fully isolated.

We implement Meyer's Singleton using a static local variable inside instance(), which modern C++ guarantees is thread-safe on first access. We delete copy and move constructors. Crucially, ExchangeConfig implements a pure virtual IExchangeConfig interface, and components take const IExchangeConfig& in their constructors (defaulting to ExchangeConfig::instance()). In unit tests, we inject a lightweight MockExchangeConfig β€” production keeps its convenient singleton while tests remain 100% isolated.

Pattern roles in our story
Singleton Interface

IExchangeConfig β€” declares const inspection methods (isMarketOpen, tickSize, maxOrderValue, exchangeName).

Meyer's Singleton

ExchangeConfig β€” implements IExchangeConfig with a private constructor, deleted copy/move, and thread-safe static local instance().

Test Double / Mock

MockExchangeConfig β€” implements IExchangeConfig for isolated test scenarios without touching global state.

Client Component

RiskEngine β€” takes const IExchangeConfig& via constructor injection.

Brute2Best Pro Content

Unlock Full UML Diagram & C++23 Solution

The interactive UML architecture diagrams, refactored C++23 code, real-world extensions, and interview Q&A are part of Brute2Best Pro. All 7 foundation chapters are 100% free.

All 440 visual dry runs & algorithms
Interactive step-by-step playback with pointer & array state transitions
52 comprehensive LLD chapters
Interactive UML diagrams, real-world stories (Food Delivery, Trading, RPG), and practice solutions
40 C++ basics labs & hardware memory models
Vtables, byte layouts, assembly insights, and multi-threading race conditions
Production-ready C++23 source code
Modern idioms, clean contracts, time/space complexity derivations, and interview takeaways

First anchor question per pattern, Part 1 LLD & Module 1 C++ basics are 100% Free forever.

Systems Integration

Related C++ Systems & Algorithmic Foundations

Deep dive into underlying layers