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.
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.
There are several ways to set breakpoints in the browser:
The simplest way is to use the Sources panel (Chrome) or Debugger panel (Firefox):
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.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.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:
dinosaurId === 'TRX-01' or dangerLevel > 8We 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}When code is paused at a breakpoint, we have several step execution options that allow controlled traversal of the code:
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}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}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}Resumes code execution until the next breakpoint is encountered or the script finishes.
When code is paused at a breakpoint, we can thoroughly examine the application state:
One of the most important panels during debugging — shows all available variables in the current scope:
this in the current contextFor 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}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)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.
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.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:
Now the browser will pause execution when any unhandled exception occurs, greatly simplifying identification of the source of problems.
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.
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}During testing, the monitoring system unexpectedly reports 'critical' alert level for some dinosaurs, even though they are safely in their enclosures.
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}1const inContainment = isInContainmentZone(location.coordinates);location variable in the Scope panel — it's nullgetLastKnownLocation sometimes returns null when it can't find the location, and the code doesn't check for this before accessing location.coordinates1// 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 worstAfter fixing the bug, remove the breakpoint and test the system again. This time it works correctly without reporting false alarms.
Breakpoints and step-by-step code execution are invaluable tools in the arsenal of every developer working on Jurassic Park systems. They allow:
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!