Your New Colleague

Programming as a Conversation (With Someone Who Has Read Everything)

You’ve been hired to work in a country where you don’t speak the language. On your first day, you discover that your employer has thoughtfully provided a translator who speaks both languages fluently, has memorized every company manual ever written, and works for free. The translator is excellent. The translator is also, on occasion, confidently wrong about things, in the manner of someone who has read so much that they sometimes confuse what they’ve read with what is actually true.

You still need to know what you want to say. The translator just helps you say it.

Imagine a research assistant who has read every paper in the library. Every single one. Ask them about mitochondrial DNA and they’ll give you a thorough answer. Ask them about fourteenth-century Flemish trade routes and they’ll give you another. Ask them something that spans two obscure fields, and they might produce a response that sounds authoritative but subtly misquotes one of the sources.

The assistant is invaluable. The assistant is not infallible. Knowing the difference is your job.

A sous chef who knows every recipe ever published, every technique from every cuisine, every substitution for every ingredient. They can execute brilliantly. But they cannot decide what’s for dinner. That’s the head chef’s job. You’re the head chef. They’re waiting for your menu.

“Make something nice” will produce something. “A seared duck breast with a cherry reduction, and let’s do a fennel slaw on the side” will produce something considerably better.

Something has changed in programming. Not the fundamentals (those are eternal, like mathematics and the tendency of software to have bugs), but the workflow. The way programs get written. The conversation between human intention and working code now has a new participant, and that participant has read more code than any human who has ever lived.

AI coding assistants are tools that understand both human language and programming languages. You describe what you want; they produce code. You point at a problem; they suggest fixes. You ask what something means; they explain. They are, in effect, the translator from the introduction’s promise made real: an entity that bridges the gap between what you mean and what the computer needs to hear.

This chapter is where that promise pays off. You’ll learn how to work with AI to build things, how to read the code it produces, and how to understand the structures it uses most. Because AI will generate a lot of functions, and your programs will need to have conversations with their users, and you need to understand both.

There are two ways to lean on a colleague like this. You can order, naming what you want and trusting whatever arrives, or you can learn to read what they hand back well enough to know why it works, which is the only way you’ll ever catch the time it doesn’t.

The Conversation Loop

AI coding assistants work by pattern-matching on a scale that does not fit in your head no matter how you tilt it. They have been trained on millions of programs, billions of lines of code, and vast quantities of documentation, tutorials, forum discussions, and bug reports. When you ask them to write code, they are not “thinking” in any meaningful sense. They are recognizing patterns: “when humans ask for something like this, the code usually looks like that.”

This is simultaneously their greatest strength and their most dangerous limitation. They are extraordinarily good at producing code that looks right. They are not guaranteed to produce code that is right. The difference matters.

The workflow is a loop: prompt, read, refine.

You write a prompt describing what you want. The AI produces code. You read it. If it’s right, you use it. If it’s wrong, or incomplete, or almost-right-but-not-quite, you tell the AI what needs to change. It produces new code. You read again. The cycle continues until the result matches your intention.

This is not so different from working with a human colleague, except the colleague never takes lunch breaks and never says “let me get back to you on that.”

Being Specific vs. Being Vague

The quality of what you get back is directly proportional to the quality of what you ask for. This is true of human communication generally, but with AI it becomes almost mathematical.

Consider two requests:

Vague: “Make a todo app.”

This will produce something. It might be a web application. It might be a command-line script. It might store tasks in memory (lost when the program closes) or in a file (persistent). It might have five features or fifty. The AI will make dozens of decisions on your behalf, and any of them might be wrong.

Specific: “Write a JavaScript program that stores tasks in an array. Let me add tasks, remove them by number, and view all current tasks from the browser console. Save the task list to localStorage so they persist between sessions.”

This will produce something much closer to what you actually want, because you’ve eliminated the ambiguity. You’ve specified the language, the data structure, the features, the interface, and the persistence mechanism. The AI has far less room to guess, which means far less room to guess wrong.

Specificity is kindness. To the AI, and to yourself.

When AI Gets It Wrong

It will. Not occasionally, but often enough that you should keep one hand on the wheel. This isn’t a flaw to apologize for; it’s just how the tool works.

AI coding assistants make several categories of error:

Plausible nonsense. Code that looks syntactically correct and reads convincingly but contains a logical flaw. A sorting algorithm that sorts most things but fails on edge cases. A calculation that works for positive numbers but produces garbage for negative ones.

Confident fabrication. The AI might reference a function or library that doesn’t exist. It has seen so many APIs that it occasionally invents one that should exist but doesn’t. This is rather like a translator who, lacking the exact word, invents a plausible-sounding one and hopes nobody checks.

Outdated patterns. The AI learned from code written across many years. It might suggest approaches that were standard practice five years ago but have since been quietly retired to the back of the wardrobe, the way last decade’s trousers were.

Subtle misunderstanding. You asked for one thing; the AI understood something slightly different. The code works, but not in the way you intended. These are the hardest errors to catch because the program runs without complaint.

The remedy for all of these is the same: read the code. Understand what it does. Test it. This is why the previous chapters taught you to read variables, follow control flow, and trace execution. Those skills are not academic exercises. They are your defense against accepting code you don’t understand.

The Art of the Follow-Up

The first response is rarely the last. Working with AI is a conversation, and conversations have follow-ups.

“That works, but I also need it to show the date each task was added.”

“This part is wrong. The loop should stop when the array is empty, not when it reaches index 10.”

“Can you explain what line 7 does? I don’t understand the .map() call.”

“I changed my mind about the file format. Can you use JSON instead of plain text?”

Each follow-up narrows the gap between what you have and what you want. The AI remembers the context of the conversation and adjusts accordingly. This is where specificity continues to matter: “this part is wrong” is less useful than “line 12 crashes when the array is empty because you’re accessing index 0 without checking the length first.”

The Contract You’re Signing

Here is the single most important sentence in this chapter:

Accepting code you don’t understand is like signing a contract you haven’t read. It might be perfectly fine. It might also commit you to things you never intended. The fact that someone else wrote it does not absolve you of the consequences.

AI is a colleague, not a replacement. The colleague writes code; you are responsible for understanding it. The colleague suggests solutions; you decide whether those solutions are correct. The colleague works at the speed of a line cook in a full dining room; you ensure the work is sound.

This is not a burden. This is the job. And it is a far more interesting job than writing every line from scratch, because you get to focus on the thinking rather than the typing.

Functions: The Building Blocks

When you ask an AI to write a program of any substance, the code it returns will be full of functions. Functions are how programmers organize code into meaningful, reusable pieces, and AI assistants have absorbed this pattern so thoroughly that they produce functions almost reflexively. To read AI-generated code, you need to understand what functions are and how they work.

Teaching someone to cook can go two ways. You could recite the complete instructions each time pasta is desired: “Fill a pot with water. Place it on the stove. Turn the heat to high. Wait for bubbles. Add salt. Add pasta. Stir occasionally. Wait twelve minutes. Drain.” This is exhaustive, accurate, and tedious for everyone involved.

Or you could teach the skill of “cooking pasta.” Once acquired, a simple “cook the pasta” suffices, and all those steps execute without further elaboration. The details have been filed under a convenient name.

Functions are skills you teach the computer. Bundles of instructions invoked by name, without repeating every step on every occasion.

The computer, unlike a human apprentice, will never improvise. It will never decide the pasta might be better al dente. It will execute precisely what you taught it, with unwavering fidelity, until explicitly instructed otherwise. This is either deeply reassuring or faintly terrifying, depending entirely on whether your function actually does what you believe it does.

The Problem of Repetition

Say you’re writing a program to greet several people. You write out the greeting for each person individually. This works, in the same way that copying a recipe by hand seventeen times works. Functional, but with an air of desperation.

Now imagine the greeting must change. That change must occur in three places. Forget one, and the program becomes inconsistent. Need to greet ten people? A hundred? The approach collapses under its own weight.

This is the “Don’t Repeat Yourself” problem. Copy-pasted code is a maintenance nightmare waiting to happen, and it need not wait long.

But what if you could teach the computer a “greet someone” skill? Define the greeting steps once, and invoke them as many times as needed. Should the greeting require modification, change it in a single location, and that change propagates everywhere.

This is the power of functions: the power of naming things and trusting the name.

Anatomy of a Function

Let’s examine the structure:

Click each part to understand the anatomy of a function definition.

Right now, nothing happens. You’ve defined the skill, but you haven’t used it yet. To put it to work, you have to call it:

sayHello();

The parentheses instruct JavaScript to execute the function. Now those console.log statements run.

When AI generates code for you, you’ll see this pattern constantly: a block of function definitions at the top (the skills being taught) followed by code that calls those functions (the skills being used). Recognizing this structure is the first step to reading any program.

This might seem elaborate ceremony for two console.log statements. And so it is. But functions are not merely about saving keystrokes. They are about creating meaningful units of behaviour. “Say hello” is a concept. It has a name. Should the greeting logic grow more complex, you change it in one location. Should you wish to test it, you test the function. Functions provide conceptual organisation as much as code reuse.

Making Functions Flexible: Parameters

A sayHello function that performs identically on every invocation is useful but limited. What if you want to greet different people?

Enter parameters: blanks to be filled in when the function is called.

Watch how values flow from caller to function through parameters.

When you call greet("Alice"), the parameter name takes on the value “Alice” inside the function’s body. Call greet("Bob") and name becomes “Bob”. Same function, different data. The skill remains constant; the particulars vary.

You can hand a function more than one parameter:

Experiment with multiple parameters and see the results immediately.

Order matters here. The first argument becomes the first parameter, the second becomes the second, and on down the line. Mix them up and you get results that are technically correct and completely nonsensical, like a recipe that swaps the salt and the sugar.

This is the difference between “fetch me a cup” and “fetch me the blue cup” and “fetch me three cups.” The skill remains cup-fetching, but you supply particulars about which cups or how many. Parameters communicate those details. The function is the verb; parameters are the adjectives and adverbs.

Getting Answers Back: Return Values

Sometimes a function should not do something visible (like printing to the console). Sometimes it should compute something and deliver the answer back to its caller.

function add(a, b) {
  return a + b;
}

The return keyword declares “here is the answer; deliver this value to whoever summoned me.”

Now the function can show up wherever a value can:

const result = add(3, 5);
console.log(result); // 8

Or even:

const total = add(10, 20) + add(5, 5);
console.log(total); // 40

Functions with return values become building blocks. You can compose them, nest them, deploy them anywhere a value might appear. They are questions that provide answers, and those answers can fuel further computation.

console.log vs. Return: A Crucial Distinction

This distinction trips up many beginners, and AI-generated code sometimes muddles the two.

Compare how console.log and return behave differently with the same inputs.

Use console.log when presenting information to the user. Use return when providing a value to the rest of your program.

Most functions should return values rather than console.log them. This makes them composable: building blocks you can use in larger expressions. A function that prints is like a performer who delivers their monologue and exits. A function that returns is like an employee who completes their task and brings back the results.

If AI gives you a function that prints where it should return, that’s worth a follow-up: “Can you change this function to return the value instead of printing it?”

Building with Functions

Once functions exist, more complex behaviour emerges from their combination.

See how functions call other functions to build complexity from simple pieces.

Look at what just happened. calculateTotal calls calculateTip. Functions can call other functions, and that’s the whole trick behind how complex programs grow out of simple parts, the way elaborate buildings go up out of modest bricks.

Each function does one thing well:

  • calculateTip computes the tip amount
  • calculateTotal combines bill and tip

If the tip calculation ever needs to change, you change calculateTip, and that change ripples out to every place the function gets used. Automatically, reliably, without you chasing down a single copy.

This is modularity: dividing programs into functions, each handling one clear responsibility. Some functions just calculate and return a value; others change things in the world (saving files, printing output, sending messages). Knowing which kind you’re looking at helps you predict what a function will do.

Why Functions Matter

Reusability. Write once, use everywhere. Change once, and it changes everywhere. The medieval scribe’s nightmare becomes a non-issue.

Organisation. Functions give names to chunks of behaviour. calculateTip tells you far more than the bare formula bill * (percentage / 100) ever could. Names are understanding made visible.

Testing. You can test functions one at a time. Does calculateTip(100, 20) return 20? Try it. If it holds up on its own, you can trust it inside something bigger.

Abstraction. You can use a function without understanding its internal workings. Knowing what it does and what parameters it requires is enough. This lets you build complex systems by standing on the shoulders of simpler pieces.

Collaboration. When working with others (including AI), functions form natural boundaries. You write one function, the AI writes another, and they cooperate seamlessly provided you agree on the interface.

“Don’t Repeat Yourself” (DRY) is the principle, and functions are the primary tool. Whenever you find yourself copying code, ask: should this be a function? Not always. Sometimes repetition is clearer than abstraction. But often the answer is yes.

Naming as Self-Documentation

One of the most important kindnesses you can extend to your future self, and to anyone reading the code, is choosing good function names.

Reveal better naming alternatives and learn why they matter.

A well-named function is self-documenting. You can read calculateTipPercentage(100) and understand its purpose without examining the implementation. The name tells the story. The code merely delivers on the promise.

Some principles:

  • Use verbs for functions that perform actions (calculate, validate, send, create)
  • Be specific (calculateTip rather than calculate, sendEmail rather than send)
  • For functions returning true/false, use is or has (isValid, hasPermission)
  • Resist abbreviation (calculateTotal rather than calcTot)
  • camelCase is the JavaScript convention (calculateTip, not calculate_tip); you’ll see snake_case in Python code and some other communities, but it’s not how JavaScript is written

When reading AI-generated code, the names it chooses are often your first clue to what the code does. Good AI output uses descriptive names. If you see function f(x, y) {} in AI-generated code, ask it to use better names. You’ll thank yourself later.

The time invested in selecting a good name is repaid many times over in comprehension. Every time someone reads your code (including you, six months hence, having entirely forgotten what you were thinking), that good name saves moments of confusion. Moments accumulate. Clarity compounds.

Function Call Trace

Understanding how functions call each other helps when reading complex AI-generated programs:

Step through execution to see how the call stack grows and shrinks.

Practice: Build Your Own Functions

Practice writing function bodies for these challenges.

The Colleague Who Has Read Everything

The functions you write are the same species of thing as JavaScript’s built-in functions. When you call console.log() or Math.random() or Array.isArray(), you’re invoking functions that someone else wrote. They’re not magical incantations. They’re just functions that happened to arrive before you did.

And there are millions of functions out there: libraries of skills that programmers have created and shared with the world, shelves of them running off past where the lights still reach. Need to download a web page? There’s a function for that. Process an image? There’s a function. Train a machine learning model? There are entire libraries of functions, carefully assembled by people who devoted years to the problem.

This is where your AI colleague truly shines. Imagine working on a problem that requires sorting an array of dates. Somewhere, surely, there’s a function for this. But the exact syntax eludes you. Does sort() change the array in place or return a new one? Does it accept a comparison function? What format does it expect?

You can simply say: “I have an array of strings that are dates in the format ‘March 15, 2024’. I want to sort them chronologically.”

The AI knows about sort(). It knows how to write a comparison function. It knows about new Date() for parsing date strings. It assembles these skills, producing code you can use, modify, or study.

This is like having a remarkably well-read colleague who has memorised the entire library yet never grows impatient at basic questions. They’ve read all the documentation so you don’t have to. Though, like any colleague, they’re occasionally wrong about details, so verification remains advisable.

You won’t merely teach the computer new skills. You’ll employ skills that the entire programming community has collectively taught it. You’ll stand on the shoulders of giants, invoking their expertise by name, as though they were colleagues down the hall, and your AI assistant knows all their phone numbers.

Your Program Has Conversations

You now have the tools to write functions and the workflow to build them with AI. But the programs written so far have a limitation that is perhaps best illustrated by analogy.

If the programs written thus far were people at a party, they would be the ones who walk up, deliver a prepared speech, and depart immediately without listening to a word in response. Efficient, perhaps, but hardly engaging and unlikely to be invited back.

Picture a one-sided phone call where someone talks for ten minutes straight, hangs up, and considers the conversation complete. That’s what our programs have been doing: running their instructions, producing output, and exiting without ever checking whether anyone cared.

Imagine a waiter who takes one table’s order, walks to the kitchen, and goes home. No delivery. No checking if the food is correct. No “can I get you anything else?” Just: task received, task executed, goodbye.

Real programs have conversations. They listen. They respond. They remember what was said five minutes ago and act accordingly. This is where programming becomes interactive, where you move from writing scripts that do a thing to building programs that do things with you.

Output: Talking to the World

Output has been present since the beginning. Every console.log("Hello, World!") is the program speaking. The console.log function is wonderfully accommodating: give it text, numbers, or multiple things separated by commas, and it displays them all, helpfully adding spaces between them. After all, nobody wants Theansweris:42 mashed together like a license plate designed by a bureaucrat with no regard for human eyeballs.

Making Output Readable

There are more elegant ways to format output than hoping the spaces fall where they ought. Template literals (note the backticks in place of quotation marks) let you embed variables directly into text. Where you write ${name}, JavaScript substitutes whatever name contains.

Explore different output formatting styles and see how they compare.

Why Output Matters

Output serves several purposes:

  • Showing results to users (the obvious case)
  • Debugging: printing values to understand what has gone wrong
  • Logging: recording what happened for later analysis
  • Communicating progress: “Processing file 47 of 100…”

The debugging use case is particularly important. Professional programmers spend an embarrassing amount of time adding console.log statements to figure out why something isn’t working, removing them once the problem is found, then adding them back a week later when a new problem appears.

Somewhere out there is a programmer who has never added a console.log statement that simply says “HERE” or “WHY” or “AAAAAAHHH” to check whether a particular line of code is even executing. That programmer is not you, it’s not me, and it’s probably not anyone who has ever written working software.

Input: Listening to the World

Output is talking. Input is listening. Programs that only produce output without accepting input are severely limited, in the same way that someone who only talks and never listens is severely limited and eventually very alone.

The simplest way to receive input in JavaScript is the prompt() function. Run it, and something notable occurs. The program stops. It waits, with the patience of a dog at a closed door. It is actually listening for you to type something into the dialog box that appears. Type your response, press Enter, and the program springs back to life, now knowing something it did not know before.

Input Is Always Text

Here is a quirk that trips up every beginner exactly once: whatever the user types arrives in the program as text, even if it looks like a number. If mathematics is required, conversion is necessary using functions like Number() to transform text into an actual number you can add, subtract, and do all the things you’d expect to do with numbers rather than characters that merely happen to look numeric.

See how input arrives as text and must be converted for mathematical operations.

Why does input give you text instead of automatically figuring out the type? Because computers are magnificently literal creatures. “007” might be a number to you, but it’s also a secret agent, a file extension, or part of a phone number. JavaScript refuses to guess. If you want a number, you have to say so.

State: The Program’s Memory

In a conversation, you don’t exchange isolated statements. Context builds up. If someone asks your name and you tell them, you expect them to remember it. If they ask again five minutes later, you’d be justifiably annoyed.

Programs, too, need to remember things. What a program remembers is called its state. A variable like score is state. Its current value depends on everything that happened before. It is the program’s memory of what has occurred.

Interact with state and watch how it accumulates over time.

State in Interactive Programs

Combine state with input, and programs begin to feel like genuine conversations. The program remembers your name and doesn’t need to ask again. This seems trivial in a five-line program, but imagine a game that remembers your inventory, a shopping cart that remembers what you’ve added, or a chat program that knows who you are. State is what makes such things possible.

A counter that maintains state across interactions.

The Main Loop: Keeping the Conversation Going

Most programs written thus far have a clear lifecycle: they start, do something, and finish, in and out like a postman who never rings twice. But many programs you use daily do not work that way. A web browser does not open, show one page, and quit. Games do not render a single frame and exit. Chat applications do not send one message and close.

These are continuous programs. They run until explicitly told to stop. And they are simply programs with a loop that keeps going.

The pattern is always the same:

  1. Wait for something to happen (user input, a click, a message)
  2. Process what happened
  3. Produce output (show results, update the display)
  4. Loop back and wait for the next thing

This is a main loop, and it’s the beating heart of almost every interactive program.

Step through the main loop pattern that powers all interactive programs.

Here’s a simple example that ties everything together: a program that has an actual conversation with you.

function getGreeting(name, timeOfDay) {
  if (timeOfDay === "morning") {
    return `Good morning, ${name}!`;
  } else if (timeOfDay === "evening") {
    return `Good evening, ${name}!`;
  } else {
    return `Hello, ${name}!`;
  }
}

const name = prompt("What's your name?");
let count = 0;

while (true) {
  const command = prompt("What would you like to do? (greet/count/quit)");

  if (command === "greet") {
    const time = prompt("Is it morning or evening?");
    console.log(getGreeting(name, time));
  } else if (command === "count") {
    count = count + 1;
    console.log(`You've done ${count} things so far, ${name}.`);
  } else if (command === "quit") {
    console.log(`Goodbye, ${name}! You did ${count} things.`);
    break;
  } else {
    console.log("I don't understand that command.");
  }
}

This program has everything. A function (getGreeting) with parameters and a return value. Input from the user. Output via console.log and template literals. State (name, count) that persists across the loop. A main loop that keeps the conversation going until the user says “quit.”

It’s not complicated. It’s not impressive by any professional standard. But it is a genuine interactive program, and everything from text editors to video games to operating systems is built from these same pieces, scaled up.

If you’re thinking, “Wait, that means a massive multiplayer game is fundamentally just a really complicated version of this input-process-output loop,” you’re right. More state, faster loops, fancier output. But the pattern is identical. Input, process, output, repeat. Everything else is polish.

Bringing It All Together

You now have three interlocking skills:

Working with AI. You know how to prompt, read, and refine. You know that specificity produces better results, that AI makes mistakes, and that understanding the code is your responsibility.

Reading functions. You can identify parameters, return values, and function calls. You know why functions exist (reusability, organisation, testability, abstraction) and how they compose into larger programs.

Building interactive programs. You understand output, input, state, and the main loop. Your programs can have conversations with their users instead of delivering monologues.

These pieces reinforce each other. When you ask AI to build something interactive, it will produce functions, use console.log and prompt(), manage state in loops. You can now read what it gives you, understand why it’s structured the way it is, and refine it toward what you actually want.

This is the workflow of modern programming. Not writing every line from scratch. Not blindly accepting whatever an AI produces. Something in between: a collaboration between human judgment and machine fluency, where the human decides what and the machine helps with how.


You can now read code, work with AI to generate it, understand functions, and build interactive programs. But your programs so far work with simple values: a number here, a string there. Real problems involve collections of things, structured information, data with relationships and hierarchies. A to-do list is not one task but many. A contact is not one string but a name, an email, a phone number, bound together. That’s where we’re headed next.