Skip to main content
A detective examining code with magnifying glass, following trails of data flow and control flow through a complex program maze

CS 3100: Program Design and Implementation II

Lecture 14: Program Understanding & Debugging

©2026 Jonathan Bell & Ellen Spertus, CC-BY-SA

Poll: Why don't you use Oakland office hours or appointments?

A. I didn't know you held them.

B. I'm not available at those times.

C. I prefer getting help from AI.

D. I prefer getting help from friends.

E. I prefer getting help through Pawtograder.

F. I don't think it would be helpful.

G. I don't know

H. other

Poll Everywhere QR Code or Logo

Text espertus to 22333 if the
URL isn't working for you.

https://pollev.com/espertus

Answers are anonymous but count toward your participation grade.

Learning Objectives

After this lecture, you will be able to:

  1. Utilize control flow and data flow analysis to understand a program
  2. Utilize diagrams (call graphs, sequence diagrams) to visualize program behavior
  3. Apply the scientific method to debugging
  4. Utilize a debugger to step through a program and inspect state
  5. Utilize an AI programming agent to assist with debugging

Coming Soon: Much Bigger Codebases

A3 (this week!):

  • Starter code with 10+ interfaces: RecipeCollection, Cookbook, UserLibrary...
  • Inheritance hierarchies you didn't design
  • Jackson annotations you've never seen

Group project (March):

  • Teammates' code you need to understand and extend

"How do I even start understanding this codebase?"

Poll: How many hours per assignment do you spend debugging?

Poll Everywhere QR Code or Logo

Text espertus to 22333 if the
URL isn't working for you.

https://pollev.com/espertus

Poll: When You Encounter a Bug, What's Your First Instinct?

A. Add print statements everywhere

B. Start changing code to see what happens

C. Read the code carefully to understand it

D. Ask AI to fix it

E. Panic and consider dropping the class

Poll Everywhere QR Code or Logo

Text espertus to 22333 if the
URL isn't working for you.

https://pollev.com/espertus

Building Mental Models

Understanding code = building a mental model.

  1. Form a hypothesis: "I think UserLibrary stores recipes directly"
  2. Test it: Look at the interface—getCollections(), not getRecipes()
  3. Refine: "It stores collections that contain recipes"

Same process for debugging: hypothesis → test → refine

Two Lenses for Understanding Code

Split visualization showing control flow and data flow analysis of the same Java code snippet

Control Flow: Tracing Execution Paths

// From A3: CookbookImpl.addRecipe()
@Override
public Cookbook addRecipe(Recipe recipe) {
if (containsRecipe(recipe.getId())) { // Branch point
throw new IllegalArgumentException(
"Recipe with ID '" + recipe.getId() + "' already exists");
}
List<Recipe> newRecipes = new ArrayList<>(recipes);
newRecipes.add(recipe);
return new CookbookImpl(id, title, newRecipes, author, isbn, publisher, publicationYear);
}

Questions to ask: Which paths are possible? What happens if the recipe ID already exists? What happens on the "happy path"?

Data Flow: Tracking How Values Change

// From A3: CookbookImpl constructor (used by Jackson for JSON deserialization)
@JsonCreator
private CookbookImpl(
@JsonProperty("id") @Nullable String id,
@JsonProperty("title") String title,
@JsonProperty("recipes") List<Recipe> recipes,
@JsonProperty("author") @Nullable String author,
@JsonProperty("isbn") @Nullable String isbn,
@JsonProperty("publisher") @Nullable String publisher,
@JsonProperty("publicationYear") @Nullable Integer publicationYear) {
this.id = (id != null) ? id : UUID.randomUUID().toString(); // Data: id assigned
this.title = title; // Data: title assigned
this.recipes = List.copyOf(recipes); // Data: defensive copy
this.author = isBlank(author) ? null : author; // Data: normalization!
this.isbn = isBlank(isbn) ? null : isbn;
this.publisher = isBlank(publisher) ? null : publisher;
this.publicationYear = publicationYear;
}

Questions to ask: What happens if author = " " (whitespace)? Trace how the data flows through isBlank() to this.author.

Combining Control and Data Flow Analysis

// From A3: UserLibraryImpl.findRecipesByTitle() - YOU must implement this!
public List<Recipe> findRecipesByTitle(String title) {
// Control: iterate through all collections
// Data: title parameter flows into comparison
return collections.stream() // Data: collections read
.flatMap(c -> c.getRecipes().stream()) // Control: flatten nested lists
.filter(r -> r.getTitle().equalsIgnoreCase(title)) // Data: title compared
.toList(); // Control: collect results

// What could go wrong?
// - If collections is modified during iteration? → Immutability prevents this!
// - If title is null? → NullPointerException (should validate at method entry)
// - If no matches? → Returns empty list (correct behavior)
}

Real bugs often involve both control and data flow issues interacting.

Interprocedural Analysis: Following Calls Across Methods

When examining control flow, you may need to trace across method boundaries:

IDE Tools:

  • Find All References: Where is this method called?
  • Go to Definition: What does this method do?
  • Call Hierarchy: Who calls whom?

Visualization:

  • Call graphs: Static view of what CAN call what
  • Sequence diagrams: Dynamic view of what DID happen

These tools become essential as codebases grow beyond what you can hold in your head.

Don't Forget: Dynamic Dispatch Affects Control Flow

From A3: When you see collection.addRecipe(recipe), which addRecipe runs?

⚠️ Recall from Quiz 1: The runtime type determines which method executes, not the declared type!

Diagrams Help You See the Big Picture

Complex systems require visualization. Three key diagram types:

Diagram TypeShowsUse When
Call GraphWhich methods call whichUnderstanding static structure
Sequence DiagramOrder of calls over timeUnderstanding dynamic behavior
Class DiagramRelationships between typesUnderstanding data model

Call Graphs: Static View of Method Relationships

From A3: JSON serialization involves many method calls under the hood. Understanding this call graph helps debug serialization issues.

Sequence Diagrams: Dynamic View Over Time

Class Diagrams: Understanding the Data Model

Practical Diagram Workflow

When debugging, sketching quick ASCII art (or gasp paper and pencil) is fastest:

Test -> JsonRecipeCollectionRepository.save(cookbook)
Repo -> ObjectMapper.writeValueAsString()
-> adds "type": "cookbook"
-> serializes Recipe[]
Repo -> Files.writeString() ⚠️ IO Exception?
  • Faster than looking up syntax
  • Forces you to understand flow
  • Easy to annotate with hypotheses

Using AI to Generate Diagrams: Concrete Examples

Example 1: Visualizing classes relating to CookbookImpl

Prompt: "Create a class diagram with CookbookImpl and all classes it has relationships with."

Example 2: Understanding CookbookImpl construction flow

Prompt: "Create a sequence diagram showing how CookbookImpl is created from JSON
using Jackson."

Example 3: Tracing data flow through UserLibraryImpl

Prompt: "Show the data flow when UserLibraryImpl.findRecipesByTitle()
searches across collections."

Great use of AI: Quickly get the benefits of visualization.

Tip: Use mermaid.live for live editing

When to Use the IDE, Not AI

❌ Don't use AI to:

  • find a method's javadoc
  • go to an implementation
  • find where a method is called

Use the IDE!

Animated demonstration of IDE navigation features

When to Use AI for Diagrams

✅ Use AI for:

Complex call chains: Multiple methods across classes

  • "Show how save() calls Jackson which calls getters"

Understanding polymorphism:

  • "Diagram the RecipeCollection hierarchy and show which addRecipe runs for each type"

Stream pipelines:

  • "Visualize the data transformations in findRecipesByTitle"

Documenting for others:

  • AI generates shareable Mermaid diagrams
  • You validate them, others can trust them

AI Generated This Diagram. Now What?

Prompt used:

"I'm working on implementing JsonRecipeCollectionRepository. Create a mermaid sequence diagram showing the save() method, from here to disk."

AI for Diagrams: What Works vs What Doesn't

❌ Vague: "Diagram the repository pattern"

  • No diagram type (sequence? class?)
  • No format → ASCII art 🤮
  • Generic, not your code

✅ Specific: "Create a mermaid flowchart showing data flow through CookbookImpl constructor"

  • Diagram type ✓ Format ✓ Your class ✓

❌ Wrong tool: "What does addRecipe do?"

  • AI reads code, explains it
  • You could just read it yourself
  • Slower than Ctrl+Click

✅ Right tool: Press F12 on addRecipe

  • Instant definition
  • See params and return type
  • Can step through in debugger

Debugging Is Critical Thinking Applied to Code

Brain capacity visualization showing writing code uses 50% capacity while debugging the same code requires 100%+

"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian Kernighan

The Scientific Method Applied to Debugging

  1. Observe: Notice unexpected behavior or test failure
  2. Hypothesize: Form a theory about the cause
  3. Predict: What evidence would support or refute this?
  4. Test: Gather evidence (debugger, logging, tests)
  5. Analyze: Does the evidence support the hypothesis?
  6. Iterate: Refine hypothesis or implement fix

Quick Background: What Is HTML?

HTML uses tags (text in angle brackets) to format web pages:

HTML (what you write):

<b>Hello</b> world
<p class="intro">Welcome!</p>

Rendered (what users see):

Hello world

Welcome!

The task: Write a function that extracts just the text, removing all tags.

InputExpected Output
<b>Hello</b>Hello
Click <a href="url">here</a>Click here

Example: Debugging HTML Markup Removal

/**
* Removes tags from s. For example, "Hello <b>world</b>" becomes "Hello world".
*/
public static String removeHtmlMarkup(String s) {
boolean tag = false, quote = false;
StringBuilder out = new StringBuilder();

for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == '<' && !quote) {
tag = true;
} else if (c == '>' && !quote) {
tag = false;
} else if (c == '"' || c == '\'' && tag) { // Handle quotes in tags
quote = !quote;
} else if (!tag) {
out.append(c);
}
}
return out.toString();
}

Adapted from Andreas Zeller's "Introduction to Debugging" (CC-BY-NC-SA).

Step 1: Observe — Build an Observation Table

InputExpectedActualResult
<b>foo</b>foofoo
<b>"foo"</b>"foo"foo
"<b>foo</b>""foo"<b>foo</b>
<b id="bar">foo</b>foofoo

Both failures involve quotes. Pattern emerging!

Step 2: Hypothesize — Form Theories

Based on our observations, we form two hypotheses:

HypothesisBased On
H1: Double quotes are stripped from output<b>"foo"</b>foo (quotes missing)
H2: Quotes outside tags break tag parsing"<b>foo</b>"<b>foo</b> (tags kept)

Let's focus on H1 first—it's simpler. We'll refine it:

H1 (refined): Double quotes are stripped from input, even without tags.

Step 3 & 4: Predict and Test

Prediction: If H1 is correct, even "foo" (no tags) should lose its quotes.

@Test
public void testPlainQuotes() {
assertEquals("\"foo\"", removeHtmlMarkup("\"foo\""));
}
// Result: FAILS! Output is "foo" - quotes stripped
InputExpectedActualResult
"foo""foo"foo

H1 CONFIRMED: Double quotes are stripped even without any HTML tags.

Step 5: Analyze — Narrow Down the Cause

Where is quote-stripping happening? The only quote-handling code is:

else if (c == '"' || c == '\'' && tag) {
quote = !quote;
}

This should only trigger when tag is true. But for input "foo", there are no tags, so tag should always be false...

New hypothesis H3: The error is due to tag being set incorrectly.

// Let's add an assertion to test H3:
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
assert !tag; // For "foo", tag should NEVER be true
// ... rest of code
}
// Result: Assertion PASSES! tag is never true.

H3 REFUTED: tag is always false, yet quotes are still stripped.

Step 5 (continued): The Root Cause Revealed

New hypothesis H4: The quote condition evaluates to true even when tag is false.

else if (c == '"' || c == '\'' && tag) {
assert false; // This should never execute for "foo"
quote = !quote;
}
// Result: Assertion FAILS! The condition IS being triggered.

H4 CONFIRMED. But wait—let's test with single quotes:

removeHtmlMarkup("'foo'");  // Returns "'foo'" - quotes preserved!

Double quotes are stripped. Single quotes are preserved. The difference?

Operator precedence! c == '"' || c == ''' && tag is parsed as
(c == '"') || (c == ''' && tag)

Step 6: Fix — Implement and Verify

Before (buggy):

else if (c == '"' || c == '\'' && tag) {
quote = !quote;
}

Parsed as:

(c == '"') || (c == '\'' && tag)

After (fixed):

else if ((c == '"' || c == '\'') && tag) {
quote = !quote;
}

Parsed as:

(c == '"' || c == '\'') && tag

✓ All tests now pass. The bug was operator precedence—parentheses fix it.

The Hypothesis Funnel: How We Narrowed Down the Bug

Funnel diagram showing how each hypothesis test narrowed the search space from 'something is wrong' to 'operator precedence bug'

4 targeted tests took us from "something is wrong" to "operator precedence bug."

When to Use Systematic Debugging

Quick Fixes (1-2 tries):

  • Obvious typos
  • Simple logic errors
  • Familiar patterns

Just fix it.

Systematic Approach:

  • Bug persists after a few attempts
  • You don't understand the root cause
  • Complex interactions involved

Open a debugging log.

Rule of thumb: If you can't fix it in two tries, start writing down your hypotheses. The log helps when you think "but I already checked that!"

Why Keep a Debugging Log?

Split image showing a developer's memory fading over time without a log versus maintaining clarity with a written debugging log

Tools for Debugging

1. Write an automated test first

  • You may need to reproduce the bug 100+ times to pinpoint it and confirm the fix
  • Manual reproduction = slow feedback loop = frustration
  • A failing test makes each hypothesis test instant

2. Reason through the code (rubber duck debugging)

  • Explain the code out loud, line by line
  • "The variable tag starts false, then when we see <..."
  • Often reveals assumptions you didn't know you were making

3. Add logging statements (cautiously)

  • Useful when you can't attach a debugger (production, distributed systems)
  • But: clutters code, requires guessing what to log, noisy output
  • Remove or use proper logging levels when done

4. Use a debugger ← next slide!

Debugging Our HTML Example

Screenshot of debugger stepping through the HTML strip tags code, showing breakpoint hit, variables panel with tag and quote values, and call stack

Debugger = Interactive Hypothesis Testing

Instead of adding print statements and re-running, the debugger lets you test hypotheses interactively:

To Test This Hypothesis...Use This Debugger Feature
"Is tag ever true outside a tag?"Conditional breakpoint: tag == true
"What is quote when we hit this line?"Watch the quote variable
"Does this branch ever execute?"Set breakpoint on that line
"What path did execution take?"Check the call stack

Core Debugger Concepts

Breakpoints:

  • Line breakpoint: Pause at specific line
  • Conditional: Pause only when condition is true
  • Exception: Pause when exception thrown

Stepping Commands:

  • Step Over (F10): Execute line, go to next
  • Step Into (F11): Enter method call
  • Step Out (Shift+F11): Finish current method
  • Continue (F5): Run to next breakpoint

Inspection:

  • Variables pane: See all values at current point
  • Watch expressions: Track specific expressions
  • Call stack: How did execution get here?
  • Evaluate expression: Test hypotheses interactively

Effective Debugger Workflow

  1. Reproduce reliably — Write a failing test if possible
  2. Form hypothesis — Use control/data flow analysis to guess location
  3. Set strategic breakpoints — Start broad, narrow down
  4. Step through systematically — Watch for unexpected values/paths
  5. Note when values first become wrong — That's where the bug manifests
  6. Verify understanding before fixing — Can you explain the bug?

Common pitfall: Stepping through every line. Use conditional breakpoints instead!

AI + Scientific Debugging Workflow

From L13, we learned a 6-step workflow for AI collaboration. That same framework applies to debugging—AI can assist at each step of the scientific method, but YOU lead:

StepYou DoAI Assists
1. ObserveNotice the bug, gather symptoms"Explain what this error message means"
2. HypothesizeForm theory about cause"What could cause this behavior?"
3. PredictDefine what evidence to look for"Generate test cases to isolate this"
4. TestRun debugger, check values"Create a Mermaid diagram of this call flow"
5. AnalyzeInterpret results"Does this match pattern X?"
6. FixImplement the solution"Suggest a fix for this root cause"

Notice: YOU lead every step. AI accelerates, but doesn't replace your thinking.

Callback: How Hard Is It to Evaluate?

From L13: AI can generate for any task, but evaluation difficulty varies:

Easy to EvaluateHard to Evaluate
Does the test pass now? ✓Did we fix the root cause or just the symptom?
Does the error message disappear? ✓Will this fix cause bugs elsewhere?
Does output match expected? ✓Is this the right fix or a workaround?

AI might "fix" your bug in a way that passes the test but breaks something else.

Your job: Understand the fix, not just accept it.

The Right Way to Use AI for Debugging

Split panel contrasting two AI approaches: left shows 'Fix my bug' leading to confusion and no learning; right shows using AI for diagrams and understanding, leading to verified knowledge

Summary: Program Understanding & Debugging

  1. Debugging is integral to implementation and validation — not a separate phase
  2. Control flow + data flow — trace execution paths AND how values change
  3. Diagrams (call graphs, sequence, class) — visualize what code can't show linearly
  4. Scientific method — observe, hypothesize, predict, test, analyze, iterate (same as L13's 6-step workflow!)
  5. Debuggers — accelerate hypothesis testing without modifying code
  6. AI assistants — use the same 6-step workflow; YOU must evaluate the fix
  7. Avoid "vibe debugging" — if you can't explain the fix, you haven't fixed it

From L13: Task familiarity determines AI appropriateness. If you can't evaluate whether a bug fix is correct, you shouldn't accept AI's suggestion blindly.

From the syllabus: "The hardest parts of building software have never been typing code." Debugging is where understanding proves its value.

Tools and Resources

VS Code Extensions for Mermaid:

  • Markdown Preview Mermaid Support — Renders Mermaid in preview
  • Mermaid Editor — Live editing with preview pane

Online Tools:

Debugging Book:

IDE Debugger Guides:

  • IntelliJ: Help → Find Action → "Debug"
  • VS Code: Run → Start Debugging (F5)

Next Steps

Tomorrow: AI Coding Assistants Lab

  • Practice the prompting techniques from L13 and today

Thursday: HW3 Due

  • If you haven't started, start NOW
  • Recipe collections, JSON serialization, polymorphic types
  • Use the debugging techniques from today when you get stuck!

Wednesday: Testing