This error appears in three languages with slightly different wording, and the causes are unrelated. In Python it means you wrote a type annotation on something that is not a plain name. In TypeScript it means you are assigning to a read-only array or a tuple of the wrong shape. In C it means you tried to assign one array to another. This guide covers all three, because the phrase sends people to the wrong answers.
Python#
data: dict[str, list[int]] = {}
data["scores"]: list[int] = [1, 2, 3]
SyntaxError: only single target (not subscript) can be annotated
Newer versions phrase it as cannot assign to expression here. Maybe you meant '==' instead of '='?, which is misleading — the comparison suggestion is not what is wrong.
The rule is simple: an annotation can only be attached to a plain name, not to an expression. Annotate the container once, then assign into it normally:
data: dict[str, list[int]] = {}
data["scores"] = [1, 2, 3] # no annotation here
The same applies to attributes and unpacking:
obj.value: int = 5 # SyntaxError
obj.value = 5 # fine
a, b: int = 1, 2 # SyntaxError - cannot annotate a tuple target
a: int = 1
b: int = 2
Declaring typed containers#
scores: list[int] = []
lookup: dict[str, int] = {}
pair: tuple[str, int] = ("a", 1)
maybe: int | None = None
names: set[str] = set()
# Nested
grid: list[list[float]] = [[0.0] * 3 for _ in range(3)]
config: dict[str, dict[str, str]] = {}
For a class, annotate the attributes at class level and assign in __init__:
class Report:
rows: list[dict[str, str]]
title: str
def __init__(self, title: str) -> None:
self.title = title
self.rows = []
Or let a dataclass generate the constructor:
from dataclasses import dataclass, field
@dataclass
class Report:
title: str
rows: list[dict[str, str]] = field(default_factory=list)
field(default_factory=list) rather than = [] is important. A mutable default would be shared by every instance, which is one of the most common Python bugs there is.
Older Python versions#
from typing import List, Dict, Optional
scores: List[int] = []
lookup: Dict[str, int] = {}
maybe: Optional[int] = None
Built-in generics such as list[int] work from Python 3.9. Before that you need the typing equivalents. If you see TypeError: 'type' object is not subscriptable on an annotation, that is the version difference.
TypeScript#
Two different errors share this shape. The first is a readonly array:
const values: readonly number[] = [1, 2, 3];
values[0] = 99;
// Index signature in type 'readonly number[]' only permits reading
values.push(4);
// Property 'push' does not exist on type 'readonly number[]'
Make a copy instead of mutating:
const updated = values.map((v, i) => (i === 0 ? 99 : v));
const appended = [...values, 4];
The second is a tuple whose shape does not match:
let pair: [string, number] = ["a", 1];
pair = ["a", 1, 2]; // Source has 3 elements, target allows only 2
pair = [1, "a"]; // Type 'number' is not assignable to type 'string'
A tuple fixes both the length and the type at each position. If you want a variable-length list, declare an array type instead:
let items: (string | number)[] = ["a", 1, 2];
A related trap is that as const makes everything readonly:
const config = { tags: ["a", "b"] } as const;
config.tags.push("c"); // error - readonly
C and C++#
int a[5] = {1, 2, 3, 4, 5};
int b[5];
b = a; // error: assignment to expression with array type
An array name in C is not a modifiable value — it decays to a pointer to its first element, and you cannot assign to that. Copy the contents instead:
#include <string.h>
memcpy(b, a, sizeof(a)); // for any type
for (int i = 0; i < 5; i++) { // or explicitly
b[i] = a[i];
}
For strings, the same applies:
char name[20];
name = "Ana"; // error
strcpy(name, "Ana"); // correct
snprintf(name, sizeof(name), "%s", "Ana"); // safer - bounded
Wrapping the array in a struct restores assignment, because structs are assignable:
typedef struct { int values[5]; } Row;
Row a = {{1, 2, 3, 4, 5}};
Row b;
b = a; // legal - copies the whole struct
Working out which one you have#
| Message contains | Language | Fix |
|---|---|---|
| only single target … can be annotated | Python | Annotate the name, not the subscript |
| cannot assign to expression here | Python | Same |
| only permits reading | TypeScript | Copy rather than mutate |
| Source has N elements | TypeScript | Tuple shape mismatch |
| assignment to expression with array type | C | Use memcpy or a loop |
Questions people ask#
Do Python type annotations do anything at run time?
Almost nothing — they are stored in __annotations__ and otherwise ignored. Their value is in editors and in checkers such as mypy. The syntax error above is still a syntax error, though, because it happens at parse time.
Can I annotate a variable without assigning it?
Yes: scores: list[int] on its own declares the type without creating the variable. Useful at class level and in stub files.
Why does TypeScript allow readonly at all?
To document and enforce that a value should not change. It catches accidental mutation at compile time, which is particularly valuable for shared configuration and function parameters.
Is there any way to assign arrays directly in C?
Not directly. Wrap them in a struct, or copy with memcpy. C++ users can use std::array or std::vector, both of which support assignment.
Where to go next#
- Why is my Python code not working? — reading syntax errors properly.
- Python lists explained — the container being annotated.
- Advanced data structures — arrays versus lists across languages.