datarekha
Python Medium Asked at GoogleAsked at StripeAsked at Palantir

What is the difference between `__new__` and `__init__` in Python, and when would you override `__new__`?

The short answer

`__new__` allocates and returns the new object; `__init__` receives that object and populates its attributes. You rarely touch `__new__` — its main legitimate uses are subclassing immutable types like `int` or `str`, and implementing the Singleton pattern.

How to think about it

Most Python developers have never written __new__, and that’s exactly the point of the question: it tests whether you know when __init__ isn’t enough. The honest answer is narrow — you reach for __new__ for immutable types (where the value is fixed at allocation, so __init__ runs too late to change it) and for the Singleton pattern.

Object creation is a two-step protocol Python runs for you:

MyClass(args)
  └─ type.__call__(MyClass, args)
       ├─ 1. obj = MyClass.__new__(MyClass, args)   # allocate the object
       └─ 2. obj.__init__(args)                      # populate it — only if obj is a MyClass

__new__ creates and returns the instance; __init__ fills it in. If __new__ returns something that isn’t an instance of the class, Python skips __init__ entirely. For ordinary mutable classes you never touch step 1 — Python allocates through object.__new__ automatically, and you just write __init__.

When new matters

You can’t set an int’s value inside __init__, because by then the integer already exists — its value was baked in at allocation. To build a clamped integer subtype you have to intercept __new__:

# Immutable subclass: the value must be fixed at allocation time
class ClampedInt(int):
    def __new__(cls, value, lo=0, hi=100):
        clamped = max(lo, min(hi, value))
        return super().__new__(cls, clamped)

x, y, z = ClampedInt(150), ClampedInt(-20), ClampedInt(50)
print(f"ClampedInt(150) = {x}")
print(f"ClampedInt(-20) = {y}")
print(f"ClampedInt(50)  = {z}")
print(f"type: {type(x).__name__}, isinstance int: {isinstance(x, int)}")

# Singleton: __new__ hands back the SAME instance every time
class Config:
    _instance = None
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance.debug = False     # default state, set once
        return cls._instance

a = Config()
b = Config()
a.debug = True
print()
print("a is b  :", a is b)      # same object
print("b.debug :", b.debug)     # so they share state
ClampedInt(150) = 100
ClampedInt(-20) = 0
ClampedInt(50)  = 50
type: ClampedInt, isinstance int: True

a is b  : True
b.debug : True

ClampedInt could only do its clamping in __new__, because once the int exists its value is frozen. And Config’s __new__ returns the cached instance instead of a fresh one, so a and b are literally the same object — set a.debug and b sees it.

A small asymmetry worth knowing

__init__ must return Nonereturn something from it raises TypeError: __init__() should return None. __new__ is the opposite: it must return an object, and if that object isn’t an instance of cls, __init__ never runs.

The mental model

A factory line: __new__ stamps out the blank part, __init__ fills in the details. For a mutable object the blank is already usable, so you only ever write __init__. For an immutable object the value is the blank — you have to get it right at the stamp.

Learn it properly Classes & Instances

Keep practising

All Python questions

Explore further

Skip to content