Built-in Data Types
Python ships with a rich set of built-in types out of the box — from None and bool to lists, dicts, and memoryview — each with a distinct role. This topic maps the full landscape before later topics dive into each one individually.
Python's built-in data types cover scalars (numbers, booleans, None), sequences (str, bytes, list, tuple, range), mappings (dict), sets (set, frozenset), binary data views (bytearray, memoryview), and the open-ended category of user-defined objects. This topic is a guided tour of the whole landscape — each type gets a concise treatment here, with full detail reserved for their own dedicated topics.
The Type Landscape at a Glance
Every value in Python belongs to exactly one type at any moment, and that type determines what operations are available. The built-in types fall into natural families:
None type -> NoneType (the absence of a value)
Booleans -> bool
Numbers -> int, float, complex
Text -> str
Binary data -> bytes, bytearray, memoryview
Sequences -> list, tuple, range, str, bytes
Mappings -> dict
Sets -> set, frozenset
User-defined -> classes you write yourselfNoneType
None is the sole instance of NoneType and represents "no value" or "nothing here." It is commonly used as a default placeholder, a function's implicit return value, or a sentinel meaning "not provided."
Always compare against None with is/is not, never ==, since None is a singleton and identity comparison is both correct and faster.
def no_return():
pass
result = no_return()
print(result) # None
print(result is None) # True -- the idiomatic way to check
print(type(None)) # <class 'NoneType'>bool
bool has exactly two instances, True and False. Under the hood, bool is a subclass of int (True == 1 and False == 0), a historical design choice that lets booleans participate directly in arithmetic while still printing and comparing as booleans.
print(isinstance(True, int)) # True -- bool IS-A int
print(True + True) # 2
print(sum([True, False, True])) # 2 -- counts the True valuesint, float, complex
int represents arbitrary-precision whole numbers, float represents IEEE-754 double-precision numbers, and complex represents numbers with a real and imaginary part. The dedicated Numbers topic covers literals, precision, and pitfalls in depth; here's the minimal shape of each.
i = 10
f = 10.5
c = 3 + 4j
print(type(i), type(f), type(c))
print(c.real, c.imag) # 3.0 4.0str, bytes, bytearray, memoryview
str is Python's immutable text type (a sequence of Unicode code points). bytes is its immutable binary counterpart (a sequence of integers 0-255). bytearray is the mutable version of bytes. memoryview provides a zero-copy view over the memory of another binary-supporting object (like bytes or bytearray), letting you slice or inspect raw memory without copying it.
str vs bytes is a strict separation in modern Python: you cannot silently mix text and binary data — encode() converts str to bytes, decode() converts bytes back to str.
text = "caf\u00e9"
data = text.encode("utf-8") # bytes
buf = bytearray(data) # mutable copy
buf[0] = ord("C")
view = memoryview(data)
print(bytes(view[:4])) # first 4 bytes, no copy of the full object made
print(type(text), type(data), type(buf), type(view))list, tuple, range
list is a mutable, ordered sequence. tuple is its immutable counterpart. range is a memory-efficient, immutable sequence of integers, generated lazily according to a start/stop/step formula rather than stored element-by-element.
nums = [1, 2, 3] # list -- mutable
coords = (10, 20) # tuple -- immutable
r = range(0, 10, 2) # range -- lazy, immutable
print(list(r)) # [0, 2, 4, 6, 8]
print(len(r), r[3]) # 5 6 -- range supports len() and indexing without materializingdict, set, frozenset
dict is a mutable mapping from unique, hashable keys to values, and preserves insertion order (a language guarantee since Python 3.7). set is a mutable, unordered collection of unique hashable elements, ideal for membership tests and set algebra. frozenset is set's immutable counterpart, which makes it hashable and usable as a dict key or set element.
scores = {"alice": 90, "bob": 85} # dict
tags = {"python", "web", "python"} # set -- duplicates collapse
frozen = frozenset(tags) # frozenset -- hashable
lookup = {frozen: "immutable tag set"}
print(scores, tags, lookup)User-Defined Objects
Beyond the built-ins, you can define your own types with class. A user-defined object is mutable by default (its instance attributes can be reassigned freely) and, unless you customize __eq__/__hash__, is compared by identity and hashed by its default identity-based hash.
Every class in Python ultimately inherits from object, the root of the type hierarchy — this is another expression of "everything is an object."
class Point:
def __init__(self, x: int, y: int):
self.x = x
self.y = y
p = Point(1, 2)
p.x = 99 # mutable by default
print(type(p), isinstance(p, object)) # every class implicitly inherits from objectQuick Reference: Mutability at a Glance
*memoryview itself has no independent value to mutate, but if it wraps a mutable buffer like bytearray, writes through the view mutate the underlying buffer.
Type Mutable? Ordered? Hashable?
NoneType no n/a yes
bool no n/a yes
int/float no n/a yes
complex no n/a yes
str no yes yes
bytes no yes yes
bytearray yes yes no
list yes yes no
tuple no yes yes (if elements are hashable)
range no yes yes
dict yes yes (ins.) no
set yes no no
frozenset no no yes
memoryview view* yes noKey Takeaways
Python's built-in types cover scalars (NoneType, bool, int, float, complex), text and binary sequences (str, bytes, bytearray, memoryview), general sequences (list, tuple, range), mappings (dict), and sets (set, frozenset).
Each type has a fixed mutability and hashability profile that determines how it can be used, especially as dict keys or set elements.
str and bytes are strictly separated: text vs binary data never mix silently.
User-defined classes extend the type system and, like everything else in Python, are themselves objects, ultimately inheriting from object.
Each of these types has its own dedicated topic elsewhere on the site; this page is the map, not the territory.
if value == None: ...
if value is None: ...
None is a singleton; identity comparison (is) is both idiomatic and immune to a class overriding __eq__ in a way that makes == None misleading.
cache = {} cache[[1, 2]] = "value" # TypeError: unhashable type: 'list' cache = {} cache[(1, 2)] = "value" # tuple is hashable
Mutable types (list, dict, set) are unhashable and cannot be used as dict keys or set elements; use an immutable equivalent like tuple or frozenset instead.
List the built-in scalar (non-container) types covered here.
NoneType, bool, int, float, complex.
Why is bool considered a subtype of int?
bool is implemented as a subclass of int, so True and False behave as 1 and 0 in arithmetic contexts, while isinstance(True, int) is True.
What is the practical difference between bytes and bytearray?
bytes is immutable binary data; bytearray is its mutable counterpart, supporting in-place item assignment and methods like append().
Why can frozenset be used as a dict key while set cannot?
frozenset is immutable and hashable; set is mutable and therefore unhashable, since its hash would be unstable if its contents changed after insertion.
What advantage does range have over a list of the same integers?
range stores only start, stop, and step and computes elements lazily on demand, using constant memory regardless of how many integers it logically represents.
Are user-defined objects mutable or immutable by default?
Mutable by default — their instance attributes can be reassigned freely unless the class specifically restricts this (e.g. frozen dataclasses, read-only properties).
What root type do all Python classes ultimately inherit from?
object — every class, built-in or user-defined, is ultimately a subclass of object.
Write code that builds a dict counting how many times each type appears in a mixed list of values.
values = [1, "a", 2.5, None, True, "b", 3] counts: dict[type, int] = {} for v in values: t = type(v) counts[t] = counts.get(t, 0) + 1 print(counts) type(v) gives each value's exact type, used as a dict key to tally occurrences with counts.get(t, 0) + 1.
What does this code print, and why are the two results different?
True then False -- bool is a subclass of int, so every bool IS an int (isinstance(True, int) is True), but the reverse isn't true: a plain int like 1 is not a bool.
Write a function describe(value) that returns a string naming the value's type and whether it is hashable, using a try/except around hash().
def describe(value): try: hash(value) hashable = True except TypeError: hashable = False return f"{type(value).__name__}: hashable={hashable}" print(describe([1, 2])) # list: hashable=False print(describe((1, 2))) # tuple: hashable=True hash() raises TypeError on unhashable (mutable) types like list, dict, and set; catching that exception detects hashability directly.
print(isinstance(True, int))
print(isinstance(1, bool))