Python 3.15 adds built-in sentinel after PEP 661 is accepted
The author of a talk on sentinel values, presented this year at PyConDE/PyData Berlin 2026, EuroPython 2026 and the Swiss Python Summit 2026, sets out the argument in a blog post. His starting point: None is probably the most overloaded value in Python. It is used to say that something is missing, unset, that an error occurred, or simply as an actual value.
He gives three examples of the ambiguity. A simple cache returns None both for a missing key and for a key that maps to None. In a configuration update, timeout=None could mean "no timeout" or "keep the current value". And a function that swallows a TimeoutError and returns None is using it as an error signal. Sentinel values resolve this by being a guard outside the domain of valid values, so they can never collide with an actual value.
Python already relies on sentinels in plain sight, he says. Ellipsis skips dimensions in NumPy slicing and marks stubs. NotImplemented means "find the implementation somewhere else", which enables double dispatch for binary operators through methods like rsub. Pydantic's MISSING distinguishes a field explicitly set to null from one that was never sent at all, and it has just left the experimental stage in Pydantic 2.14.
Creating your own sentinels is harder. The classic MISSING = object() works at runtime but breaks type narrowing in static type checkers. Workarounds such as TypeIs guards, magic Literal strings or the single-member Enum trick all come with trade-offs, and identity checks break when sentinels are copied or pickled.
Then came what he calls a plot twist. At PyConDE in April he had to conclude that PEP 661, the proposal to standardize sentinels, was deferred. Only nine days later, the Steering Council accepted it. Since Python 3.15, sentinel is a built-in, and type checkers can narrow it just like None. His example defines MISSING = sentinel("MISSING") and a function update_email(user_id: int, email: str | None | MISSING = MISSING). If email is MISSING, nothing is updated; if it is None, the email is deleted from the database; otherwise the email is lowercased and saved, with the type narrowed to str.
On older Python versions, typing_extensions provides a backport. He warns that type-checker support is still catching up, so readers should check their type checker before relying on it.
His main takeaway: be aware of the ambiguity of None and think about what you actually want to express. Use sentinels to resolve it, prefer the ones your library already provides, and replace the old object() and Enum hacks with the new built-in. He also points to his separate talk on exceptions and result types for anyone tempted to return None instead of raising an exception. The PyConDE slides still describe PEP 661 as deferred; the EuroPython recording is the most up-to-date version of the talk and covers the new built-in.
Key facts
- None carries too many meanings in Python: missing, unset, error signal, or a real value. A cache returning None cannot tell a missing key from a key mapped to None.
- PEP 661, the proposal to standardize sentinels, looked deferred at PyConDE in April; the Steering Council accepted it only nine days later.
- Since Python 3.15, sentinel is a built-in, and type checkers can narrow it like None. Older versions can use the typing_extensions backport.
- The old MISSING = object() pattern works at runtime but breaks type narrowing; TypeIs guards, Literal strings and single-member Enums each have trade-offs.
- Pydantic's MISSING, which separates an explicit null from a field never sent, left the experimental stage in Pydantic 2.14.
Why it matters
Python has long lacked a standard way to say "no value was given" apart from None. Developers filled the gap with object() instances, Enums and special Literal strings, and static type checkers handled these poorly. A built-in sentinel in Python 3.15, accepted through PEP 661, gives the language one standard answer that type checkers can narrow just like None. The author also notes that Python and its libraries already lean on sentinels such as Ellipsis, NotImplemented and Pydantic's MISSING.
Who it affects
Anyone who writes Python functions or libraries where None could mean more than one thing: cache authors, configuration and update APIs, and code that returns None to signal an error. Users of Pydantic, where MISSING is now past its experimental stage in 2.14, are affected too. Teams on older Python versions are covered by the typing_extensions backport.
How to use it
Define a sentinel with MISSING = sentinel("MISSING") and use it as the default for a parameter whose absence differs from None, as in the author's update_email example: MISSING means leave the email alone, None means delete it, and a string means set it. His advice is to prefer sentinels your library already provides and to replace old object() and Enum hacks with the built-in. On Python versions before 3.15, import it from typing_extensions. For the full argument, the EuroPython recording is the most current version of the talk.
How solid is it
This is a first-person write-up of a conference talk, not the PEP or release notes. The facts that the author states directly are that the Steering Council accepted PEP 661 nine days after the April PyConDE talk and that sentinel is a built-in since Python 3.15. The code example is a short illustration, not the built-in's full API. The author's slides from PyConDE are out of date on the PEP's status, and he points to the EuroPython recording as the current version.
Risks and caveats
The author warns that type-checker support is still catching up, so check your own checker before relying on the built-in. Old-style sentinels carry their own hazards: identity checks break when they are copied or pickled, and workarounds like TypeIs guards, magic Literal strings and the single-member Enum trick all have trade-offs. The advice to avoid returning None in place of raising an exception is separate, and he covers it in another talk.
“None is probably the most overloaded value in Python.”
— From the talk write-up on florianwilhelm.info