What is Python's `Ellipsis` object, and what is `...` actually used for?
answer
- Three dots are a real object
- One shared instance, no behaviour of its own
- Meaning comes from whoever receives it
- Stub bodies still return None
- types.EllipsisType names its type since 3.10
basics
~20 s... is Ellipsis, the single instance of a built-in type with no behaviour of its own. It is truthy and does nothing; code gives it meaning - as a stub body, inside a subscript, or in annotation forms.
solid answer
~50 s`Ellipsis` is a built-in singleton, and the literal `...` is another spelling of it, so `... is Ellipsis` is True. Its type has been available as `types.EllipsisType` since Python 3.10. The object carries no behaviour: it is truthy, hashable, and has no methods worth calling. Three uses account for nearly all sightings. As a statement it is a valid expression, so `def f(): ...` is a complete function body - the convention in stub files and protocol method definitions, and it still returns None. Inside a subscript it is simply passed through to `__getitem__`, where an N-dimensional array library may interpret it as "all remaining axes"; Python itself assigns it no meaning there. In annotations it appears in forms such as `Callable[..., int]` and `tuple[int, ...]`. Because it is a shared global, it is a poor private sentinel.
code
python · 9 linesfrom types import EllipsisType
print(... is Ellipsis) # True
print(type(...) is EllipsisType) # True
print(bool(...)) # True
def rebuild_shard(name: str) -> int: ...
print(rebuild_shard("primary")) # Nonego deeper
Recognise ... when you meet it: it is the Ellipsis object, it is legal wherever an expression is, and a function whose body is ... simply returns None rather than failing.
Explain that the object carries no behaviour and that meaning is supplied by the receiver - a subscript hands it to __getitem__ untouched, and the typing machinery reads it in Callable[..., int] and tuple[int, ...]. Name types.EllipsisType and its 3.10 arrival.
Show the judgement: an ellipsis body is safe only where the code never runs, so raise NotImplementedError in anything reachable. Explain why a globally shared singleton is disqualified as a private sentinel, and when publishing it as a documented marker is nevertheless defensible.
Own it as a convention question: which placeholder your codebase uses in stubs, whether ... ever carries semantic weight in your public APIs, and the review rule that keeps an ellipsis body from silently shipping as a live function returning None.
`Ellipsis` is one of Python's small set of built-in singletons, alongside `None`, `True`, `False` and `NotImplemented`. The literal `...` evaluates to that same object, so `... is Ellipsis` is True and there is never more than one of it in an interpreter. Its type prints as `ellipsis` and has been reachable by a public name, `types.EllipsisType`, since Python 3.10; before that you compared against the object itself because the type had no importable name. The object is truthy, hashable, immutable in practice and carries no behaviour at all - which is precisely the point. It is a token that other code interprets. **Syntax note.** In Python 3 the `...` literal is valid anywhere an expression is, not only inside a subscript as in Python 2. That change is what made the stub-body idiom possible. **Use one: a placeholder body.** Because `...` is an expression, an expression statement made of it is a complete suite: ```python def rebuild_shard(name: str) -> int: ... ``` This is the conventional body in `.pyi` stub files, in `typing.Protocol` method declarations, and in overload declarations, where the point is to declare a signature without an implementation. `pass` would work identically - both leave the function returning `None` - and the difference is purely conventional: `...` reads as "the body is intentionally elsewhere", `pass` as "the body is intentionally empty". Note the trap: a function whose body is `...` is *not* unimplemented at runtime. Call it and you get `None`, silently. If a caller must not reach it, raise `NotImplementedError` instead; the ellipsis body belongs where the code is never executed, such as a stub file or a protocol declaration. **Use two: a subscript token.** Python gives `...` no meaning inside `[]`. The subscript machinery builds whatever object the brackets describe and hands it to `__getitem__` unchanged: ```python class Cube: def __getitem__(self, key): return key print(Cube()[..., 0]) # (Ellipsis, 0) print(Cube()[1:2, ...]) # (slice(1, 2, None), Ellipsis) ``` Everything interesting happens in the receiving class. N-dimensional array libraries adopted the convention that `...` stands for "as many full slices as are needed to fill the remaining axes", so `a[..., 0]` selects the first element along the last axis regardless of rank. That meaning is the library's, not the language's, and a class is free to interpret the token differently or reject it. **Use three: annotation forms.** `Callable[..., int]` means "any parameter list, returning int", and `tuple[int, ...]` means a homogeneous tuple of unknown length. In both, `...` is the ordinary object being used as a marker by the typing machinery. **Why it is a poor sentinel.** It is tempting to write `def update(alias=...)` and test `alias is ...` instead of building a private marker. The problem is ownership: `Ellipsis` is a single global object that every module in the process shares, so a caller, a framework or a serialization layer may pass it for reasons of their own, and your check cannot distinguish "omitted" from "the caller genuinely meant Ellipsis". Some libraries do adopt it deliberately as a public "leave unchanged" marker, which is a defensible published convention rather than a private one - but for a private not-supplied marker, a module-level `object()` you alone can produce is the correct tool. `NotImplemented` is out for the same reason and worse: it has a specific meaning in comparison protocols and using it elsewhere is a category error. **Small facts that come up.** `bool(...)` is True, so an ellipsis never disappears in a truth test. `Ellipsis` is a name in the builtins namespace, so it can be shadowed by a local or module-level assignment, but the `...` literal still produces the object - the literal is compiled, not looked up. `repr(...)` is `Ellipsis`. And because there is exactly one instance, an identity check is the only sensible way to test for it; `types.EllipsisType` exists mainly for `isinstance` checks and for writing the type in an annotation. In an interview this is a curiosity rather than a gate. What a strong answer shows is the shape of the reasoning: `...` is an inert object, the meaning lives in whoever receives it, and its very shared-ness is what disqualifies it from the sentinel role.
- What is the difference between a function body of `pass` and one of `...`?Nothing at runtime - both compile to an empty suite and the function returns None. The difference is convention: `...` is the house style in stub files, protocol method declarations and overload declarations, where a signature is being declared without an implementation, while `pass` reads as an intentionally empty block in ordinary code. Neither raises, which is the trap.
- Why is `...` a poor choice for a private not-supplied marker?Because it is a single global object shared by every module in the process. A caller, a framework or a deserializer can hand you `Ellipsis` for its own reasons, and `arg is ...` then cannot tell "argument omitted" from "the caller meant Ellipsis". A module-level `object()` is unique to your code, which is the whole property a sentinel needs.
- How would you write an isinstance check or an annotation for the ellipsis object?Use `types.EllipsisType`, added in Python 3.10 - `isinstance(x, EllipsisType)` works, and the name is usable in an annotation. Before 3.10 the type had no public name and the idiomatic test was `x is ...`, which is still the more direct check since there is only ever one instance.
It is a blank sticker. The sticker means nothing by itself; the meaning is whatever the shelf you stick it on has agreed it means.
saying these in an interview costs you the question
- Says `...` is only valid inside a subscript
- Believes a body of `...` raises NotImplementedError
- Thinks `...` is a special spelling of None
- Claims Python itself defines what `...` means in slicing
- Uses `...` as a private missing-argument marker
- Says there can be several distinct Ellipsis objects