Reading the Language

Making Sense of What Machines Say

Every language has rules. In English, the verb generally follows the subject: “The cat sleeps.” In Japanese, the verb migrates to the end. In Arabic, one reads right to left. These arrangements are not arbitrary whimsy; they are the conventions that emerged to allow speakers to understand one another with minimal error.

Bend the rules slightly, and you might still be understood: “Sleeping, that cat” is grammatically irregular but comprehensible, like your foreign friend. Break them sufficiently, and communication collapses: “Cat the sleeps” produces a look such as that on the face of someone born before Y2K hearing the latest drop of gen-Z lingo.

Programming languages operate on the same principle, though with considerably less tolerance for eccentricity. Follow the rules, and the computer understands. Violate them, and you may as well address it in interpretive dance. Unlike human languages, programming languages are necessarily rigid. There is no “you know what I meant.” No helpful context or benefit of the doubt. Just the rules, applied with the mechanical consistency of a gearbox.

This also means code is highly predictable. Learn a few patterns and you can read almost any program in any language fluently enough to know what it’s doing. At first, we will read more code than we write, so we can build the mental models which will elevate our use of coding agents. During the process, imagine your past self wrote the code you’re reading and you just don’t remember it, because that is often what it feels like once you’re making enough software.

Variables: Recognizing Names for Things

Imagine describing everything in full detail, every single time.

“Is your dog asleep?”

“Yes the small, partially brown canine creature with the Rorschach-like white patch on its chest that resides in my dwelling is currently in a state of semi-consciousness.”

You would be exhausted by lunchtime and friendless by dinner. We solved this problem millennia ago by inventing names: “Yes, Ruffles is sleeping.”

Names allow reference without redescription. This is such a fundamental convenience that we barely notice it, until it’s taken away. (It’s not a simple coincidence that before we can talk proper, we start by pointing to things.)

Variables are the embodiment of this principle; they are names for values. They are the first of the tools of abstraction available to us on our programming journey, often imagined as boxes to transport information around in. When writing a program we assign a value to a variable in a syntax depending on the language. For example, in python:

age = 25

Now, whenever the program encounters age, it knows to substitute 25. The name has been bestowed; the value put in the box.

name = "Alyce"

Now name refers to the text string: “Alyce”. Another value in another box.

When you are reading the code for a program, whether AI-generated or written by a human, spotting the variables tells you what the code is keeping track of. They’re the characters in the story. And the story is almost always best pictured as assembly lines in a factory, moving and processing information in the boxes.

price = 29.99
tax = price * 0.08
total = price + tax

The price variable appears multiple times. Reading this code, you can see that price is being reused—once to calculate tax, once to calculate the total. Should the price change, a single edit ripples through every calculation that depends on it. It is not just convenience, it is insurance against the chaos of maintaining the same information in seventeen different places.

The name “variable” is instructive: these things can vary. You might see price = 29.99 at the top of a function and price = 39.99 later. The calculations that reference price will dutifully use whatever value it currently holds, with the unquestioning loyalty of a golden retriever. If you want a pet rock instead, the equivalent is a “constant”, which is also a type of box, but you can never change what’s inside the box once you define it.

Naming Rules

The computer, because it has protocol in place of preference, insists on certain formalities. These rules help distinguish variables from other elements, applicable to most languages:

  • Names may contain letters, numbers, and underscores
  • They cannot begin with a number (no 2fast, though fast2 passes muster)
  • They cannot contain spaces (use underscores: my_name when you mean my name)
  • They are case-sensitive (age and Age are entirely different, just like it would be more exciting to have an Age while everyone born has an age)
  • They cannot be reserved words (you cannot name a variable words like if or for, these are used as part of the language)

These rules are nearly universal. Python, JavaScript, Ruby, Go, Rust—they all agree on the basics, much as human languages all seem to require both consonants and vowels. The disagreements emerge in the finer points: whether to allow dollar signs ($name is perfectly legal JavaScript but syntactic heresy in Python, while Php folk didn’t skip a beat), whether underscores may lead (Python permits _private as a convention for internal use), and whether hyphens belong anywhere near variable names (they do not, though CSS has somehow normalized background-color as if hyphens were perfectly reasonable).

Naming Conventions

When reading code, descriptive names are a gift. Not x, not data, not thing, but username, total_price, customer_email. The game is find the shortest name that conveys the most certainty, so one knows what the code is doing without requiring a detective’s skills. (That means total_price_cents is Even Better (tm).)

Languages have developed tribal allegiances to particular naming styles. Recognizing them is like recognizing accents - you can tell where the code is from at a glance:

snake_case — Lowercase words separated by underscores. Python’s native tongue, also spoken fluently in Ruby and Rust. Variables look like user_name, total_price, is_authenticated. Functions follow suit: calculate_tax(), send_email(). The aesthetic is pragmatic, workmanlike, occasionally accused of being verbose.

camelCase — Words joined together, each new word capitalized except the first. The dialect of JavaScript, Java, and much of the front-end web. Variables appear as userName, totalPrice, isAuthenticated. The style is compact, and reading it requires a slight mental pause at the capital letters to parse the boundaries.

PascalCase — Like camelCase, but with the first letter also capitalized. Reserved for classes and types in most languages: UserAccount, ShoppingCart, DatabaseConnection. When you see PascalCase, you’re usually looking at a blueprint rather than an instance. Python, JavaScript, Java, C# all use this convention for class names, a rare point of agreement.

SCREAMING_SNAKE_CASE — All caps with underscores. The universal convention for naming your pet rocks, constants - values that should never change in the course of the code running: MAX_CONNECTIONS, DEFAULT_TIMEOUT, API_KEY. The visual shout serves as a spiritual warning: do not modify this value unless you enjoy sufferi.. ahem debugging.

# Python speaks snake_case
user_name = "alice"
def calculate_total_price():
    pass
// JavaScript prefers camelCase
let userName = "alice";
function calculateTotalPrice() {}
// Java uses camelCase for variables, PascalCase for classes
String userName = "alice";
class ShoppingCart {}

The convention you use matters less than using it consistently. A codebase that mixes user_name and userName is like a document that alternates between British and American spelling - comprehensible, but unsettling. Please don’t do it, por favour.

Explore different naming conventions used across programming languages. Click each style to see examples.

Data Types: What Kind of Thing Is This?

The computer, despite its speed, requires explicit categorization. It needs to know what kind of thing it’s dealing with, because different kinds of things permit different operations. (This is true for the abstraction level of programming you are like to be doing, and not at deeper/lower levels.) For example, you can add numbers together, but the notion of “adding” two true/false values is philosophically suspect. You can convert text to uppercase, but asking for uppercase 7 produces only confusion. The type of a thing determines what you can reasonably do with it.

This is saliently essential because when reading code, recognizing data types tells you what operations make sense. If you see "4" + "2", you know the quotation marks mean these are strings, not numbers—so the result will be "42" (text concatenation), not 6 (arithmetic).

Returning to our box metaphor: if variables are boxes, then types describe what kind of thing the box is designed to hold. Some boxes hold numbers. Some hold text. Some hold true/false values. And some (more interesting ones) hold other boxes, or instructions for building boxes, or references to boxes stored elsewhere in the warehouse. The type tells you what you’re dealing with before you open the lid. If you want to pursue this rabbit hole, ask your favourite LLM to teach you about Primitive Types and Data Structures.

Explore the fundamental data types. Click each type to see examples and available operations.

Numbers

Numbers serve for mathematics, counting, and measurement: any situation requiring numeric values. When you see them in code without quotes, you know arithmetic is afoot. If you know your math, you know there are infinite numbers between any two numbers, and thus numbers on computers also exist in different sub-types with different precision. If you count that as complex and heavy, the good news is it won’t be relevant in most cases!

Try different values and operators to see how number operations work.

Text (Strings)

Text (words, sentences, tweets, etc) goes by the technical name “string”, referring to a string of characters. The quotation marks are your signal. See quotes, think text. That is why "5" + "3" yields "53" (concatenated text), not 8 (arithmetic sum). The quotation marks transform digits into characters. The computer sees text where you see numbers, and acts accordingly.

Experiment with string operations. Try concatenation, case changes, and more.

Booleans (True/False)

Some things exist in binary states: yes or no, on or off, present or absent. For these we have Booleans, named after George Boole, a nineteenth-century mathematician who may or may not be flattered to learn how often his name appears in error messages. Booleans find their natural habitat in decisions. When you see True or False in code, you’re looking at logic gates opening or closing.

Toggle A and B to see how boolean logic operators (AND, OR, NOT) combine conditions.

When Types Collide

What happens when you try to combine different types? The answer depends on the language. Some like Rust will not even compile, others like Python will try to make it work, but you won’t know if it works till you try. That might sound like a feature, but when you need guarantees for production code, it’s a bug.

See what happens when operations involve mismatched types. Click through the scenarios.

Beyond Simple Values: Collections and Objects

So far our boxes have held single values: one number, one string, one true/false. But programs routinely need to manage collections of things. A shopping cart contains multiple items. A user has multiple bookmarks. A playlist holds multiple songs. For these, we need boxes that can hold other boxes.

Lists (or arrays, depending on the language) are ordered collections of values. Think of them as a row of numbered compartments within a single box:

# Python calls them lists
fruits = ["apple", "banana", "cherry"]
prices = [1.50, 0.75, 2.25]

# Access by position (starting from 0, not 1)
first_fruit = fruits[0]  # "apple"
// JavaScript calls them arrays
const fruits = ["apple", "banana", "cherry"];
const firstFruit = fruits[0];  // "apple"

Zero-indexing (starting the count at 0 rather than 1) is one of programming’s more counterintuitive conventions. The first item is at position 0, the second at position 1, and so forth. This has caused enough off-by-one errors to fill a modest library, because the convention persists across nearly every language. You will adapt, and eventually, counting from zero will feel natural. Eventually.

Dictionaries (or objects, or maps, or hashes) store key-value pairs. Rather than numbered compartments, imagine labeled drawers within a box:

# Python dictionaries
user = {
    "name": "Alice",
    "age": 30,
    "email": "alice@example.com"
}

# Access by key
user_name = user["name"]  # "Alice"
// JavaScript objects
const user = {
    name: "Alice",
    age: 30,
    email: "alice@example.com"
};

// Access by key (two ways)
const userName = user.name;      // "Alice"
const userAge = user["age"];     // 30

When you encounter curly braces containing key-value pairs, you’re looking at structured data. Image a whole treasure chest instead of a box, with labeled compartments rather than numbered slots. This is how programs represent most composite real-world entities: a user, an order, a product, a message.

Aside: The Box That Contains a Map, Not the Territory

Here is where our box analogy encounters its most important nuance, and where many newcomers stumble on the invisible caveat. For primitive values like numbers/strings/booleans, the box contains the value directly. When you write age = 25, the number 25 lives inside the box labeled age. Copy that box to another variable, and you get an independent copy:

age = 25
backup = age      # Copy the value
age = 26          # Change the original
print(backup)     # Still 25 — independent copy

But for complex values like lists, dictionaries, objects, the box contains something more like an address. A note saying “the actual data is stored in warehouse section 47B.” When you copy this kind of variable, you’re copying the address, not the contents:

original = ["a", "b", "c"]
new_copy = original      # Copy the address, not the list
new_copy.append("d")      # Modify through the original
print(original)          # ["a", "b", "c", "d"] — same list!

Both original and new_copy point to the same warehouse location. Change the contents through one, and the other sees the change. This is called reference semantics or “pass by reference,” as opposed to value semantics or “pass by value” for simple types.

The practical implications are significant. Different languages handle this differently. In JavaScript, objects and arrays are always references. In Python, the same. In some languages like Rust, the rules are stricter and more explicit. In others like Java, primitive types are values while objects are references. The specifics vary, but the concept persists: some boxes contain things directly, while other boxes contain pointers to things.

Operators: Understanding What Happens

If variables are the nouns of programming (the things we speak about), then operators are the verbs. They describe actions, transformations, the things that happen to things. When reading code, operators tell you what’s being done to the values.

Arithmetic Operators

sum = 10 + 5        # Addition
difference = 10 - 5  # Subtraction
product = 10 * 5     # Multiplication
quotient = 10 / 5    # Division
remainder = 10 % 3   # Modulo (remainder)
power = 2 ** 3       # Exponentiation

These operators are universal, though the symbols occasionally diverge. Most languages use ** or ^ for exponentiation, % for modulo (the remainder after division—useful for determining whether a number is even, or cycling through a list). Division has its own quirks: in Python 3, / always produces a decimal result, while // performs integer division that rounds down. JavaScript uses Math.pow() or ** for exponents. The details vary; the operations remain constant.

The = sign deserves attention. In mathematics, it declares equality, a statement of fact. In programming, it performs assignment, which is more a command. “Take this value and attach it to this name.” Totally different.

x = 5      # Assign 5 to x
x = x + 1  # Take current x, add 1, store the result back in x

That says “Fetch the current value of x, add one to it, and store the result as the new value of x.” The old value is replaced, and the variable has varied. When you read x = x + 1, you’re seeing a value being updated, not an equation being declared.

Because incrementing and decrementing values is so common, many languages provide shorthand:

# Python
x += 1    # Same as x = x + 1
x -= 5    # Same as x = x - 5
x *= 2    # Same as x = x * 2
// JavaScript goes further
x++;      // Increment by 1
x--;      // Decrement by 1
x += 1;   // Also works
// Go uses the same shorthand
x++
x--
x += 10

When reading code, these compound operators (+=, -=, *=, ++, --) signal that a value is being updated in place. The box’s contents are being modified, not replaced wholesale.

Comparison Operators

These examine relationships between values and deliver verdicts of True or False. Note the double equals == for comparison. A single = assigns, while a double == interrogates.

x == y    # Equal?
x != y    # Not equal?
x < y     # Less than?
x > y     # Greater than?
x <= y    # Less than or equal?
x >= y    # Greater than or equal?

These operators are universal, though JavaScript adds a curious wrinkle: it distinguishes between == (loose equality, with type coercion) and === (strict equality, no coercion). In JavaScript, 5 == "5" is true (the string gets converted), but 5 === "5" is false (different types, no match). This subtlety is responsible for a thousand bugs and at least as many style guide debates. Most modern JavaScript style guides recommend always using === to avoid surprises.

When reading code, comparison operators signal that a decision is being made, it is a fork in the road where the program will go one way or another.

Change the values and see how comparison operators evaluate to True or False.
Strings can be compared too. Try different text values to see equality and ordering.

Syntax: The Grammar Rules

Syntax is the set of rules that determines what constitutes valid code and what constitutes gibberish. It is the grammar of the machine, and the machine is unforgiving in matters of grammar. Different languages have different syntactic customs, much as human languages have different rules about word order and punctuation. The core concepts transfer; the details require adjustment.

Python relies on indentation to indicate structure. Those leading spaces are not decorative, they’re load-bearing:

if temperature > 30:
    print("It's hot")      # This belongs to the if
    drink_water()          # So does this
print("Moving on")         # This runs regardless of 'if'

JavaScript uses curly braces. Indentation is merely conventional, for human readability:

if (temperature > 30) {
    console.log("It's hot");  // Inside the if
    drinkWater();
}
console.log("Moving on");     // Outside

Python ends statements with newlines. JavaScript, Java, C, and their relatives end statements with semicolons. Go technically requires semicolons but inserts them automatically at line endings. Luckily we will almost never have to worry about this kind of thing thanks to linters.

Some universal rules though:

  • Strings require matching quotation marks ("Hello", not "Hello')
  • Variables must be defined before they may be used (the computer lacks precognition)
  • Parentheses, brackets, and braces must be properly paired
  • Reserved words cannot be repurposed

Violate these rules, and the computer produces a syntax error:

name = "Alice  # Missing closing quote - syntax error!

Syntax errors are, paradoxically, among the friendliest errors one can encounter. The computer tells you precisely where it lost you.

  File "example.py", line 1
    name = "Alice
                 ^
SyntaxError: EOL while scanning string literal

Error messages often look intimidating, with all the warmth of a legal document. But it’s actually helpful, if you focus on the what not the how. Translated into plain English: “On line 1, around this character, I reached the end of the line while still searching for the closing quotation mark.”

Semantics: What Does It Mean?

Syntax concerns grammar - whether the computer can parse what you’ve written. Semantics concerns meaning - what your code actually does, as opposed to what you hoped it might do. You can read grammatically impeccable code that’s nonetheless nonsensical:

age = "twenty-five"  # Syntactically fine
next_age = age + 1   # Semantic error: can't add number to string

The first line passes syntactic muster; assigning a string to a variable named age violates no rules. But the second line attempts to add a number to text, which is like trying to add a giraffe to the cocktail. The grammar is correct, but the meaning is absent.

This distinction explains why testing is not optional. The computer catches syntax for you, with all the diligence of an untiring auditor. Semantic errors, where the code executes but does the wrong thing entirely, are your responsibility to discover. The computer will happily do the wrong thing with perfect efficiency, as long as the grammar is correct.

Can you tell syntax errors from semantic errors? Test your classification skills.

Reading a Complete Program

Let us examine these elements in a small but complete program. An AI assistant has written a tip calculator. Your task is not to write it yourself, but to understand what it does:

# Calculate the total price with tax
price = 29.99
tax_rate = 0.08
tax = price * tax_rate
total = price + tax

print("Price: $" + str(price))
print("Tax: $" + str(tax))
print("Total: $" + str(total))

Reading through, line by line:

  1. A variable named price is created and assigned the value 29.99
  2. A variable named tax_rate receives 0.08 (representing 8 percent)
  3. The tax is calculated by multiplying price by rate
  4. The total emerges from adding price and tax together
  5. The results are printed for the waiting audience

Lines beginning with # are comments, notes for human readers that the computer politely ignores. They serve as marginalia, explanatory footnotes, and occasionally as apologies to future readers of the code.

Mistakes to Spot in Generated Code

Every programmer makes mistakes. AI can make them too. Knowing them in advance means you can spot them when reviewing generated code:

Learn to recognize common mistakes. Click “Show Fix” to see the corrected code.

Forgetting quotation marks around strings: Without quotes, Python assumes Alice is the name of some variable. Finding no such variable, it expresses disappointment. JavaScript does the same. Every language does.

Using a variable before defining it: The computer, lacking precognition, can’t know what greeting will become. It only knows what it is right now, which is nothing. In JavaScript, you’ll see undefined; in Python, a NameError; in stricter languages like Go, the code won’t even compile.

Confusing = and ==: One assigns, the other asks. This particular confusion has caused enough debugging sessions to qualify as a minor epidemic. Some languages (like Go) mitigate this by prohibiting assignment inside conditionals. Others (like JavaScript with ===) add a third variant that’s even stricter about type matching.

Case sensitivity: To the computer, name and Name are as different as chalk and cheese. The capitalization is not optional decoration. This bites hardest in languages where convention matters: Python’s True must be capitalized (not true), while JavaScript’s true must not be (not True).

Off-by-one errors: When counting starts at zero, the last item in a 10-element list is at position 9, not 10. Asking for position 10 produces an error or unexpected behavior. These mistakes are so common they have their own name and their own category of jokes.

Unintended reference sharing: Remember our earlier discussion of boxes containing addresses? Passing a list to a function that modifies it, expecting the original to remain unchanged, is a classic source of bugs. When reading code, watch for functions that mutate their arguments.

All of these produce errors, but errors of the fixable variety. The computer reports the problem. You address it. This cycle of mistake and correction isn’t failure; it is the process.

The Building Blocks

What we’ve covered here (variables, types, operators, syntax, semantics) are the fundamental building blocks. They are essential, even though you may never have to deal with them as you learn to code with AI. Fundamentally it is all moving around boxes of value and operators. Every program ever written, from the operating systems that run supercomputers to the small script that renames your holiday photos, is constructed from these basics:

  • Values stored in variables that can be referenced by name
  • Types that determine what operations make sense
  • Collections (lists, dictionaries) that hold multiple values
  • The distinction between copying values and sharing references
  • Operators that transform, combine, and compare those values
  • Syntax rules that vary by language but share common principles
  • Semantic meaning that determines what actually happens

At this point, you are equipped to:

  • Recognize variables and understand what they track
  • Identify different types of data (numbers, text, true/false, collections) in code you read
  • Understand what operations do (arithmetic, comparisons, string concatenation)
  • Recognize naming conventions and syntax patterns across languages
  • Anticipate the difference between value and reference semantics
  • Read simple code and divine its purpose
  • Spot common mistakes in generated code

This is the foundation. Everything that follows (control structures, functions, classes, libraries, etc) rests upon these elementary principles. They are the alphabet of the language. Familiarizing yourself with them is the end of this chapter, and also the moment the rest of the journey becomes possible.


Next: Now that we get the language, we can start to understand how to convey complex instructions, how programs make decisions and repeat actions, how to make what happen when - the art of control flow.