My Cheatsheets

Singleton Pattern & Atomic Operations

The short version: A Singleton ensures one shared instance of a class exists app-wide. Atomics let multiple threads safely update a shared value without any locking.


Singleton Pattern

What It Is

A design pattern that guarantees a class has exactly one instance for the lifetime of the application, and provides a global access point to it.

You probably know it as: global object, global instance, or shared instance.

The formal name comes from the Gang of Four (GoF) design patterns book (1994). The concept predates the name.


The Three Moving Parts

Part What It Does
Private constructor Prevents external code from calling new directly
Static instance variable The class holds a reference to its own single instance
Static access method Returns the instance (creates it on first call if needed)

Python Example

class DatabaseConnection:
    _instance = None  # Static variable: holds the single instance

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance._initialized = False
        return cls._instance

    def __init__(self):
        if not self._initialized:          # Only runs once
            self.connection = "Connected to DB"
            self._initialized = True

# Usage
db1 = DatabaseConnection()
db2 = DatabaseConnection()

print(db1 is db2)        # True (same object)
print(db1.connection)    # "Connected to DB"

JavaScript Example

class Logger {
  constructor() {
    if (Logger.instance) {
      return Logger.instance;  // Return existing instance
    }
    this.logs = [];
    Logger.instance = this;
  }

  log(message) {
    this.logs.push(message);
    console.log(`[LOG]: ${message}`);
  }

  getHistory() {
    return this.logs;
  }
}

// Usage
const logger1 = new Logger();
const logger2 = new Logger();

logger1.log("App started");
logger2.log("User logged in");

console.log(logger1 === logger2);      // true
console.log(logger1.getHistory());     // ["App started", "User logged in"]

In JavaScript/Python: You Might Already Be Doing This

Module-level exports are de facto singletons. A module is evaluated once; every file that imports it gets the same instance. No formal pattern needed.

// db.js: this object is a singleton by nature of the module system
const db = { connection: "Connected to DB" };
export default db;

Common Use Cases


Watch Out For

Pitfall Why It Hurts
Testing difficulty Global state bleeds between tests, making them hard to isolate
Hidden dependencies Callers implicitly depend on it without declaring it
Concurrency Multi-threaded environments need thread-safe initialization (locks, synchronized)

Modern alternative: Dependency injection gives you the same "one instance" behavior with explicit, testable wiring.


Atomic Operations

What They Are

Operations that execute as a single, indivisible step at the hardware level. No thread can observe a half-finished state. The operation either completes fully or not at all.

Used for: safely reading and updating shared values across threads without a lock.


The Problem They Solve

Thread A reads counter: 5
Thread B reads counter: 5   ← same value, before A writes back
Thread A writes:        6
Thread B writes:        6   <- overwrites A (one update is lost)
Correct answer:         7

This is a race condition. Both threads read stale data and clobber each other's write.


Atomics vs. Mutex: The Key Difference

Mutex (Lock) Atomic
Mechanism Threads take turns (lock/unlock) CPU handles it in one hardware instruction
Overhead Locking + waiting overhead Near-zero overhead
Scope Any block of code Simple read-modify-write ops only
Analogy Deli counter: take a number, wait your turn Everyone walks up at once; the counter serializes each request instantly

When Atomics Win

High-contention values (a counter or flag that many threads touch constantly) are where atomics shine. A mutex makes those threads queue up. An atomic skips the queue entirely.


The Catch: Scope Is Limited

Atomics handle simple, single-value operations:

If you need to update multiple related values together as one logical unit, you still need a mutex. Atomics are a scalpel; a mutex is the general-purpose tool.


C++ Atomics: std::atomic<T>

Available since C++11 via <atomic>.

#include <atomic>
#include <thread>

std::atomic<int> counter{0};

void increment() {
    for (int i = 0; i < 1000; ++i) {
        counter.fetch_add(1);   // atomic, no race condition
        // counter++ also works; shorthand for fetch_add(1)
    }
}

int main() {
    std::thread t1(increment);
    std::thread t2(increment);
    t1.join();
    t2.join();
    // counter is guaranteed to be 2000
}

Common std::atomic operations:

Operation Method Shorthand
Read load() implicit cast
Write store(val) = val
Add and return old value fetch_add(n) +=
Subtract and return old value fetch_sub(n) -=
Compare, then conditionally swap compare_exchange_weak/strong()
Swap and return old value exchange(val)

Memory ordering -- std::atomic defaults to memory_order_seq_cst (sequential consistency: safest, slight perf cost). For fine-tuned performance you can specify a weaker order like memory_order_relaxed on operations where ordering guarantees aren't needed. This is an advanced topic; default ordering is correct for most use cases.

// Relaxed: only guarantees atomicity, not ordering relative to other ops
counter.fetch_add(1, std::memory_order_relaxed);

Other Languages (for reference)

Language Atomic Type Example
Java AtomicInteger counter.incrementAndGet()
Python threading.Lock or queue.Queue counter += 1 is NOT safe despite the GIL (it compiles to multiple bytecodes); use threading.Lock or queue.Queue instead
JavaScript Atomics (SharedArrayBuffer) Atomics.add(arr, 0, 1)

Summary: Pick the Right Tool

Situation Use
One shared resource, needs single owner Singleton
Simple counter / flag across threads Atomic
Multiple values that must update together Mutex
Single-threaded app None of the above; relax