Data Structures, Modular Programming, and Version Control
Welcome, maker — let's organize your code like a pro!
You know variables and loops. Now we add containers that hold many values at once, patterns that keep your code tidy across multiple files, and a tool that saves every version of your work so you can always go back. These skills make the difference between a first draft and a finished project.
Summary
This chapter rounds out the core MicroPython toolkit and introduces software
engineering practices that scale. Students learn Python's primary data structures —
lists, tuples, and dictionaries — along with string manipulation and formatted output.
The chapter then teaches modular programming patterns: writing reusable functions,
separating hardware configuration into a config.py file, and keeping secrets like
WiFi credentials out of version control. Students also learn Git basics so they can
track their robot programs throughout the course.
Concepts Covered
This chapter covers the following 15 concepts from the learning graph:
- Lists
- List Indexing
- List Iteration
- Tuples
- Dictionaries
- String Manipulation
- Formatted Strings
- Modular Programming
- Reusable Functions
- Import Config Pattern
- Serial Communication
- Software Troubleshooting
- Code Documentation
- Version Control Git
- Git Commit Workflow
Prerequisites
This chapter builds on concepts from:
- Chapter 3: MicroPython and Development Environment Setup
- Chapter 4: Control Flow, Functions, and Exception Handling
Lists — Ordered Collections
A list is an ordered collection of values stored under a single variable name. Instead of creating color_0, color_1, color_2, you store all three in one list. Lists use square brackets and commas:
1 2 3 | |
Lists can hold any data type — strings, integers, floats, even other lists. A single list can even mix types, though it is usually clearer to keep one type per list.
List Indexing
Indexing means accessing a single item by its position. Python counts positions starting at 0, not 1. So the first item in colors is at index 0, the second at index 1, and so on.
1 2 3 4 5 | |
Negative indexes count from the end. colors[-1] gives "blue" — the last item. colors[-2] gives "green".
You can also update a list item by assigning to an index:
1 | |
Use len(colors) to find out how many items the list has. Accessing colors[3] when there are only 3 items (indexes 0–2) raises an IndexError — a very common bug.
List Iteration
Iterating a list means visiting each item one at a time. The simplest way uses a for loop:
1 2 | |
When you also need the index, use enumerate():
1 2 | |
Common list methods worth knowing:
colors.append("yellow")— adds an item to the endcolors.remove("green")— removes the first matching itemlen(colors)— returns the number of items
Diagram: List Index Explorer
Run List Index Explorer Fullscreen
Interactive MicroSim showing list indexing and iteration
Type: MicroSim
sim-id: list-index-explorer
Library: p5.js
Status: Specified
Create a p5.js MicroSim with a 700 × 300 canvas. Show a horizontal array of 5 colored boxes labeled with values (e.g., "red", "green", "blue", "orange", "purple"). Above each box, display the positive index (0, 1, 2, 3, 4). Below each box, display the negative index (-5, -4, -3, -2, -1).
Controls: - A slider "Index" from -5 to 4 selects a box, which highlights yellow with a glowing border. - A text display shows: "colors[X] = 'value'" where X is the current index. - A "Iterate" button animates the highlight moving left to right through all items with a 500ms delay between steps.
Clicking any box directly selects it and updates the index display.
Learning objective (Bloom's Taxonomy — Applying): students practice accessing specific items by index, including negative indexing.
Responsive: redraw on window resize.
Tuples — Immutable Sequences
A tuple is like a list, but it cannot be changed after creation. Tuples use parentheses instead of square brackets:
1 2 3 | |
The word immutable means "cannot be mutated (changed)." Once you create a tuple, you cannot append to it, remove from it, or change its values. Python enforces this. Trying to do motor_pins[0] = 5 raises a TypeError.
Tuples are useful when the data should never change — hardware pin numbers, screen dimensions, color constants. Using a tuple instead of a list signals to anyone reading your code: "these values are fixed on purpose."
Tuples support indexing and iteration exactly like lists:
1 2 3 4 | |
Dictionaries — Key-Value Stores
A dictionary stores pairs of keys and values. Instead of accessing data by position (like a list), you access it by name. Dictionaries use curly braces:
1 2 3 4 5 6 | |
Access a value with its key in square brackets:
1 2 | |
Update a value the same way:
1 2 | |
Add a new key-value pair by simply assigning to a new key:
1 | |
Dictionaries are great for grouping related data about one thing. Instead of separate variables robot_name, robot_speed, robot_distance, you have one robot dictionary with all the properties in one place.
The table below compares the three data structures:
| Structure | Syntax | Access method | Mutable? | Best for |
|---|---|---|---|---|
| List | [a, b, c] |
Index [0] |
Yes | Ordered sequences, sensor logs |
| Tuple | (a, b, c) |
Index [0] |
No | Fixed values, pin assignments |
| Dictionary | {k: v} |
Key ["name"] |
Yes | Named properties of one object |
Which container should I use?
Ask yourself: "Do I need to change the values? Are the values named or numbered?" If the values are fixed, use a tuple. If they're numbered in order, use a list. If they're named properties of one thing (like a robot's stats), use a dictionary. Getting this right makes your code easier to read and harder to break.
String Manipulation
Strings are more than just labels. MicroPython provides many operations for working with text — splitting it, joining it, searching it, and formatting it.
Some useful string operations:
1 2 3 4 5 6 7 8 | |
String slicing lets you extract a part of a string. message[0:5] gives characters at indexes 0, 1, 2, 3, 4 — but not index 5. The format is [start:stop] where stop is not included.
Formatted Strings
Formatted strings (also called f-strings) let you embed variable values directly inside a string. Before the code, here is the idea: put the letter f before the opening quote, then use curly braces {} around any variable or expression you want to insert.
1 2 3 4 5 6 7 8 | |
The : inside {} adds formatting. :.1f means "one decimal place as a float." F-strings are the clearest way to build display text for the OLED screen.
Modular Programming
As your robot programs grow, one long main.py file becomes hard to manage. Modular programming means splitting your code into separate files — each file handles one concern. This is the same decomposition idea from Chapter 1, applied to code organization.
In MicroPython, each .py file is a module. You import it just like a built-in library.
The config.py Pattern
The most important modular pattern in this course is the config.py file. Hardware pin numbers belong in config.py, not scattered through your program. If you ever change which pin a motor connects to, you change one line in config.py and everything else still works.
Create config.py on your board with pin assignments as constants:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
Then import it in main.py:
1 2 3 4 | |
This pattern keeps hardware details in one place and logic in another. Professional engineers call this separation of concerns — each file has exactly one job.
UPPERCASE constants are a signal
Pin numbers and hardware constants go in config.py in UPPERCASE. That capitalization signals to any reader: "this value is fixed — don't change it at runtime." When you see config.RIGHT_FORWARD_PIN in code, you know exactly where to look if the pin ever needs changing.
Reusable Functions and Module Files
You can also put groups of related functions in their own module file. For example, a motors.py file could hold all motor control functions:
1 2 3 4 5 6 7 | |
Then import and use it in main.py:
1 2 | |
This is exactly how the course's library files (vl53l0x.py, ssd1306.py) work. They are modules you import to get sensor and display functionality without writing those drivers yourself.
Serial Communication
Serial communication is how MicroPython sends text to Thonny's Shell pane. Every print() statement you write sends data over the USB cable as serial output. This works because the USB connection creates a virtual serial port — a communication channel that sends data one bit at a time.
Serial output is your most basic debugging tool. When you don't know what a sensor is reading, add a print(). When you don't know if a function is being called, add a print(). This technique is called print debugging, and professional engineers use it constantly.
1 2 3 4 5 | |
Remove or comment out debug prints before your final program — too many slows the loop slightly and clutters the output.
Software Troubleshooting
Software troubleshooting is the systematic process of finding and fixing bugs. Bugs are not failures — they are information. An error message tells you exactly where something went wrong and what type of error occurred.
When Thonny shows a red traceback, read it from the bottom up:
- The last line names the exception type and message:
AttributeError: 'NoneType' object has no attribute 'read' - The line above shows the file and line number:
File "main.py", line 23 - Working upward shows the call stack — how the program got there
A systematic troubleshooting process:
- Read the full error message before changing anything.
- Identify the file and line number from the traceback.
- Add print statements before and after the failing line.
- Check one variable at a time in the REPL.
- Test the smallest possible piece of code that still shows the bug.
This process — isolate, hypothesize, test, observe — is the engineering design process applied to code.
Code Documentation
Code documentation means writing explanations that help readers understand your code. There are two places to do this in Python: comments and docstrings.
Comments use # and explain a single line or short section. You practiced these in Chapter 3.
Docstrings are strings at the top of a function that explain what it does. They use triple quotes:
1 2 3 4 5 | |
Good documentation answers: What does this do? What inputs does it need? What does it return? These questions matter most when someone else reads your code — or when you return to it after a week away.
Version Control with Git
Version control is a system that tracks changes to your files over time. It lets you save checkpoints of your code. If you break something, you can go back to the last working checkpoint. We use Git — the most widely used version control system in the world.
Git stores your code's history in a hidden folder called a repository (or repo). Every checkpoint is called a commit.
Initializing a Repository
On your laptop (not the robot board), open a terminal in your project folder. Before the commands below, here is what each does: git init creates a new repository. git add stages files for the next commit. git commit saves those files as a checkpoint with a description.
1 2 3 | |
The Git Commit Workflow
The Git commit workflow is the cycle you repeat after every meaningful change:
- Make changes to your code in Thonny.
- Test the changes on the robot.
- Copy the updated files to your laptop.
- Stage the changed files:
git add filename.py - Commit with a clear message:
git commit -m "Add collision avoidance function"
A good commit message completes the sentence "This commit will...". "Fix motor speed bug" is good. "updates" is not.
Diagram: Git Commit Workflow
Run Git Commit Workflow Fullscreen
Interactive diagram of the Git commit workflow stages
Type: diagram
sim-id: git-commit-workflow
Library: Mermaid
Status: Specified
Create a Mermaid flowchart (graph LR, left to right) showing the four Git areas:
- "Working Directory" (yellow box) — where you edit files
- "Staging Area" (orange box) — files marked for the next commit
- "Local Repository" (blue box) — committed history on your laptop
- "Remote (GitHub)" (green box) — optional cloud backup
Arrows between areas labeled with commands:
- Working Dir → Staging Area: git add
- Staging Area → Local Repo: git commit -m "message"
- Local Repo → Remote: git push
- Remote → Local Repo: git pull
Every box and every arrow has a click directive that opens an infobox with a plain-language explanation of what that area stores or what that command does.
Canvas: 700 × 200 px. Responsive on window resize.
The .gitignore File
Not every file belongs in version control. A .gitignore file lists patterns of files Git should ignore.
Create a file named .gitignore in your project folder:
1 2 3 4 5 6 7 8 9 10 | |
The secrets.py file stores your WiFi network name and password. If you commit it to a public repository, anyone in the world can see your credentials. Always list secrets.py in .gitignore before your first commit.
Never commit secrets.py
WiFi passwords and API keys should never be in a Git repository. Add secrets.py to .gitignore before your first commit. If you accidentally commit credentials, consider them compromised — change the password immediately. This is a real-world security principle, not just a class rule.
Key Takeaways
- Lists store ordered, changeable sequences accessed by index (
[0],[-1]) - Tuples store fixed, unchangeable sequences — use them for pin numbers and constants
- Dictionaries store named key-value pairs — perfect for grouping an object's properties
- f-strings embed variables in text cleanly:
f"Speed: {speed_pct}%" - config.py centralizes all pin assignments — change hardware in one place
- Modular programming splits code across multiple files, each with one job
- Serial output (
print()) is your primary debugging tool - Git tracks code history; commit often with clear messages
- secrets.py never goes in version control — always add it to
.gitignore
You code like a software engineer now!
Double thumbs-up, maker! Lists, tuples, dictionaries, modular files, Git commits — you now have the tools that professional engineers use every day. The next chapter dives into the electronics and motors that make me actually move. Get ready to roll!