Singleton Semantics in Modern Languages: Kotlin and Python

Singleton behavior is a foundational runtime concept that influences memory usage, identity comparison, concurrency guarantees, and API design. Both Kotlin and Python implement singleton semantics at the language/runtime level — but in different ways and with different guarantees.

This article focuses on:

  • What singleton means at runtime
  • How Kotlin implements shared instances
  • How Python guarantees None as a singleton
  • Production-ready patterns
  • Correct identity comparisons

Kotlin: Shared Instances and Native Singleton Support

https://img.icons8.com/color/1200/kotlin.jpg
https://i.sstatic.net/Y2eqz.png
https://jenkov.com/images/java-concurrency/java-memory-model-3.png

4

1️⃣ emptyList() — Shared Immutable Instance

val list1 = emptyList<Int>()
val list2 = emptyList<Int>()

println(list1 === list2)  // true

Why This Works

  • emptyList() returns a shared immutable object.
  • Kotlin internally uses a single EmptyList instance.
  • Because it is immutable, sharing is safe.
  • No additional allocation occurs after initial use.

Production Implications

  • Memory efficient
  • Thread-safe
  • Zero mutation risk
  • Safe for high-concurrency systems

This is an intentional JVM-level optimization.


2️⃣ Kotlin object — First-Class Singleton

Kotlin provides a native singleton construct:

object ConfigManager {
    var environment: String = "prod"
}

Usage:

ConfigManager.environment = "staging"

Runtime Guarantees

  • Lazily initialized
  • Thread-safe initialization on JVM
  • Exactly one instance per classloader

When to Use in Production

Use object for:

  • Stateless services
  • Configuration holders
  • Registries
  • Caches with controlled mutation

Avoid using it for:

  • Complex dependency graphs (prefer DI frameworks)
  • Mutable global state without synchronization
  • Highly test-coupled systems

Python: Runtime Singletons and Identity Guarantees

https://images.openai.com/static-rsc-3/_AiWSys-LLQk23dQhz3MIpJGF5WU72ehbxtA-CGTcy0PlVbp4Hd-yChzq3waWz_zKUhZi2F4GwIhDWs3Pxk3UGkczY-x7IOoRuL8417CJUM?purpose=fullsize&v=1
https://www.teach.cs.toronto.edu/~csc148h/notes/_images/Parameters-crop.jpg
https://i.sstatic.net/fiO1L.png

4

1️⃣ None — A True Language-Level Singleton

value = None
value1 = None

print(value is value1)  # True

Why This Is Guaranteed

  • CPython defines a single internal object: Py_None
  • All references to None point to that object
  • The singleton guarantee is part of the language specification

There is exactly one None instance per interpreter.


Production Rule: Always Use Identity Comparison

Correct:

if value is None:
    handle_missing()

Incorrect:

if value == None:  # Avoid
    handle_missing()

Reason:

  • == invokes __eq__
  • Custom objects can override equality
  • is guarantees identity check

This is a production-critical distinction.


Production-Ready Singleton Patterns in Python

Python does not provide a built-in object keyword like Kotlin. Singleton behavior must be implemented deliberately.

Option 1 — Module-Level Singleton (Recommended)

Python modules are naturally singletons.

config.py

class Config:
    def __init__(self):
        self.environment = "prod"

config = Config()

Usage:

from config import config

config.environment = "staging"

Why This Is Production-Safe

  • Modules load once per interpreter process
  • Clear and explicit
  • No metaclass complexity
  • Easy to mock in tests

This is the preferred approach in real systems.


Option 2 — Thread-Safe Lazy Singleton Class

import threading

class Singleton:
    _instance = None
    _lock = threading.Lock()

    def __new__(cls):
        if cls._instance is None:
            with cls._lock:
                if cls._instance is None:
                    cls._instance = super().__new__(cls)
        return cls._instance

Characteristics

  • Lazy initialization
  • Thread-safe
  • Suitable for shared infrastructure components

Use only when a module-level singleton is insufficient.


Identity vs Structural Equality

Kotlin

val a = emptyList<Int>()
val b = emptyList<Int>()

println(a === b)  // identity
println(a == b)   // structural equality
  • === → same object
  • == → calls equals()

Python

a = None
b = None

print(a is b)  # identity
print(a == b)  # equality
  • is → object identity
  • == → invokes __eq__

Understanding this distinction prevents subtle production bugs.


Design Trade-offs

FeatureKotlinPython
Null singletonnullNone
Native singleton constructobjectNo
Identity operator===is
Structural equality====
Thread-safe singleton initializationYes (JVM)Manual implementation

When to Avoid Singletons

Do not use singletons when:

  • You need multiple runtime configurations
  • You rely heavily on dependency injection
  • You require strict test isolation
  • You manage lifecycle-scoped components

Modern backend architectures (Spring, Ktor, FastAPI, Django) typically prefer scoped dependency injection over global singletons.


Final Takeaways

  • Kotlin provides built-in, thread-safe singleton support.
  • emptyList() returns a shared immutable instance.
  • Python guarantees None as a true singleton.
  • Always use identity operators for singleton comparison.
  • Prefer module-level singletons in Python for production systems.
expertpython

Top Rated Book Check Now

Leave a Comment

Your email address will not be published. Required fields are marked *