Case Study 2: Rosa's First Budget Tracker
Meet Rosa
Rosa Martinelli is a 22-year-old freelance graphic designer who just graduated from art school. She has three regular clients, a few one-off gigs each month, and absolutely no idea where her money goes. Last month, she earned $3,200 but somehow ended the month with only $47 in her checking account. "I don't even buy expensive things," she tells her roommate. "Where does it all go?"
Her roommate, who is taking an introductory programming course, suggests they build a simple budget tracker in Pascal. "It doesn't need to be fancy," Rosa says. "I just want to know how much I'm spending on what."
This is the story of how Rosa thinks through her data — choosing types, defining variables, and encountering the first real design decisions that every programmer faces.
Step 1: What Data Does Rosa Need?
Rosa sits down with a notebook and lists what she wants to track for each expense:
- How much did I spend? (the amount)
- What category? (food, rent, transportation, entertainment, supplies, etc.)
- What was it? (a short description)
- When? (the date)
- Was it a business expense? (for tax purposes — she's a freelancer)
She also wants to track income separately:
- How much did I earn? (the income amount)
- From which client? (the source)
- When was I paid? (the date)
"Let's start simple," her roommate advises. "Just expenses for now. We can add income later."
Step 2: Choosing Types — The First Design Decisions
The Amount: Real or Integer?
Rosa's first instinct: "The amount is a number with cents, so it should be a Real."
var
expenseAmount: Real;
begin
expenseAmount := 4.50; { Coffee }
end.
Her roommate pauses. "That works, but there's a catch." He pulls up Free Pascal and types:
program MoneyTest;
var
total: Real;
begin
total := 0.0;
total := total + 0.10;
total := total + 0.10;
total := total + 0.10;
WriteLn('Expected: 0.30');
WriteLn('Got: ', total:0:20);
if total = 0.30 then
WriteLn('Equal!')
else
WriteLn('NOT equal!');
end.
Output:
Expected: 0.30
Got: 0.29999999999999999000
NOT equal!
"Wait, what?" Rosa stares at the screen. "How is 0.1 + 0.1 + 0.1 not 0.3?"
Her roommate explains: computers store Real numbers in binary. The fraction 1/10 cannot be represented exactly in binary, just as 1/3 cannot be represented exactly in decimal (0.333...). Every time you add 0.10, you're adding a tiny approximation. The errors accumulate.
"So what do we do?" Rosa asks. "My expenses have cents!"
There are three approaches:
Approach A: Use Real and Format Carefully
var
amount: Real;
begin
amount := 19.99;
WriteLn('$', amount:0:2); { Always display with 2 decimal places }
end.
This works for display purposes and for a simple budget tracker. The floating-point errors are in the 15th decimal place — they won't affect Rosa's displayed totals. But if she were writing banking software processing millions of transactions, those tiny errors could accumulate into real money.
Approach B: Store Amounts in Cents (Integer)
var
amountCents: LongInt;
begin
amountCents := 1999; { $19.99 stored as 1999 cents }
WriteLn('$', amountCents div 100, '.', amountCents mod 100:2);
end.
This eliminates floating-point error entirely. Addition of integers is exact. The downside: you have to remember that 1999 means $19.99, and displaying amounts requires dividing by 100.
Approach C: Use Currency Type (Free Pascal Extension)
var
amount: Currency; { 64-bit fixed-point, 4 decimal places }
begin
amount := 19.99;
WriteLn('$', amount:0:2);
end.
Free Pascal's Currency type is a 64-bit fixed-point type with exactly 4 decimal places. It can represent values from -922,337,203,685,477.5808 to 922,337,203,685,477.5807 with no floating-point imprecision. This is what professional financial software uses.
Rosa's Decision: For now, she goes with Approach A — Real with careful formatting. Her budget tracker is for personal use. She's not writing banking software. The tiny floating-point errors won't affect her displayed totals. But she makes a mental note: "If this ever gets serious, switch to integers-in-cents or Currency."
💡 Design Insight: There is no universally "correct" type for money. The right choice depends on your requirements. For a homework assignment or personal tool,
Realwith formatting is fine. For professional financial software, integer-cents orCurrencyis mandatory. The important thing is that you thought about it — and Pascal's type system forced you to think about it.
The Category: String or Something Else?
Rosa considers her categories: Food, Rent, Transport, Entertainment, Supplies, Other.
var
category: String;
begin
category := 'Food';
end.
A String works, but it has a weakness: nothing prevents Rosa from accidentally typing 'Fod' or 'food' or 'FOOD'. These would all be treated as different categories. Later in the book, when we learn about enumerated types (Chapter 8), we'll see a better solution:
{ Preview — we'll learn this properly in Chapter 8 }
type
TCategory = (catFood, catRent, catTransport, catEntertainment, catSupplies, catOther);
var
category: TCategory;
begin
category := catFood; { Valid }
category := catFod; { Compile error! Not a valid category }
end.
For now, Rosa uses String and is careful with her spelling.
The Date: String for Now
Dates are complicated. A proper date type would store year, month, and day as separate integers. Free Pascal has a TDateTime type in the SysUtils unit. But for Rosa's first version, a plain String in 'YYYY-MM-DD' format is good enough.
var
expenseDate: String;
begin
expenseDate := '2026-03-23';
end.
This is a valid design decision for a first version. The important thing is that Rosa knows this is a simplification and plans to improve it later.
The Business Flag: Boolean
Is this a business expense? This is a natural Boolean:
var
isBusiness: Boolean;
begin
isBusiness := True; { Design software subscription }
end.
Step 3: Rosa's First Program
After thinking through her types, Rosa writes her first budget tracker:
program RosaBudget;
const
PROGRAM_NAME = 'Rosa''s Budget Tracker';
VERSION = '0.1';
var
{ Expense 1 }
amount1: Real;
category1: String;
description1: String;
date1: String;
isBusiness1: Boolean;
{ Expense 2 }
amount2: Real;
category2: String;
description2: String;
date2: String;
isBusiness2: Boolean;
{ Expense 3 }
amount3: Real;
category3: String;
description3: String;
date3: String;
isBusiness3: Boolean;
{ Totals }
totalPersonal: Real;
totalBusiness: Real;
grandTotal: Real;
begin
{ Expense 1: Morning coffee }
amount1 := 4.50;
category1 := 'Food';
description1 := 'Oat milk latte, campus cafe';
date1 := '2026-03-20';
isBusiness1 := False;
{ Expense 2: Design software }
amount2 := 22.99;
category2 := 'Supplies';
description2 := 'Figma Pro monthly subscription';
date2 := '2026-03-20';
isBusiness2 := True;
{ Expense 3: Subway ride }
amount3 := 2.90;
category3 := 'Transport';
description3 := 'Metro card refill';
date3 := '2026-03-21';
isBusiness3 := False;
{ Calculate totals }
grandTotal := amount1 + amount2 + amount3;
totalBusiness := 0;
totalPersonal := 0;
if isBusiness1 then
totalBusiness := totalBusiness + amount1
else
totalPersonal := totalPersonal + amount1;
if isBusiness2 then
totalBusiness := totalBusiness + amount2
else
totalPersonal := totalPersonal + amount2;
if isBusiness3 then
totalBusiness := totalBusiness + amount3
else
totalPersonal := totalPersonal + amount3;
{ Display }
WriteLn('========================================');
WriteLn(' ', PROGRAM_NAME, ' v', VERSION);
WriteLn('========================================');
WriteLn;
WriteLn('--- Expense 1 ---');
WriteLn(' Date: ', date1);
WriteLn(' Category: ', category1);
WriteLn(' Description: ', description1);
WriteLn(' Amount: $', amount1:0:2);
if isBusiness1 then
WriteLn(' Type: BUSINESS')
else
WriteLn(' Type: Personal');
WriteLn;
WriteLn('--- Expense 2 ---');
WriteLn(' Date: ', date2);
WriteLn(' Category: ', category2);
WriteLn(' Description: ', description2);
WriteLn(' Amount: $', amount2:0:2);
if isBusiness2 then
WriteLn(' Type: BUSINESS')
else
WriteLn(' Type: Personal');
WriteLn;
WriteLn('--- Expense 3 ---');
WriteLn(' Date: ', date3);
WriteLn(' Category: ', category3);
WriteLn(' Description: ', description3);
WriteLn(' Amount: $', amount3:0:2);
if isBusiness3 then
WriteLn(' Type: BUSINESS')
else
WriteLn(' Type: Personal');
WriteLn;
WriteLn('========================================');
WriteLn(' TOTALS');
WriteLn(' Personal: $', totalPersonal:0:2);
WriteLn(' Business: $', totalBusiness:0:2);
WriteLn(' Grand: $', grandTotal:0:2);
WriteLn('========================================');
end.
Output:
========================================
Rosa's Budget Tracker v0.1
========================================
--- Expense 1 ---
Date: 2026-03-20
Category: Food
Description: Oat milk latte, campus cafe
Amount: $4.50
Type: Personal
--- Expense 2 ---
Date: 2026-03-20
Category: Supplies
Description: Figma Pro monthly subscription
Amount: $22.99
Type: BUSINESS
--- Expense 3 ---
Date: 2026-03-21
Category: Transport
Description: Metro card refill
Amount: $2.90
Type: Personal
========================================
TOTALS
Personal: $7.40
Business: $22.99
Grand: $30.39
========================================
Step 4: Rosa Sees the Pain Points
Rosa stares at her code. It works. But it is already 100 lines long and only tracks three expenses. She notices several problems:
-
Repetition: She had to declare five variables for each expense (
amount1,amount2,amount3...). Adding a fourth expense means adding five more variables and copy-pasting the display block. -
Scalability: What if she wants to track 30 expenses in a month? That would be 150 variable declarations. Clearly unsustainable.
-
Fragility: If she misspells a category —
'Trasport'instead of'Transport'— nothing catches it. The program happily stores the typo. -
No input: Everything is hardcoded. She has to edit the source code to add each expense.
"This is going to get better, right?" Rosa asks.
"Absolutely," her roommate says. "Each of these problems has a solution in Pascal:
- Repetition → Arrays (Chapter 6) and Records (Chapter 8) will let you group related variables and create collections.
- Scalability → Loops (Chapter 5) will let you process any number of expenses with the same block of code.
- Fragility → Enumerated types (Chapter 8) will make invalid categories a compile error.
- No input → We'll add ReadLn for user input in the very next chapter."
Step 5: What Rosa Learned About Types
Rosa reflects on the type decisions she made:
| Decision | Choice | Why | Future Improvement |
|---|---|---|---|
| Expense amount | Real |
Simple, handles dollars and cents | Currency or integer-cents for accuracy |
| Category | String |
Flexible, easy to use | Enumerated type for compile-time validation |
| Description | String |
Natural fit for free-form text | No change needed |
| Date | String |
Simple for now | TDateTime for date arithmetic |
| Business flag | Boolean |
Perfect fit — exactly two states | No change needed |
The most important insight: the choice of type is a design decision. It communicates your intent, constrains what operations are valid, and determines what kinds of errors the compiler can catch. Choosing Boolean for the business flag was perfect — there are exactly two possibilities. Choosing String for the category works but sacrifices compile-time safety. These trade-offs are real, and thinking about them is part of being a programmer.
Discussion Questions
-
Rosa chose
Realfor money amounts. Under what circumstances would you switch to integer-cents orCurrency? What would the tipping point be? -
Rosa's program has five variables per expense. If you had to track 100 expenses, what would break? (Imagine life before arrays and records.)
-
Rosa stores dates as strings in
'YYYY-MM-DD'format. What operations become difficult with this representation? (Hint: think about "How many days between two expenses?" or "Show me all expenses from March.") -
Rosa's
isBusinessflag is a Boolean. What if she needed a third category — "Mixed" (partially business, partially personal)? How would you modify the type? What does this tell you about choosing types carefully? -
Consider Rosa's category
Stringversus a future enumerated type. Which catches more bugs at compile time? Which is more flexible for the user? Is there always a tension between safety and flexibility?
Key Takeaways
- Type choices are design decisions. Every variable type communicates intent and determines what errors the compiler can catch.
Realfor money is a simplification. It works for simple programs but fails for high-precision financial calculations. Know the trade-off.- Start simple, improve iteratively. Rosa's first version has clear limitations, but it works. Each limitation has a Pascal feature that solves it — she just hasn't learned those features yet.
- Think about your data before you write code. Rosa spent time listing her fields and choosing types before writing a single line. This upfront thinking prevents costly redesigns later.
- Pascal's type system forces you to think. In a dynamically typed language, Rosa could store anything anywhere without planning. Pascal's requirement to declare types upfront is a forcing function for good design.