Courses Learn Blog Contact
Get Started →
Lesson 16

Day 16: AI-Assisted Development

Prompting with real context, reviewing generated code as a hostile reader, catching hallucinated methods, and the one rule: never submit code you cannot explain.

Webbo3 Level 2: JavaScript Development 12 min read Free

Webbo3 Level 2 · JavaScript Development · Day 16 of 20

AI-Assisted Development

Using AI the way an engineer does: as a fast assistant you supervise, never as an authority you obey.

By the end of today you can

  • Prompt for code with enough context to get something useful
  • Review AI output the way you would review a stranger's pull request
  • Spot a hallucinated method, a subtle logic error and an insecure pattern
  • Use AI for the things it is genuinely good at, and stop using it for the rest
  • Live by one rule: never submit code you cannot explain

Nobody is going to tell you not to use AI. You will use it, your colleagues use it, and pretending otherwise would make this course useless the day you leave it.

What separates a developer from somebody typing prompts is not whether they use it. It is whether they can tell when it is wrong.

1. What It Is Actually Good At

Definition

An AI coding assistant is a model that has read an enormous amount of published code and writing, and predicts a likely answer to what you ask. Likely is the key word: it produces what usually follows a question like yours, not what is true for your project.

Real-Life Example

A very well-read new intern. They can rewrite your letter beautifully, explain any term, and draft ten versions in a minute. They have never met your customers, have not seen your files, and will answer confidently even when they are guessing. You would use them every day, and you would check everything they hand you.

Types: Good At and Bad At

Genuinely good atQuietly bad at
Explaining an error message in plain EnglishKnowing whether your code is actually broken: it cannot see your screen
Generating practice material and test dataJudging what matters in your product: it does not know your users
Boilerplate you have written a hundred timesAnything depending on your codebase that you did not paste in
Reviewing your code as a hostile readerWriting code for a library version it half-remembers
Expanding a list of edge cases you startedProducing the first list, which is where your judgement lives
Turning a rough README into a clear oneDeciding your architecture, which it will do confidently and badly
The Pattern Across That Whole Table

AI is strong where the work is transformation and weak where the work is judgement. Rewriting, explaining, expanding, translating: good. Deciding what matters, whether this is right, what to build: yours.

Picture This

Picture the well-read intern: fast, tireless, articulate, and never once in your office. Use them for drafts; keep the decisions.

2. Prompting That Gets Something Usable

Definition

A prompt is the instruction you give the model. Everything it does not say, the model fills in with a guess. A good prompt leaves nothing important to guess.

Real-Life Example

Telling a tailor "make me trousers" gets you trousers for somebody. Giving them your measurements, the fabric, the occasion and the date gets you your trousers.

What this code doesWe want to see a prompt that wastes your time, so the difference is obvious. It gives no data shape, no rules and no idea of what "done" means.
weak-prompt.txt
1
write me a function to filter students
NOTE   
You get a plausible function for a made-up data shape that does not match yours.
What this code doesWe want a prompt that earns its keep: the real data, the rules, what done means, and a question that makes the model argue against its own answer.
strong-prompt.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
I am writing vanilla JavaScript, no frameworks, ES modules, for a browser.

Here is my actual data shape:
{ id: 12, name: "Chidinma Okafor", score: 88, city: "Lagos", paid: true }

I have an array of about 200 of these in `state.students`.

Write a function `visibleStudents(state)` that returns the students matching
BOTH `state.search` (case-insensitive, matches any part of the name) and
`state.filter` (one of "all", "paid", "unpaid").

Constraints:
- Do not mutate `state.students`
- Return an empty array when nothing matches, never null
- No libraries

Then list three edge cases your function does NOT handle.

Types: The Four Parts

  • The real data shape. Most bad output is AI guessing at a shape you never gave it.
  • The constraints. Vanilla, no libraries, do not mutate. Otherwise it reaches for lodash and React.
  • What "done" means. Empty array, not null. Say it, or you get null and a crash three files away.
  • Ask what it did not handle. The highest-value sentence in the prompt: it forces the model to argue against its own output.

Whatever comes back, you test it against your own cases before it goes anywhere near your project. Here is a correct answer to that prompt, checked with the Day 14 check() function.

What this code doesWe want to test an answer to the strong prompt against every rule the prompt set: case, empty search, the filter, an empty array for no match, and no mutation.
app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
function check(description, actual, expected) {
  const pass = JSON.stringify(actual) === JSON.stringify(expected);
  if (pass) {
    console.log(`PASS  ${description}`);
  } else {
    console.log(`FAIL  ${description}`);
    console.log(`      expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`);
  }
}

// The function the prompt asked for
function visibleStudents(state) {
  const search = state.search.trim().toLowerCase();
  return state.students.filter((student) => {
    const nameMatches = student.name.toLowerCase().includes(search);
    if (state.filter === 'paid') return nameMatches && student.paid;
    if (state.filter === 'unpaid') return nameMatches && !student.paid;
    return nameMatches;
  });
}

const students = [
  { id: 12, name: 'Chidinma Okafor', paid: true },
  { id: 13, name: 'Musa Bello', paid: false },
];

// Your cases, not the model's
const names = (list) => list.map((student) => student.name);
check('lower-case search', names(visibleStudents({ students, search: 'chid', filter: 'all' })),
  ['Chidinma Okafor']);
check('empty search shows all', visibleStudents({ students, search: '', filter: 'all' }).length, 2);
check('unpaid filter', names(visibleStudents({ students, search: '', filter: 'unpaid' })),
  ['Musa Bello']);
check('no match is an empty array', visibleStudents({ students, search: 'zzz', filter: 'all' }), []);
check('original untouched', students.length, 2);
OUTPUT   TERMINAL
➜ webbo3 node app.js
PASS  lower-case search
PASS  empty search shows all
PASS  unpaid filter
PASS  no match is an empty array
PASS  original untouched
Try This

Write the strong prompt for one feature of your Day 15 project with your real data shape. Then write your own check() calls before you read the answer, and run them against it.

Picture This

Picture the tailor with your measurements in hand. Everything you leave out, they guess.

3. Reviewing What Comes Back

Definition

Code review is reading somebody else's code to find what is wrong before it ships. Read AI output the way you would read a pull request from somebody you do not know.

Real-Life Example

A proof-reader does not ask "does this look like a letter?" They read every line asking "what is wrong here?" The first question always says yes.

Here is generated code of exactly the kind you will be handed. Four things are wrong with it. Read it and find them before you open the answer.

What this code doesWe want a realistic piece of generated code to review. Read it line by line and write down four problems before going on.
app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
// "Renders the student list with a search filter"
function renderStudents(students, search) {
  var html = '';
  for (var i = 0; i < students.length; i++) {
    if (students[i].name.includes(search)) {
      html += '<li onclick="selectStudent(' + students[i].id + ')">' + students[i].name + '</li>';
    }
  }
  document.getElementById('list').innerHTML = html;

  var avg = students.reduce((a, b) => a + b.score) / students.length;
  document.getElementById('avg').innerHTML = 'Average: ' + avg;
}
NOTE   
Do not run it yet. Reading comes first.
Show me the four problems

1. The average is wrong in two ways. reduce with no starting value uses the first student object as the running total, so it adds an object to a number and produces NaN. And it averages all students, not the ones the search shows. 2. User text goes into innerHTML. A student named with an img tag and an onerror attribute runs code (Day 4, and tomorrow). 3. onclick written into a string. It depends on a global function, breaks under modules, and would be a delegated listener in anything written this month. 4. The search is case-sensitive, so typing "chidinma" finds nothing. And var in 2026 is a warning sign on its own.

None of those stop the page loading. That is why "it ran" is not a review. Now prove each one, starting with the two a user would see.

What this code doesWe want to run the generated code with three real students and a search typed in lower case, the way a user would.
index.html
1
2
<ul id="list"></ul>
<p id="avg"></p>
app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// "Renders the student list with a search filter"
function renderStudents(students, search) {
  var html = '';
  for (var i = 0; i < students.length; i++) {
    if (students[i].name.includes(search)) {
      html += '<li onclick="selectStudent(' + students[i].id + ')">' + students[i].name + '</li>';
    }
  }
  document.getElementById('list').innerHTML = html;

  var avg = students.reduce((a, b) => a + b.score) / students.length;
  document.getElementById('avg').innerHTML = 'Average: ' + avg;
}

const students = [
  { id: 1, name: 'Chidinma Okafor', score: 88 },
  { id: 2, name: 'Musa Bello', score: 54 },
  { id: 3, name: 'Zainab Yusuf', score: 71 },
];

// The user types a name in lower case
renderStudents(students, 'chidinma');
BROWSERindex.html · after searching for "chidinma"

An empty list and "Average: NaN". Now problem 1 in isolation, because it is the one people do not believe. Without a starting value, reduce uses the first item, a whole object, as the running total.

What this code doesWe want to see exactly what reduce produces with and without its starting value, on two students.
app.js
1
2
3
4
5
6
7
const students = [{ name: 'Chidinma', score: 88 }, { name: 'Musa', score: 54 }];

// No starting value: the first object becomes the running total
console.log('no starting value:', students.reduce((a, b) => a + b.score));

// Starting value 0: the running total is a number from the start
console.log('with a starting value:', students.reduce((a, b) => a + b.score, 0) / students.length);
OUTPUT   TERMINAL
➜ webbo3 node app.js
no starting value: [object Object]54
with a starting value: 71

The object was turned into the text "[object Object]" and 54 was stuck on the end. Divide that by 2 and you get NaN. Now problem 2: a student record whose name is HTML.

What this code doesWe want to prove that user text in innerHTML runs. One "student" has a name that is an image tag with an onerror attribute, which runs when the image fails to load.
index.html
1
2
<ul id="list"></ul>
<p id="avg"></p>
app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// "Renders the student list with a search filter"
function renderStudents(students, search) {
  var html = '';
  for (var i = 0; i < students.length; i++) {
    if (students[i].name.includes(search)) {
      html += '<li onclick="selectStudent(' + students[i].id + ')">' + students[i].name + '</li>';
    }
  }
  document.getElementById('list').innerHTML = html;

  var avg = students.reduce((a, b) => a + b.score) / students.length;
  document.getElementById('avg').innerHTML = 'Average: ' + avg;
}

const students = [
  { id: 1, name: 'Chidinma Okafor', score: 88 },
  { id: 2, name: '<img src=x onerror="console.log(\'this code ran from a student name\')">', score: 0 },
];

renderStudents(students, '');
BROWSERindex.html · after rendering a hostile name
CONSOLE   BROWSER DEVTOOLS
Failed to load resource: net::ERR_CONNECTION_REFUSED
this code ran from a student name

The browser treated the name as HTML and ran the attacker's code. Tomorrow shows what that code could do instead of logging. Here is the same function reviewed and fixed.

What this code doesWe want the fixed version: lower-case search, names inserted as text, one delegated listener, and an average of the students actually shown.
index.html
1
2
<ul id="list"></ul>
<p id="avg"></p>
app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
function renderStudents(students, search) {
  const term = search.toLowerCase();
  const shown = students.filter((student) => student.name.toLowerCase().includes(term));

  // Build each item with textContent, so a name is only ever text
  const list = document.getElementById('list');
  list.replaceChildren();
  for (const student of shown) {
    const item = document.createElement('li');
    item.textContent = student.name;
    item.dataset.id = student.id;
    list.append(item);
  }

  // Average of the students on screen, starting from 0
  if (shown.length === 0) {
    document.getElementById('avg').textContent = 'No students match.';
    return;
  }
  const total = shown.reduce((sum, student) => sum + student.score, 0);
  document.getElementById('avg').textContent = `Average: ${Math.round(total / shown.length)}`;
}

// One listener on the list, instead of onclick strings
document.getElementById('list').addEventListener('click', (event) => {
  console.log('selected student', event.target.dataset.id);
});

const students = [
  { id: 1, name: 'Chidinma Okafor', score: 88 },
  { id: 2, name: '<img src=x onerror="console.log(\'this code ran\')">', score: 0 },
  { id: 3, name: 'Chinedu Obi', score: 71 },
];

renderStudents(students, 'chi');
BROWSERindex.html · after searching "chi" and clicking the first name
CONSOLE   BROWSER DEVTOOLS
selected student 1

Both matching names are found despite the lower case, the average is of those two, the hostile name was never matched (and would show as plain text if it were), and the click reaches the one listener. replaceChildren() with nothing inside empties the list before refilling it.

Picture This

Picture the proof-reader with a red pen, reading every line asking what is wrong, never whether it looks right.

4. Hallucinated APIs

Definition

A hallucination is output the model made up that looks exactly like the real thing: a method name that sounds right and does not exist. It is the most dangerous output because it does not look wrong.

Real-Life Example

Somebody giving you confident directions to a street that sounds like a Lagos street and is not one. You only find out when you get there.

typeof tells you what kind of value something is. For a real method it says 'function'; for a made-up one it says 'undefined'. toSorted() is a real, newer array method: it returns a sorted copy and leaves the original alone, unlike sort().

What this code doesWe want to check four array methods an AI might hand us, and find out which are real before building on any of them.
app.js
1
2
3
4
5
6
7
8
9
10
11
12
const items = [3, 1, 2];

// Two that sound real, and are not
console.log('sortBy:  ', typeof items.sortBy);
console.log('remove:  ', typeof items.remove);

// Two that are real
console.log('toSorted:', typeof items.toSorted);
console.log('includes:', typeof items.includes);

// toSorted gives a sorted copy; the original keeps its order
console.log(items.toSorted((a, b) => a - b), 'original untouched:', items);
OUTPUT   TERMINAL
➜ webbo3 node app.js
sortBy:   undefined
remove:   undefined
toSorted: function
includes: function
[ 1, 2, 3 ] original untouched: [ 3, 1, 2 ]
What this code doesWe want to see what calling a hallucinated method does at runtime, so you recognise it when it happens in your project.
app.js
1
2
3
4
5
const items = [3, 1, 2];

// What happens when you build on a made-up method
const sorted = items.sortBy((a, b) => a - b);
console.log(sorted);
OUTPUT   TERMINAL
➜ webbo3 node app.js
TypeError: items.sortBy is not a function
The Ten-Second Check That Saves an Hour

When AI hands you a method you have not used before, search MDN for it by name before you build on it. If MDN does not have it, it either does not exist or is not something to rely on. This habit costs ten seconds and is the highest-return thing in this lesson.

Picture This

Picture the confident stranger's directions to a street that does not exist. Check the map before you set off.

5. The Security Failures AI Hands You Most Often

What it writesWhy it is wrongDay
el.innerHTML = userInputXSS. The user can inject markup that runs4, 17
An API key inline in the JavaScriptPublic the moment you deploy8, 17
eval() on anythingRuns arbitrary code. Almost never justified17
Validation only in the browserTrivially bypassed. The server must check13
Storing a token in localStorageReadable by any injected script11, 17

These recur because the model learned from a decade of tutorials that did the same things. It is not being careless: it is reproducing the average of what people have published, and the average is not secure.

6. Use It as a Reviewer, Which Is Where It Shines

The highest-value use of AI in this whole course is not generation. It is asking it to attack work you already did.

What this code doesWe want the model to attack our own code in five specific ways, and to be allowed to say it found nothing.
review-prompt.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
You are a senior JavaScript engineer reviewing a junior's code. Be strict and
specific. Do not compliment me.

Here is my code:
[paste it]

Answer:
1. Any bug that produces a wrong result rather than an error. Show me the input
   that breaks it.
2. Anywhere user input reaches the DOM or a URL unsafely.
3. Anything that will break with 5,000 records instead of 50.
4. Three names that do not say what they hold.
5. The single change that would most improve this file, and why.

If you find nothing in a category, say so rather than inventing something.

That last line matters more than it looks. Without it you get five findings whether or not there are five, because the shape of your question implied there should be.

7. The Rule

Never Submit Code You Cannot Explain

Not "did not write". Cannot explain. Copying from Stack Overflow was always fine if you understood what you copied, and pasting from an AI is fine on exactly the same terms. If you cannot say what a line does and why it is there, it does not go in your project: the moment it breaks you cannot fix it, and the moment you are asked about it you cannot answer.

The Three-Question Test, Before Any Generated Code Goes In

  1. What does this line do? If you cannot say, ask for an explanation rather than pasting it.
  2. What happens with empty, zero, or 5,000 items? Try it. Do not reason about it.
  3. Could a user put something hostile through this? If user text reaches innerHTML or a URL, the answer is yes.

Do This Now

  1. Take the broken code from Section 3, fix all four problems yourself, and write one sentence per fix explaining what would have gone wrong.
  2. Ask an AI to generate a small feature for your Day 15 project with your real data shape pasted in. Before you run it, write down what you think is wrong with it. Then run it and see if you were right.
  3. Run the reviewer prompt on a file you wrote this month. Fix what is genuinely a problem, and write down anything it flagged that was actually fine. That second list is the point of the exercise.
  4. Find one hallucination. Ask for "five lesser-known array methods with examples" and check every one on MDN. Note which were real.

Checkpoint

Q1. What is AI genuinely good at, in one word?

Transformation: explaining, rewriting, expanding, translating, generating practice material. It is weak wherever the task is judgement: what matters, what to build, whether this is right.

Q2. Why include "list three edge cases you did NOT handle" in a prompt?

It forces the model to argue against its own output, which surfaces the assumptions it quietly made. It is the cheapest quality improvement you can make to any code prompt.

Q3. You are given array.sortBy(). What do you do?

Search MDN for it before building on it. It does not exist: typeof says undefined, and calling it throws "is not a function". Ten seconds now against an hour of debugging.

Q4. What does "never submit code you cannot explain" actually forbid?

Not using AI, and not copying. It forbids shipping lines you could not defend if asked, whoever or whatever produced them.

Tick Before You Move On

  • ☐ I found all four problems in the generated code before opening the answer
  • ☐ I proved the reduce bug myself rather than taking it on trust
  • ☐ I ran the reviewer prompt and noted where it was wrong as well as right
  • ☐ I caught at least one hallucinated method against MDN
  • ☐ Every line in my Day 15 project is one I can explain

Quick Recap

AI is strong at transformation and weak at judgement. Paste your real data shape and your constraints, or you get plausible fiction. Test generated code with your own cases, not the model's. Review generated code as a hostile reviewer: innerHTML, keys, eval, reduce. Check unfamiliar methods on MDN, and never submit code you cannot explain.

Final Scenario

In your first week at a job, a teammate pastes an AI-written search function into the shared project. It looks tidy and it runs.

You ask three questions: what happens with a lower-case search, what happens with no students, and where does the name go into the page? Within five minutes you have found a case-sensitive match, a NaN average and an innerHTML with user text, each with the input that proves it.

Nobody on that team will ask whether you use AI. They will remember that you can tell when it is wrong.

Tomorrow, Day 17: the security mistakes a junior developer is actually expected not to make.

Learn this with a real instructor

Free lessons take you a long way on your own. The full programmes add live classes, assignments, Skill Points and a certificate — built for African students.