← First principles STAGE 01 · 18 MIN

Values, names & state

Trace a program one line at a time and see why two names can point to one changing object.

Open in the laboratory ↗

BY THE END, YOU CAN

  • Distinguish a value from a variable name
  • Predict the effect of assignment and mutation
  • Model a job with meaningful types
Two names, one object: a change made through backlog is visible through jobs.

Diagram description: jobs → ["email", "report"] (refers to); backlog → ["email", "report"] (refers to); backlog.append( "report") → ["email", "report"] (mutates)

A program is a sequence of state changes

Software engineering starts with turning a vague request into precise behavior.

Relay receives jobs, remembers them, and eventually performs their work. A program describes how its state (the data it remembers at a given moment) changes: perhaps pending jobs increases from zero to one. Python executes statements (instructions) in order.

An expression computes a value; assignment binds a name to that value. Read the small program below slowly and write the value of pending after each line. This tracing habit is more useful than memorizing syntax without a model.

Python
pending = 0
pending = pending + 1
label = "send welcome email"
print(label, pending)

Types carry meaning

A string represents text, an integer represents a whole-number count, and a Boolean represents a true-or-false decision.

Choosing a type is part of designing the system: a job identifier may be text even when it contains digits, because you never add two identifiers. Python checks many type constraints while the program runs. Converting input explicitly makes assumptions visible.

The string "3" and the integer 3 have different behavior. A failed conversion is information about an input that does not fit your contract (the rules your code states about what input it accepts). A dictionary (dict) groups related values under keys: job["attempts"] reads the value stored under the key "attempts", and assigning to it changes that value.

Its get method, as in job.get("owner", "nobody"), returns a default instead of failing when the key is missing.

Python
job = {"id": "job-1", "attempts": 0, "done": False}
job["attempts"] = job["attempts"] + 1
print(job["attempts"], job.get("owner", "nobody"))

Names are labels, not boxes

A name refers to an object.

A list is mutable: it can be changed in place after it is created. Assigning one list to another name does not create an independent list. If both names reference the same list, appending through either name changes the shared object.

In contrast, integers are immutable: adding one to an integer creates a new integer value and rebinds the name. Use a fresh collection when state should be independent. Before running the example, predict both printed lists.

This tiny distinction later explains surprising bugs in caches (stored copies of earlier results), test fixtures (shared setup data for tests), and state shared between workers (programs that take jobs and run them).

Python
jobs = ["email"]
alias = jobs
alias.append("report")
print(jobs)
print(alias)

COMMON FAILURE MODE

Using assignment as if it copied a collection. A shallow copy (a copy of the outer list only) also still shares any nested mutable objects.

TRY IT YOURSELF

Create a job with a text name, an integer attempt count, and a Boolean completed flag. Make two names refer to the same list of jobs, mutate one, and explain the result before running it.

RETRIEVAL CHECK / APPLY THE IDEA

Make the call in the laboratory.

The two-question checkpoint, your notes, and your saved progress are in the laboratory. Free, with no account.

Open in the laboratory ↗

CONNECT IT TO RELAY

Represent Relay's first job with a dictionary containing id, kind, attempts, and status. Write down which fields may change.