skip to content

What is Python's `Ellipsis` object, and what is `...` actually used for?

level: middleimportance: nice to knowfreq 20%

answer

  1. Three dots are a real object
  2. One shared instance, no behaviour of its own
  3. Meaning comes from whoever receives it
  4. Stub bodies still return None
  5. 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 lines
python
from 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"))    # None

go deeper

for a junior

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.

for a middle

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.

for a senior

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.

for a principal

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

context