We use cookies to enhance your experience on the site
CodeWorlds

Breakpoints and Step-by-Step Code Execution

In the previous exercise we learned about the browser console and its logging functions. However, even the best console messages can be insufficient when dealing with complex bugs in the Jurassic Park management system. What if the

validateDinosaurContainment()
function returns an incorrect result but we don't know why? What if the predator behavior tracking algorithm has a subtle bug?

In such cases we need the ability to pause code execution at a specific point and analyze the application state. This is where breakpoints and step-by-step code execution come in — some of the most powerful JavaScript debugging techniques.

What Are Breakpoints?

A breakpoint is a marker placed in code that tells the browser to stop code execution when it reaches that point. This allows detailed examination of the application state at a chosen moment — we can check variable values, inspect the call stack, and execute code step by step.

Setting Breakpoints in the Browser

There are several ways to set breakpoints in the browser:

1. Breakpoints in the Sources/Debugger Panel

The simplest way is to use the Sources panel (Chrome) or Debugger panel (Firefox):

  1. Open developer tools (<kbd>F12</kbd> or <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>I</kbd>)
  2. Go to the "Sources" tab (Chrome) or "Debugger" tab (Firefox)
  3. Find and open the file where you want to set a breakpoint
  4. Click the line number where you want to pause execution
  5. A blue arrow or marker will appear at the selected point, indicating an active breakpoint

For example, we may want to pause execution when checking fence status in Jurassic Park:

1function checkFenceStatus(sectorId) {
2  const sector = getSector(sectorId); // Set breakpoint on this line
3
4  if (!sector) {
5    throw new Error(`Unknown sector: ${sectorId}`);
6  }
7
8  const fenceStatus = sector.fence.getStatus();
9
10  if (fenceStatus !== 'active') {
11    triggerAlarm(sectorId, `Fence failure: ${fenceStatus}`);
12  }
13
14  return fenceStatus;
15}

After setting the breakpoint, when

checkFenceStatus()
is called, the browser will pause execution at that line and we'll be able to check the value of
sectorId
and other available variables.

2. Programmatic Breakpoints with the debugger Statement

Sometimes it's more convenient to place a breakpoint directly in the code using the

debugger
statement:

1function trackDinosaur(dinosaurId, location) {
2  const dinosaur = getDinosaurById(dinosaurId);
3
4  debugger; // Code will pause here when the function is called
5
6  if (!dinosaur) {
7    console.error(`Dinosaur not found with ID: ${dinosaurId}`);
8    return false;
9  }
10
11  updateDinosaurLocation(dinosaurId, location);
12  checkContainmentStatus(dinosaurId, location);
13
14  return true;
15}

The

debugger
statement is ignored when developer tools are not open, but when active, the browser pauses execution at that point.

3. Conditional Breakpoints

Sometimes we want to pause execution only when a certain condition is met — for example, when tracking a dinosaur with a specific ID or when the danger level exceeds a value:

Setting via Browser Interface

  1. Set a regular breakpoint by clicking the line number
  2. Right-click on the breakpoint
  3. Select "Edit breakpoint" or "Add condition"
  4. Enter a condition, for example:
    dinosaurId === 'TRX-01'
    or
    dangerLevel > 8

Programmatic Conditional Pause

We can also use a conditional construct with the

debugger
statement:

1function monitorDinosaurBehavior(dinosaur) {
2  const behaviorPattern = analyzeBehavior(dinosaur);
3  const dangerLevel = assessThreatLevel(behaviorPattern);
4
5  // Pause execution only if the danger level is high
6  if (dangerLevel > 8) {
7    debugger; // Will pause only for high danger levels
8
9    // Code to handle dangerous behaviors
10    activateEmergencyProtocol(dinosaur.id, dangerLevel);
11  }
12
13  return {
14    dinosaurId: dinosaur.id,
15    behaviorPattern,
16    dangerLevel,
17    timestamp: new Date()
18  };
19}

Step-by-Step Code Execution

When code is paused at a breakpoint, we have several step execution options that allow controlled traversal of the code:

1. Step Over (F10)

Executes the current line and stops at the next line in the same function. If the current line contains a function call, that function is executed entirely (we don't step inside it).

1function monitorParkSystems() {
2  checkSecuritySystems();     // Step Over executes this entire function...
3  checkDinosaurContainment(); // ...and stops here
4  checkVisitorSafety();
5}

2. Step Into (F11)

Executes the current line, but if the line contains a function call, the debugger "enters" that function and stops at its first line.

1function monitorParkSystems() {
2  checkSecuritySystems();     // Step Into will enter this function...
3  checkDinosaurContainment(); // ...instead of moving here
4  checkVisitorSafety();
5}
6
7function checkSecuritySystems() {
8  const fenceStatus = checkFences();   // ...and will stop here
9  const cameraStatus = checkCameras();
10  const gateStatus = checkGates();
11
12  return { fenceStatus, cameraStatus, gateStatus };
13}

3. Step Out (Shift+F11)

Executes the remaining part of the current function and stops after returning from the function, at the point where it was called.

1function checkSecuritySystems() {
2  const fenceStatus = checkFences();
3  const cameraStatus = checkCameras(); // If we are here...
4  const gateStatus = checkGates();
5
6  return { fenceStatus, cameraStatus, gateStatus };
7}
8
9function monitorParkSystems() {
10  checkSecuritySystems();     // ...Step Out executes the rest of the function and stops here
11  checkDinosaurContainment();
12  checkVisitorSafety();
13}

4. Continue (F8)

Resumes code execution until the next breakpoint is encountered or the script finishes.

Inspecting Variables and State

When code is paused at a breakpoint, we can thoroughly examine the application state:

1. Scope/Variables Panel

One of the most important panels during debugging — shows all available variables in the current scope:

  • Local — local variables in the current function
  • Closure — variables from closures (parent functions)
  • Global — global variables
  • this — the value of
    this
    in the current context

For example, when debugging a function monitoring dinosaur behavior:

1function analyzeDinosaurBehavior(dinosaur, environmentalFactors) {
2  const { species, previousBehaviors, lastFeeding } = dinosaur;
3  const { weather, timeOfDay, nearbyDinosaurs } = environmentalFactors;
4
5  const timeSinceFeeding = Date.now() - lastFeeding;
6  const isHungry = timeSinceFeeding > getSpeciesFeedingThreshold(species);
7
8  debugger; // pause here
9
10  // At this point the Scope panel will show:
11  // - Local: dinosaur, environmentalFactors, species, previousBehaviors, lastFeeding,
12  //          weather, timeOfDay, nearbyDinosaurs, timeSinceFeeding, isHungry
13  // - Global: global objects and functions
14
15  const behaviorPrediction = predictBehavior(
16    species, previousBehaviors, isHungry, weather, timeOfDay, nearbyDinosaurs
17  );
18
19  return {
20    dinosaurId: dinosaur.id,
21    prediction: behaviorPrediction,
22    riskLevel: assessRiskLevel(behaviorPrediction),
23    isHungry,
24    timestamp: new Date()
25  };
26}

2. Watch Expressions

The Watch panel lets you add expressions to monitor during debugging. This is especially useful for complex expressions or values not directly visible in the Scope panel:

1// We can add watched expressions such as:
2dinosaur.species === 'Velociraptor'
3getDistanceFromFence(dinosaur.location)
4isInDangerZone(dinosaur.location, restrictedSectors)

3. Console in Debug Mode

While paused at a breakpoint, the console operates in the context of the current code position. We can execute expressions that have access to all variables visible in the current scope:

1// In the console we can type:
2dinosaur.species
3timeSinceFeeding / (1000 * 60 * 60) // Convert milliseconds to hours
4nearbyDinosaurs.filter(d => d.species === 'Velociraptor')

This is a powerful way to interactively explore the application state and run experiments without modifying code.

Practical Debugging Techniques for Jurassic Park

1. Debugging Asynchronous Code

Jurassic Park systems often use asynchronous operations — fetching data from sensors, communicating with security systems, etc. Debugging such code requires special techniques:

1async function monitorsEnvironmentalSensors() {
2  try {
3    console.log("Starting sensor monitoring...");
4
5    // Asynchronously fetch sensor data
6    const temperatureData = await fetchSensorData('temperature');
7    debugger; // Breakpoint pauses after receiving the response
8
9    const humidityData = await fetchSensorData('humidity');
10    const windData = await fetchSensorData('wind');
11
12    const environmentalRisk = analyzeEnvironmentalRisk(
13      temperatureData, humidityData, windData
14    );
15
16    if (environmentalRisk.level === 'high') {
17      notifyParkManagement(`High environmental risk detected: ${environmentalRisk.reason}`);
18    }
19
20    return environmentalRisk;
21  } catch (error) {
22    console.error("Sensor monitoring error:", error);
23    triggerBackupSystems();
24    return { level: 'unknown', reason: 'error' };
25  }
26}

In modern browser developer tools, breakpoints work correctly with async functions — execution will pause at the

debugger
point, even after
await
operations.

2. Pausing on Exceptions

In a complex system like Jurassic Park, errors can appear in unexpected places. Instead of manually setting breakpoints, we can configure the debugger to pause when an exception occurs:

  1. Open the Sources/Debugger panel
  2. Find the "Breakpoints" section
  3. Check "Pause on exceptions"
  4. Optionally: check "Pause on caught exceptions"

Now the browser will pause execution when any unhandled exception occurs, greatly simplifying identification of the source of problems.

3. Source Maps

In production applications, JavaScript code is often minified and bundled, making debugging difficult. Source maps allow browsers to show and debug the original source code, even when minified code is being executed:

1// webpack.config.js
2module.exports = {
3  devtool: 'source-map', // Generate source maps for all files
4};

For Jurassic Park, where safety is a priority, this is a critical feature that enables fast debugging of production issues.

Debugging in Practice — Case Study

Let's analyze a practical example of debugging a problem in the dinosaur monitoring system:

1class DinosaurMonitoringSystem {
2  constructor() {
3    this.activeMonitors = new Map();
4    this.alertLevel = 'normal';
5  }
6
7  checkDinosaurStatus(dinosaurId) {
8    const monitor = this.activeMonitors.get(dinosaurId);
9
10    if (!monitor) {
11      console.error(`No active monitoring for dinosaur ${dinosaurId}`);
12      return null;
13    }
14
15    try {
16      const dinosaur = getDinosaurById(dinosaurId);
17      const location = getLastKnownLocation(dinosaurId);
18      const vitalSigns = getVitalSigns(dinosaurId);
19
20      // BUG: Not checking if location exists!
21      const inContainment = isInContainmentZone(location.coordinates);
22
23      const reading = {
24        timestamp: Date.now(),
25        location,
26        vitalSigns,
27        inContainment
28      };
29
30      monitor.readings.push(reading);
31      monitor.locationStatus = inContainment ? 'contained' : 'breach';
32
33      return reading;
34    } catch (error) {
35      console.error(`Error checking status for dinosaur ${dinosaurId}:`, error);
36      return null;
37    }
38  }
39}

Step 1: Identify the Problem

During testing, the monitoring system unexpectedly reports 'critical' alert level for some dinosaurs, even though they are safely in their enclosures.

Step 2: Set Breakpoints

We set a breakpoint in

checkDinosaurStatus
to investigate:

1checkDinosaurStatus(dinosaurId) {
2  const monitor = this.activeMonitors.get(dinosaurId);
3  if (!monitor) { return null; }
4
5  debugger; // Set breakpoint here to examine all variables
6
7  try { /* ...rest of code */ }
8}

Step 3: Step Through and Analyze

  1. Step through code using "Step Into" and "Step Over"
  2. We reach the line checking location status:
    1const inContainment = isInContainmentZone(location.coordinates);
  3. Check the
    location
    variable in the Scope panel — it's
    null
  4. Identified problem:
    getLastKnownLocation
    sometimes returns
    null
    when it can't find the location, and the code doesn't check for this before accessing
    location.coordinates

Step 4: Fix the Bug

1// Safer code with verification
2const location = getLastKnownLocation(dinosaurId);
3const inContainment = location && location.coordinates
4  ? isInContainmentZone(location.coordinates)
5  : false; // If we don't know the location, assume the worst

Step 5: Verify the Solution

After fixing the bug, remove the breakpoint and test the system again. This time it works correctly without reporting false alarms.

Best Practices for Debugging

  1. Use meaningful variable names — makes identifying variables during debugging easier
  2. Add documentation to functions — JSDoc comments help understand the purpose and expected values
  3. Logically structure code — clean code is easier to debug
  4. Use defensive programming — validate inputs and handle edge cases
  5. Create small, dedicated functions — functions with single responsibility are easier to debug
  6. Use try-catch for potentially problematic code — helps localize error sources
  7. Avoid modifying debugged variables — it can change program behavior

Summary

Breakpoints and step-by-step code execution are invaluable tools in the arsenal of every developer working on Jurassic Park systems. They allow:

  • Pausing code execution at critical moments
  • Examining application state, variable values, and execution context
  • Executing code step by step to understand its flow
  • Identifying error sources, especially in complex systems
  • Verifying correctness of functions and modules

Remember that in Jurassic Park, the ability to quickly find and fix bugs can be a matter of life or death — both for the code and (in a real park) for the visitors!

Go to CodeWorlds