Debugging Your Data Science Bugs: 5 Low-Level Lessons from Harvard’s CS50
This post translates core computer science concepts from Harvard’s CS50 into practical mental models for data professionals to build more robust and performant data pipelines.
Introduction
We’ve all been there: a data script that runs inexplicably slowly, a bug that disappears when you add a print() statement, or a piece of code that’s impossible to automate. These common frustrations for data practitioners often have roots in fundamental computer science concepts that are easy to overlook when working with high-level tools like Python or R. By understanding what’s happening “under the hood,” we can build better mental models for diagnosing problems, optimizing performance, and building robust data pipelines. Este post é uma síntese baseada no CS50x 2025 (Harvard) no YouTube. It explores key takeaways from the lecture on arrays, memory, and debugging, connecting them directly to the daily challenges of data work.

Takeaway 1: Compilation Isn’t Magic, It’s a Pipeline
The simple make command in C, much like running a Python script, hides a multi-step process. Just as high-level data tools abstract away underlying complexity, compilation is not a single action but a four-stage pipeline that transforms human-readable source code into machine-readable instructions.
The four distinct stages of compilation are:
- Pre-processing: This is an automated copy-and-paste step. When the compiler sees a directive like
#include, it finds the specified library file and pastes its contents into your code. - Compiling: This stage translates the human-readable C code into a lower-level language called assembly code, which consists of instructions specific to the computer’s processor.
- Assembling: This step converts the assembly code into the raw binary of machine code—the zeros and ones that the computer’s CPU can directly execute.
- Linking: The final stage combines your program’s machine code with the machine code from any libraries you used (e.g.,
cs50.c,stdio.c) into a single, complete, executable file.
This process is managed by a compiler program like clang, which uses flags like -o to define the output file and -l to link necessary libraries—a process that make conveniently automates for us.
Data Science Connection: This pipeline is a powerful mental model for debugging data environments. Just as the C linker requires all component files to be present, a modern data environment (like a Docker container) requires all dependencies in requirements.txt to be correctly installed. A linking error is directly analogous to a ModuleNotFoundError that breaks an entire Airflow DAG. This understanding helps a data scientist debug environmental issues systemically, recognizing them as dependency failures rather than random errors.
Takeaway 2: Memory is a Grid, and Your Data Has an Address
It’s helpful to visualize a computer’s memory as a massive grid of individual boxes, or bytes, each with a unique numerical address. When you declare a variable, you are asking the system to reserve one or more of these boxes to store your data.
Different data types occupy different amounts of space: a char is 1 byte, a float is 4 bytes, an int is 4 bytes, and a long is 8 bytes. Until a variable is explicitly initialized, its reserved memory can hold random, leftover data. In the lecture’s debug50 demonstration, an uninitialized variable H was shown to hold a bizarre garbage value like 32766 before it was assigned a value by the program.
Data Science Connection: This memory model is critical for performance in data analysis. Choosing float64 (a double in C, 8 bytes) when float32 (a float in C, 4 bytes) would suffice can literally double your memory usage for that column. For large datasets, this choice is the difference between a job that runs smoothly and one that crashes. Likewise, “garbage values” explain the unpredictable bugs that can arise from uninitialized variables or unhandled NULL values in a data transformation script.
Takeaway 3: A String is an Array with a Hidden Danger
In a low-level language like C, a string is simply an array of characters. The string “Hi!” is not a single entity but is stored in memory as three consecutive char variables: ‘H’, ‘i’, and ‘!’.
The “hidden danger,” or twist, is how the computer knows where the string ends. C automatically adds a special character, the null terminator (\0), to the end of every string. This character, which has a numerical value of 0, acts as a stop sign. This means a string’s actual memory footprint is always its visible length plus one extra byte for this terminator. The lecture demonstrates this by printing the characters of “Hi!” and then showing that the value at the very next position is 0, followed by garbage values.
“…the way a computer keeps track of where the end of a string is even if that string is three characters the length of the string the length of the array really that’s storing the string is technically four it’s one plus whatever the actual length of the phrase is…” — Harvard CS50x 2025 (YouTube) — Lecture: CS50x 2025 – Lecture 2 – Arrays
Data Science Connection: This has direct implications for data cleaning and text processing. The underlying array structure of text explains why going “out of bounds”—trying to access a character beyond the end of a string—can lead to bizarre bugs or critical security vulnerabilities like buffer overflows. The concept of a special terminator is also common in many data formats, such as the newline character (\n) that separates rows in CSV files or log files. Understanding this helps in writing more robust parsers for unstructured text data.
Takeaway 4: Stop Using print() and Start Using a Debugger
Debugging is a core skill, and the term itself has a famous origin story. As mentioned in the lecture, Rear Admiral Grace Hopper documented the “first actual case of bug being found” when a moth was physically removed from the Harvard Mark I computer. Today’s bugs are less tangible, but our methods for finding them have become far more sophisticated than simply printing variables.
A debugger is a tool that gives you direct control over your code’s execution. As shown in the lecture with debug50, its key features include:
- Breakpoints: The ability to set a “stop sign” on a specific line of code, pausing the program’s execution at that exact point.
- Stepping: The power to execute code line-by-line.
Step overexecutes a function as a single action, which is ideal for trusted library functions (like a pandasgroupby). In contrast,step intoenters a function to inspect its internal logic, which is essential for finding errors in your own custom functions. - Inspecting: The capability to observe the real-time values of all your variables as the code runs, revealing how they change step-by-step.
Data Science Connection: This is a high-leverage skill for data professionals. Debugging a complex, multi-step pandas transformation by printing intermediate data frames is messy and slow. Using a debugger in an IDE like VS Code or PyCharm provides a systematic, repeatable way to find the root cause of an error in your logic, rather than relying on guesswork and cluttering your code with temporary print statements.
Takeaway 5: Your Script’s Most Important Output is a Single Number
The reason the main function in C programs is defined to return an int is to provide an exit status code to the operating system. This single number communicates whether the program finished successfully or encountered an error.
The convention described in the lecture is universal in computing:
return 0;signifies success.return 1;(or any other non-zero number) signifies failure.
This convention exists because while there is only one way for a program to succeed (it did what it was supposed to do), there can be many different reasons for it to fail. Different non-zero numbers can be used to represent different types of errors, much like the error code 1132 in Zoom or the 404 code for a missing web page.
Data Science Connection: Exit codes are the backbone of automated data pipelines and workflows managed by tools like Airflow, cron, or dbt. A scheduler executes a script and then checks its exit code to determine the next step. A non-zero code signals that something went wrong, which can halt the pipeline, prevent bad data from propagating, and trigger an alert for an on-call analyst. Building scripts that properly signal success or failure with exit codes is essential for creating robust and reliable automated systems.
What to Apply Today
Here are five practical, actionable steps a data practitioner can take based on these takeaways:
- Inspect your data’s memory. Use a command like
df.info(memory_usage='deep')in pandas to see how much memory your data types are consuming. Question if afloat64is necessary when afloat32would suffice. - Use a real debugger once. The next time you have a bug, resist the urge to add a
print()statement. Instead, set a breakpoint in your IDE (like VS Code or PyCharm) and step through your code. - Map your dependencies. For your most critical data pipeline, draw a simple diagram of its dependencies—which scripts call which, what libraries are imported, and what data sources are accessed. This mirrors the ‘linking’ stage and helps you anticipate points of failure.
- Add an exit code. In a simple automation script, add a
try...exceptblock. If an error occurs, print a message and exit with a non-zero status code (e.g.,sys.exit(1)in Python). - Think about termination. When parsing a text file or log, consciously identify the “null terminator”—the character (like a newline) that signals the end of a record.
Closing
Understanding low-level computer science fundamentals provides powerful mental models that directly apply to high-level data work. Concepts like memory management, debugging, and exit codes are not just academic trivia; they are the foundation for writing code that is more performant, reliable, and scalable. These principles are the bedrock for more advanced topics like algorithms and data structures, which are critical for tackling larger and more complex data challenges.
What’s a high-level data bug you’ve encountered that now makes more sense through the lens of these low-level mechanics?

