How does typing.NamedTuple differ from collections.namedtuple?
answer
- Two spellings, one runtime shape
- Class body versus runtime factory call
- Annotations declare the fields and their order
- Methods, defaults and a docstring in the body
- Nothing checks the declared types at runtime
basics
~20 styping.NamedTuple is a class-statement spelling of the same runtime object: annotations declare the fields and their order, assignments supply defaults, and the body may hold methods and a docstring. Both produce a tuple subclass; neither checks the annotations at runtime.
solid answer
~50 sThey build the same kind of thing — a `tuple` subclass with named fields, `_fields`, `_replace()` and the rest — but they differ in how you write it and what a type checker can see. `collections.namedtuple` is a runtime factory taking a name and a field spec, so it suits field names computed at runtime, and it takes `defaults=` for trailing defaults. `typing.NamedTuple` is written as a class: each annotated name is a field in source order, an assignment on an annotated name is that field's default, and the body can carry methods, properties and a docstring, which the functional form cannot. The annotations are the point — a static checker types every field and every attribute access — but they are not enforced at runtime. The class form allows no multiple inheritance: only `Generic` may be mixed in, and generic records have been allowed since 3.11.
code
python · 11 linesfrom typing import NamedTuple
class Delivery(NamedTuple):
url: str
attempts: int = 0
def exhausted(self, limit: int) -> bool:
return self.attempts >= limit
d = Delivery("https://example.test/hook")
print(d, d.exhausted(3), Delivery._field_defaults)go deeper
Recognise both spellings and know they produce the same kind of record. Be able to write the class form with annotated fields and read a value out by name.
Explain the mechanics: annotated names become fields in source order, an assignment supplies a default, and the body may hold methods — while the runtime shape stays a tuple subclass either way.
Argue the choice in review: typed class form as the default, functional form only where names are computed, and a clear statement that annotations are not runtime validation for untrusted input.
Own the convention across services: which record style is standard, where validation actually happens, and how a team avoids the silent 'added a field in a subclass' failure as records evolve.
**Same runtime object, two front ends.** Both spellings end at a class that inherits from `tuple`, carries empty `__slots__`, exposes `_fields`, `_field_defaults`, `_make()`, `_asdict()` and `_replace()`, and whose instances index, unpack, compare and hash exactly like tuples. Nothing you learn about one is wasted on the other. What differs is authoring ergonomics and what a static type checker can say about the result. **The functional form.** `collections.namedtuple(typename, field_names)` is an ordinary function call evaluated at runtime. The field spec is a string like `"tenant event attempts"` or a sequence of names, which makes it the right tool when the field names are not known when you write the code — the header row of a delimited file, a database cursor description, a column list from configuration. Its keyword arguments cover the same ground: `defaults=` supplies values for the trailing fields (3.7 and later), `rename=True` rewrites illegal or duplicate names to `_0`, `_1`, …, and `module=` fixes the `__module__` the generated class reports so pickling resolves it. A type checker sees `Delivery = namedtuple("Delivery", "url attempts")` as a record whose fields all have unknown type; you get names, not types. **The class form.** `class Delivery(NamedTuple):` reads like any other class body: ```python from typing import NamedTuple class Delivery(NamedTuple): """One queued webhook delivery.""" url: str attempts: int = 0 def exhausted(self, limit: int) -> bool: return self.attempts >= limit ``` Three things happen that the functional form cannot do. Each annotated name becomes a field, *in the order written*, with its declared type visible to a checker. An assignment on an annotated name becomes that field's default, and it lands in `_field_defaults`; as with function parameters, a defaulted field may not be followed by an undefaulted one. And the body may hold methods, properties, classmethods and a docstring — a record with a small amount of derived behaviour on it stays one readable unit instead of a class plus a module of free functions. **Annotations are documentation to the interpreter.** Nothing checks them when the record is built: a field annotated `int` will hold a string without complaint. The enforcement is entirely a static checker's job. This matters when a candidate assumes a record validates its input — it does not, and that expectation is exactly what pulls teams towards a validating third-party model library instead. If you need runtime coercion or validation, a tuple-backed record is the wrong layer. **Inheritance is narrow.** Only `NamedTuple` — optionally together with `Generic` — may appear in the bases; anything else raises `TypeError` at class creation. Generic records have been supported since 3.11, and with the 3.12 type-parameter syntax you can write `class Pair[T](NamedTuple):` directly. You *may* subclass a finished record class, but new annotated names in the subclass do **not** extend the tuple: `_fields` stays as the base declared it, the tuple keeps its original length, and the new name is only an ordinary class attribute holding its default. Nothing raises, so the subclass looks like it worked until something reads `_fields`, unpacks the instance or serialises it and finds the field missing. That silent half-success is a real bug source; when a record needs another field, declare a new record or compose one value inside another. **Choosing between them.** Use the class form by default in typed code: field types, defaults, methods and a docstring in one place, and a checker that catches a misspelled attribute. Use the functional form when the shape is dynamic, or when you want a one-line throwaway record inside a function. One practical wrinkle applies to both: a generated class reports the module it thinks it was created in, and the functional form accepts a `module=` argument to correct that, which matters when instances are pickled and the class has to be found again by name on the other side. There is also an older functional spelling of `typing.NamedTuple` taking a list of `(name, type)` pairs, which is fine when the fields are computed but the types are known; the undocumented keyword-argument spelling of it is deprecated as of 3.13 and will be removed, so do not write it. **What neither one gives you.** Neither validates, neither lets you mutate a field, and neither makes the record stop being a tuple — it still compares equal to a plain tuple of the same values, still serialises as a JSON array, and still constructs positionally, which is what makes long records error-prone. When you want type-sensitive equality, keyword-only construction, per-field control or mutability, the decision is not which record spelling to use; it is to use a dataclass instead.
- Are the annotations on a typing.NamedTuple enforced when an instance is constructed?No. The class machinery reads the annotations only to decide the field names and their order; construction stores whatever you pass. A field annotated `int` will happily hold a string, and no error is raised until something downstream chokes. A static checker is the only enforcement, which is why a record is the wrong layer for validating untrusted input.
- Can you subclass an existing NamedTuple to add another field?Not usefully. Subclassing is allowed and inherits the methods, but a new annotated name in the subclass does not become a tuple field — `_fields` still reports the base's fields and the new name is just a class attribute with a default. Multiple inheritance is rejected outright with `TypeError`; only `Generic` may be mixed in. Declare a new record or compose instead.
- When would you still reach for the functional collections.namedtuple form in new typed code?When the field names are not known at authoring time — a delimited-file header, a cursor description, a configured column list — since the class form needs the names written out. `rename=True` is useful there too, turning illegal or duplicate incoming names into positional placeholders instead of raising. For anything with a fixed, known shape, the class form reads better and types better.
saying these in an interview costs you the question
- Says typing.NamedTuple validates field types at runtime
- Believes the two forms produce different runtime types
- Thinks the functional form can carry methods
- Claims multiple inheritance works for a NamedTuple class
- Assumes subclassing adds fields to the tuple