Python List Literals: Syntax and Behavior
python list literal: Learn how Python list literals work — from basic syntax and nested lists to mutability, edge cases, and when to prefer literals over list().
A python list literal is the most direct way to create a list in Python: you write square brackets and separate elements with commas. The expression [1, 2, 3] creates a list containing three integers, and [] creates an empty list. Unlike some languages where collection syntax requires a constructor call or a type annotation, Python treats the literal as a first-class expression that can appear anywhere a value is expected — in a return statement, as a default argument, inside a dictionary, or as part of a larger expression.
The Core Syntax of a Python List Literal
The grammar is intentionally minimal. Square brackets enclose zero or more elements, and commas separate them:
numbers = [1, 2, 3] names = ["ada", "grace", "linus"] empty = []
A trailing comma is allowed and often recommended for multi-line literals because it makes future additions produce cleaner diffs:
config = [ "host", "port", "timeout", ]
The trailing comma after "timeout" is valid and does not add an extra element. A single-element list literal [42] needs no comma — unlike a single-element tuple, which requires one: (42,) is a tuple, while (42) is just the integer 42 in parentheses.
List Literals vs. the list() Constructor
Python offers two ways to create a list: the literal syntax and the list() constructor. They are not interchangeable in every situation.
The literal creates a list from the elements written in the source code:
literal = [1, 2, 3]
The constructor creates a list from an iterable:
constructed = list((1, 2, 3)) constructed_from_range = list(range(3))
The constructor accepts any iterable, which makes it the right tool when the source data is already a tuple, a range, a generator, or a set. The literal, by contrast, is the right tool when you know the elements at write time.
There is also a subtle difference in how the two forms handle an existing list. list(existing_list) creates a shallow copy, while [existing_list] creates a list containing the original list as its single element:
original = [1, 2] copy = list(original) # [1, 2], a new list nested = [original] # [[1, 2]], one element that is the original list
This distinction matters when the goal is to avoid mutating a shared object.
Mutability and Object Identity
Every list literal evaluates to a new, distinct list object. Two literals with identical contents are not the same object:
a = [1, 2, 3] b = [1, 2, 3] print(a == b) # True, values are equal print(a is b) # False, distinct objects
This matters in real code. If you store a list literal as a default argument, every call that omits the argument receives the same object, because the default is evaluated once at function definition time:
def append_item(item, target=[]): target.append(item) return target
Calling append_item(1) twice returns [1] then [1, 1] because the same list object is reused across calls. The fix is to use None as the default and create a fresh list inside the function:
def append_item(item, target=None): if target is None: target = [] target.append(item) return target
This is one of the most common runtime surprises associated with list literals, and it follows directly from the fact that a literal creates a single object at evaluation time.
Edge Cases That Trip Up Developers
Several list literal behaviors are easy to misread.
An empty list literal [] is falsy in a boolean context. Code that checks if items: works as expected, but code that checks if items is not None: does not distinguish an empty list from a missing value. That is a semantic decision, not a bug, but it affects how you validate input.
A list literal can contain heterogeneous types:
mixed = [1, "two", 3.0, None]
Python does not enforce a single element type, which is convenient for heterogeneous records but means type checkers such as mypy will infer list[object] or a union unless you annotate the variable.
Repeated elements can be written explicitly, but the multiplication operator provides a shorthand:
zeros = [0] * 5 # [0, 0, 0, 0, 0]
For immutable elements this is safe. For mutable elements it is not, because the multiplication repeats the same object reference. The next section shows why that matters.
Nested List Literals and Shared References
Nested list literals are straightforward: a list literal can contain other list literals.
matrix = [ [1, 2], [3, 4], ]
This is the natural way to represent a fixed-size matrix or a grid in a script where a library like NumPy would be overkill. Each inner literal is evaluated independently, so matrix[0] and matrix[1] are distinct objects. Mutating one row does not affect the other.
The danger appears when a list literal is multiplied. The * operator repeats the same reference:
rows = [[]] * 3 # [[], [], []] rows[0].append(1) print(rows) # [[1], [1], [1]]
All three inner lists are the same object. The correct way to create independent empty lists is a comprehension:
rows = [[] for _ in range(3)]
The practical rule is: write nested literals explicitly when the inner lists are independent, and use a comprehension when the inner lists must be created fresh per element.
Performance and Memory Considerations
List literals are evaluated at runtime, not at compile time. Every time execution reaches a line containing a list literal, Python allocates a new list and evaluates each element expression. For small, fixed collections this cost is negligible. For a literal inside a hot loop, the allocation happens on every iteration:
for i in range(100_000): data = [0, 1, 2]
Each iteration allocates a new list. If the list contents never change, hoisting the literal outside the loop avoids repeated allocation:
constant = [0, 1, 2] for i in range(100_000): data = constant
This is not a micro-optimization to apply everywhere; it matters only when profiling shows the allocation is a bottleneck. The general mechanism is that a literal is an expression evaluated per execution, and the resulting object is not cached or reused.
Memory-wise, a list stores references to its elements, not the elements themselves. A list literal of integers therefore holds pointers to integer objects that already exist or are created as part of the literal evaluation. For large literals, the source code size and the runtime allocation both scale with the number of elements.
When a List Literal Is Not the Right Choice
A list literal is the clearest choice when the elements are known at write time and the collection is small. When the elements come from another iterable, the list() constructor is more direct:
values = list(gen())
When the elements must be computed, a list comprehension is usually more readable than a literal containing a loop or repeated expression:
squares = [x * x for x in range(10)]
When the collection should not change after creation, a tuple literal (1, 2, 3) provides immutability and can be used as a dictionary key. A list literal cannot be hashed, so using a list as a key raises TypeError.
When the collection must support fast membership tests and the elements are hashable, a set literal {1, 2, 3} is the better structure. The decision is not about syntax preference; it is about the operations the data will support. A list literal is the right default for ordered, indexable, mutable sequences, and the alternatives exist because those requirements are not universal.