Chapter 02
Collections, errors and files
The core collections you use every day, what happens when you share them, and how to handle errors, files and code split across modules.
Lists and dynamic arrays
Also called array, ArrayList, vector, List<T>
- What
- An ordered collection that grows as you add items and gives fast access by index:
listin Python, arrays in JavaScript,ArrayListin Java,std::vectorin C++ andList<T>in C#. - Why
- It is the default collection for "a bunch of things in order". Reach for something else only when you need fast lookup by key or uniqueness.
std::vector<int> nums{3, 1, 2};
nums.push_back(5);
std::ranges::sort(nums);
int first = nums.front();
int last = nums.back();
std::vector<int> evens;
std::ranges::copy_if(nums, std::back_inserter(evens),
[](int n) { return n % 2 == 0; });
std::cout << nums.size() << '\n';Watch out
Removing items from a list while looping over it skips elements; build a new list or filter instead.
Dictionaries and maps
Also called dict, HashMap, Map, unordered_map, Dictionary, key-value
- What
- A collection of key and value pairs where you look a value up by its key, such as a product name to its stock count. Lookups, inserts and deletes take constant time on average.
- Why
- Use one whenever you would otherwise search a list for a matching item: counting, grouping, caching and indexing records by id.
std::unordered_map<std::string, int> stock{{"apple", 3}};
stock["pear"] = 5;
stock["apple"] += 1;
auto it = stock.find("kiwi");
int count = it != stock.end() ? it->second : 0;
for (const auto& [fruit, qty] : stock) {
std::cout << fruit << ' ' << qty << '\n';
}Watch out
Reading a missing key: Python and C# throw, Java and JavaScript give null or undefined, and C++ [] silently inserts. Use a get with a default.
Sets
Also called HashSet, unordered_set, unique values
- What
- An unordered collection of unique values with fast membership checks. Adding a value that is already there does nothing.
- Why
- Use a set to remove duplicates, to check "have I seen this before?" in constant time, or to find items two groups have in common.
std::unordered_set<std::string> seen;
for (const auto& email : emails) {
if (!seen.insert(email).second) {
std::cout << "duplicate: " << email << '\n';
}
}Watch out
Checking membership with a list inside a loop, which makes the loop quadratic when a set would keep it linear.
Mutability and copying
Also called shallow copy, deep copy, aliasing, immutable
- What
- A mutable value can change in place; an immutable one cannot. In Python, JavaScript, Java and C#, assigning a list to a second variable shares the same list. In C++ assignment copies unless you use a reference.
- Why
- Two names for one list is the source of many "why did my data change?" bugs. Knowing when you copy and when you share lets you avoid them.
std::vector<int> a{1, 2};
std::vector<int> b = a;
b.push_back(3);
std::cout << a.size() << '\n'; // 2, b is a copy
std::vector<int>& r = a;
r.push_back(3);
std::cout << a.size() << '\n'; // 3, r is aWatch out
Assuming a shallow copy is independent: nested lists or objects inside it are still shared with the original.
Exceptions
Also called try catch, error handling, throw, raise
- What
- An exception is an error object thrown when something goes wrong. It stops normal flow and travels up the call stack until a
try/catch(Python:try/except) handles it. - Why
- Exceptions keep error handling out of the happy path and let the layer that knows what to do (retry, show a message, log) deal with the problem.
double parse_amount(const std::string& text) {
try {
return std::stod(text);
} catch (const std::invalid_argument&) {
throw std::runtime_error("bad amount: " + text);
}
}
try {
parse_amount("abc");
} catch (const std::exception& e) {
std::cerr << e.what() << '\n';
}Watch out
Catching every exception and carrying on silently: the program keeps running with bad data and the real cause is lost.
Reading files and cleaning up
Also called with, try-with-resources, using, RAII, finally
- What
- Files, network connections and locks must be closed when you are done, even if an error happens halfway. Each language has a construct that does this for you:
with,try-with-resources,using,finallyor a C++ destructor. - Why
- Unclosed files leak handles, can lose buffered writes and lock the file on Windows. The built-in construct makes cleanup automatic instead of something you must remember.
std::ifstream in{"notes.txt"};
if (!in) {
std::cerr << "Could not open notes.txt\n";
return 1;
}
std::string line;
while (std::getline(in, line)) {
std::cout << line << '\n';
}Watch out
Opening text files without naming the encoding, so the program reads accented text correctly on one machine and garbles it on another.
Modules and imports
Also called packages, namespaces, header files, import
- What
- A module is a file (or group of files) of related code that other files import by name. Python and JavaScript import modules, Java uses packages, C# uses namespaces and C++ includes header files.
- Why
- Splitting code into modules keeps each file focused, avoids name clashes and lets you reuse and test parts on their own.
// pricing.h
#pragma once
double with_tax(double amount);
// pricing.cpp
#include "pricing.h"
double with_tax(double amount) { return amount * 1.2; }
// main.cpp
#include <iostream>
#include "pricing.h"
int main() { std::cout << with_tax(10) << '\n'; }Watch out
Circular imports, where two modules import each other: move the shared code into a third module.
Reading errors and debugging
Also called stack trace, traceback, breakpoint, debugger
- What
- A stack trace lists the chain of function calls that led to an error, with file names and line numbers. A debugger lets you pause at a breakpoint, step line by line and inspect variables.
- Why
- Reading the trace tells you where to look in seconds, and stepping through code shows what it actually does instead of what you assume it does.
- Example
- A trace ending in
KeyError: 'total'atreport.pyline 42, called frommain.pyline 10, means line 42 reads a key the dictionary does not have. Set a breakpoint there and inspect the dictionary.
Watch out
Reading only the last line of a long error, or fixing the last of many compiler errors, when the first one usually causes the rest.