Skip to content
Happy Programming Guide
Start learning
Debugging & Errors

Assignment to Expression with Array Type

Why Python raises "cannot assign to expression here" when you annotate a subscript, and the same-sounding TypeScript and C errors, each with its fix.

A red ladybird on a leaf in the sun

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#

Python
data: dict[str, list[int]] = {}

data["scores"]: list[int] = [1, 2, 3]
Output
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:

Python
data: dict[str, list[int]] = {}
data["scores"] = [1, 2, 3]        # no annotation here

The same applies to attributes and unpacking:

Python
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#

Python
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__:

Python
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:

Python
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#

Python
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:

TYPESCRIPT
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:

TYPESCRIPT
const updated = values.map((v, i) => (i === 0 ? 99 : v));
const appended = [...values, 4];

The second is a tuple whose shape does not match:

TYPESCRIPT
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:

TYPESCRIPT
let items: (string | number)[] = ["a", 1, 2];

A related trap is that as const makes everything readonly:

TYPESCRIPT
const config = { tags: ["a", "b"] } as const;
config.tags.push("c");     // error - readonly

C and C++#

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:

C
#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:

C
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:

C
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#

Reading Python errors methodicallyRead next

Keep reading

Keep going — pick your next guide

The fastest way to improve is to read one guide, then build the thing it describes. Start with the basics, or jump straight to a project.

Ask a question or share what worked

Your email address will not be published. Required fields are marked *