My calculator says 2 + 3 is 23
See the problem
Section titled “See the problem”You write your first useful program: it asks for two numbers and adds them.
a = input("First number: ")b = input("Second number: ")print(a + b)You run it and type 2 and 3:
First number: 2Second number: 323Python didn’t add the numbers. It stuck them together. Why?
Define the task
Section titled “Define the task”- Input: two pieces of text, exactly as
input()gave them back:"2"and"3". They may have spaces round them, and may be decimals like"2.5". - Output: text like
"2 + 3 = 5", with the numbers as typed, spaces removed. A whole answer shows no.0:"0.5 + 0.5 = 1", not"= 1.0". - The hard part: text that looks like a number is still text, until you turn it into one.
Example: add_typed("2.5", "1") gives "2.5 + 1 = 3.5".
Pause and think
Section titled “Pause and think”Hints open one at a time:
Hint 1 of 3
input() always gives back a str, even when the user types digits.
Hint 2 of 3
int("2") gives the number 2. What does int("2.5") do?
Hint 3 of 3
float() reads decimals. A float that is a whole number can become an int again.
Understand the idea
Section titled “Understand the idea”A variable is a name for a value. price = 5 makes the name price and points it at the number 5. Point it at something new any time: price = price + 1. Names are case-sensitive, so Price is a different name.
Every value has a type. type() tells you which:
| Value | type() | What + does |
|---|---|---|
5 | int, a whole number | Adds: 5 + 3 is 8 |
2.5 | float, a decimal | Adds: 2.5 + 1 is 3.5 |
"5" | str, text | Joins: "5" + "3" is "53" |
input() always gives back text. Whatever the user types, you get a str. Turn it into a number with int() or float(). And turn a number into text with str(), or put it in an f-string.
print() shows values. It puts a space between them and a new line at the end. sep= and end= change those. An f-string puts values inside text: f"{qty} pens".
Here are text and numbers side by side. Each name points to its value. The player stops to ask what happens next. Then type your own lines under it.
Names
none yet
Objects
no lists, dicts or tuples yet
Step 1 of 8. Before the first line runs, there are no names and no objects.
a = "2" b = "3" joined = a + b x = 2 y = 3 total = x + y print(joined, total)
Climb the ladder
Section titled “Climb the ladder”All three do one sum on two short pieces of text, so all are O(1). What changes is which typed text they get right.
The program around add_typed stays the same. It asks for the two numbers and prints the answer:
# calculator.py: run it with python calculator.pyfrom best import add_typed
a = input("First number: ")b = input("Second number: ")print(add_typed(a, b))Add the text
The idea: Put a + between what input() gave back.
On 2 and 3: a is "2" and b is "3", both text. + on text joins it, so the answer is "23".
def add_typed(a: str, b: str) -> str: # a and b are what input() gave back. return f"{a} + {b} = {a + b}"- Time
O(1)- Space
O(1)- Why
- One join.
Where it breaks: every time. + never adds text as numbers.
int() first
The idea: Turn each piece of text into a whole number with int(), then add.
The waste in Okay: it uses the text as it came, when it needs to be a number.
On 2 and 3: int("2") + int("3") is 5. int() ignores spaces round the digits, so " 2 " works too.
Highlighted lines are new in this rung.
def add_typed(a: str, b: str) -> str: # a and b are what input() gave back. total = int(a) + int(b) return f"{a.strip()} + {b.strip()} = {total}"- Time
O(1)- Space
O(1)- Why
- Two conversions and one sum.
What changed from the previous rung
def add_typed(a: str, b: str) -> str: # a and b are what input() gave back. return f"{a} + {b} = {a + b}" total = int(a) + int(b) return f"{a.strip()} + {b.strip()} = {total}"Where it breaks: int("2.5") raises ValueError: invalid literal for int() with base 10: '2.5'. Any decimal crashes the program.
float(), then tidy the answer
The idea: Turn the text into floats, add, and show a whole answer without .0.
The waste in Better: it reads only whole numbers, when people type decimals too.
float() reads both "2" and "2.5". But 2.0 + 3.0 is 5.0, and people expect 5. total.is_integer() is true for 5.0, so int(total) drops the .0.
Highlighted lines are new in this rung.
def add_typed(a: str, b: str) -> str: # a and b are what input() gave back. total = float(a) + float(b) if total.is_integer(): total = int(total) return f"{a.strip()} + {b.strip()} = {total}"- Time
O(1)- Space
O(1)- Why
- Two conversions, one sum and one check.
What changed from the previous rung
def add_typed(a: str, b: str) -> str: # a and b are what input() gave back. total = int(a) + int(b) total = float(a) + float(b) if total.is_integer(): total = int(total) return f"{a.strip()} + {b.strip()} = {total}"When Best isn’t best: text like "two" still raises ValueError. The exceptions lesson shows how to catch it and ask again. For money, use whole cents or the decimal module, because floats can’t hold 0.1 exactly.
Write it yourself
Section titled “Write it yourself”Now write add_typed in the page. Press Run tests to check your code. Python runs in your browser, so nothing is sent anywhere.
Try Okay and Better from the ladder first: each fails a different test.
Add two typed numbers
Ctrl + Enter runs- Not run yet:
add_typed('2', '3')→'2 + 3 = 5' - Not run yet:
add_typed(' 2 ', '3 ')→'2 + 3 = 5' - Not run yet:
add_typed('2.5', '1')→'2.5 + 1 = 3.5' - Not run yet: Extra test
- Not run yet: Extra test
Extra tests check tricky cases. You see their input only if they fail.
Try your own input
Compare
Section titled “Compare”| Way | "2", "3" | "2.5", "1" | Good | Not so good |
|---|---|---|---|---|
Okay: a + b | 23 | 2.51 | Short | Joins text, never adds |
Better: int() | 5 | ValueError | Right for whole numbers | Crashes on decimals |
Best: float(), then tidy | 5 | 3.5 | Right for both, and reads well | A few more lines |
Common mistakes and edge cases
Section titled “Common mistakes and edge cases”int()of a decimal number:int(2.9)is2. It drops the part after the point; it doesn’t round.int()of decimal text:int("2.9")is aValueError. Usefloat()first.- Names: letters, digits and
_, not starting with a digit.my_totalis fine;2ndandmy-totalare not. - Swapping two values:
a, b = b, aswaps them in one line, with no extra name. - Decimal places:
f"{total:.2f}"shows two places. The value itself doesn’t change.
Say it in the interview
Section titled “Say it in the interview”Questions to ask first
- Can the input have decimals, spaces or signs?
- What should happen when the text isn’t a number?
A 60-second walkthrough
“
input()always returns a string, so+concatenates. I convert withfloat(), which accepts whole numbers, decimals and surrounding spaces. A whole result like5.0I turn back into an int for display. Text like'two'raisesValueError, so in a real tool I’d catch that and ask again.”
Likely follow-ups
- How do you check a value’s type?
type(x), orisinstance(x, int)to also accept subclasses. - What is the difference between
intandfloat? Anintis a whole number of any size. Afloatis a decimal with about 15 to 17 digits of precision. - Why does
0.1 + 0.2print0.30000000000000004? Floats are stored in binary, which can’t hold 0.1 exactly. The numbers and strings lesson explains it.
Practice
Section titled “Practice”Three problems, from easy to hard. Each has hints, tests you can run here, and its own Okay, Better and Best answers. Try before you open them.
1. Greet by name (easy)
Section titled “1. Greet by name (easy)”Write greet(name). name is what the user typed: it may have spaces at either end or between words, in any case. Return "Hello, Ana Maria!", with each word starting with a capital letter and one space between words.
Hint 1 of 3
name.strip() removes spaces at both ends.
Hint 2 of 3
name.split() with no argument splits on any run of spaces, and drops the empty pieces.
Hint 3 of 3
" ".join(words) puts the words back with one space each.
Greet by name
Ctrl + Enter runs- Not run yet:
greet('Ana')→'Hello, Ana!' - Not run yet:
greet(' ana ')→'Hello, Ana!' - Not run yet:
greet(' ana maria ')→'Hello, Ana Maria!' - Not run yet: Extra test
Extra tests check tricky cases. You see their input only if they fail.
Try your own input
Show the Okay, Better and Best answers
Put the name in an f-string
The idea: Use the name exactly as typed.
"Ana" gives "Hello, Ana!".
def greet(name: str) -> str: return f"Hello, {name}!"- Time
O(n)- Space
O(n)- Why
- n is the length of the name; the f-string copies it.
Where it breaks: " ana " gives "Hello, ana !", with the spaces and the small letter kept.
strip() and title()
The idea: Remove the spaces at the ends, and capitalise each word.
The waste in Okay: it trusts the user to type the name neatly.
Highlighted lines are new in this rung.
def greet(name: str) -> str: return f"Hello, {name.strip().title()}!"- Time
O(n)- Space
O(n)- Why
- Each method makes one new string.
What changed from the previous rung
def greet(name: str) -> str: return f"Hello, {name}!" return f"Hello, {name.strip().title()}!"Where it breaks: spaces between words stay. "ana maria" gives "Hello, Ana Maria!".
split(), then join()
The idea: Split the name into words, capitalise each, and join them with one space.
The waste in Better: it tidies the ends, but not the middle.
Highlighted lines are new in this rung.
def greet(name: str) -> str: words = name.split() return f"Hello, {' '.join(word.capitalize() for word in words)}!"- Time
O(n)- Space
O(n)- Why
- One pass to split, one to join.
What changed from the previous rung
def greet(name: str) -> str: return f"Hello, {name.strip().title()}!" words = name.split() return f"Hello, {' '.join(word.capitalize() for word in words)}!"When Best isn’t best: names like “McDonald” or “van der Berg” have their own capitals. Real systems keep the name as the person typed it, with only the outer spaces removed.
2. Celsius to Fahrenheit (medium)
Section titled “2. Celsius to Fahrenheit (medium)”Write to_fahrenheit(text). text is a temperature in °C, typed by a user, like "36.6". Return "36.6°C is 97.9°F", both with one place after the point. The formula is °F = °C × 9 / 5 + 32.
Hint 1 of 3
The text may be a decimal, so int() won’t do.
Hint 2 of 3
36.6 * 9 / 5 + 32 is 97.88000000000001 in Python. Floats can’t hold every decimal exactly.
Hint 3 of 3
Show one place with {value:.1f}.
Celsius to Fahrenheit
Ctrl + Enter runs- Not run yet:
to_fahrenheit('37')→'37.0°C is 98.6°F' - Not run yet:
to_fahrenheit('36.6')→'36.6°C is 97.9°F' - Not run yet: Extra test
- Not run yet: Extra test
Extra tests check tricky cases. You see their input only if they fail.
Try your own input
Show the Okay, Better and Best answers
int() and the formula
The idea: Turn the text into a whole number and apply the formula.
"100" gives "100°C is 212.0°F". The / always gives a float.
def to_fahrenheit(text: str) -> str: celsius = int(text) return f"{celsius}°C is {celsius * 9 / 5 + 32}°F"- Time
O(1)- Space
O(1)- Why
- One sum.
Where it breaks: "36.6" raises ValueError.
float()
The idea: Read decimals with float().
The waste in Okay: it reads only whole numbers.
Highlighted lines are new in this rung.
def to_fahrenheit(text: str) -> str: celsius = float(text) return f"{celsius}°C is {celsius * 9 / 5 + 32}°F"- Time
O(1)- Space
O(1)- Why
- One sum.
What changed from the previous rung
def to_fahrenheit(text: str) -> str: celsius = int(text) celsius = float(text) return f"{celsius}°C is {celsius * 9 / 5 + 32}°F"Where it breaks: "36.6" gives "36.6°C is 97.88000000000001°F". The value is right, but nobody wants to read it.
One place after the point
The idea: Show both numbers with :.1f.
The waste in Better: it shows every digit Python stores.
Highlighted lines are new in this rung.
def to_fahrenheit(text: str) -> str: celsius = float(text) return f"{celsius:.1f}°C is {celsius * 9 / 5 + 32:.1f}°F"- Time
O(1)- Space
O(1)- Why
- One sum.
What changed from the previous rung
def to_fahrenheit(text: str) -> str: celsius = float(text) return f"{celsius}°C is {celsius * 9 / 5 + 32}°F" return f"{celsius:.1f}°C is {celsius * 9 / 5 + 32:.1f}°F"When Best isn’t best: if the result goes on to more sums, keep the float and format it only when you show it.
3. A receipt line that lines up (hard)
Section titled “3. A receipt line that lines up (hard)”Write line(name, qty, price). Every line must be exactly 22 characters, so the columns line up. Put the name in 10 places on the left and the count in 3 places on the right. Then " x ", then the price in 6 places with 2 decimals.
A name longer than 10 characters is cut to 9, plus "…". Example: line("Pen", 3, 1.5) gives "Pen 3 x 1.50".
Hint 1 of 3
In an f-string, {name:<10} pads to 10 places on the left; {qty:>3} pads on the right.
Hint 2 of 3
{price:>6.2f} shows 2 decimals in 6 places.
Hint 3 of 3
Padding never cuts a long value. Cut it yourself with name[:9].
A receipt line that lines up
Ctrl + Enter runs- Not run yet:
line('Pen', 3, 1.5)→'Pen 3 x 1.50' - Not run yet:
line('Notebook', 12, 10)→'Notebook 12 x 10.00' - Not run yet:
line('Highlighter pen', 1, 2.25)→'Highlight… 1 x 2.25' - Not run yet: Extra test
Extra tests check tricky cases. You see their input only if they fail.
Try your own input
Show the Okay, Better and Best answers
Join with str()
The idea: Turn each number into text with str() and join the pieces with +.
"Pen 3 x 1.5" holds the right facts.
def line(name: str, qty: int, price: float) -> str: return name + " " + str(qty) + " x " + str(price)- Time
O(1)- Space
O(1)- Why
- A few short pieces of text.
Where it breaks: lines of different lengths don’t line up, and 1.5 isn’t shown as money.
Widths in an f-string
The idea: Pad each value to its own width with :<10, :>3 and :>6.2f.
The waste in Okay: it builds the line by hand, when the f-string can pad it.
Highlighted lines are new in this rung.
def line(name: str, qty: int, price: float) -> str: return f"{name:<10}{qty:>3} x {price:>6.2f}"- Time
O(1)- Space
O(1)- Why
- One f-string.
What changed from the previous rung
def line(name: str, qty: int, price: float) -> str: return name + " " + str(qty) + " x " + str(price) return f"{name:<10}{qty:>3} x {price:>6.2f}"Where it breaks: a width is the least, not the most. “Highlighter pen” takes 15 places and pushes the columns right.
Cut long names
The idea: Cut a name over 10 characters to 9 plus …, then pad as before.
The waste in Better: it trusts every name to be short.
Highlighted lines are new in this rung.
def line(name: str, qty: int, price: float) -> str: if len(name) > 10: name = name[:9] + "…" return f"{name:<10}{qty:>3} x {price:>6.2f}"- Time
O(1)- Space
O(1)- Why
- One check and one f-string.
What changed from the previous rung
def line(name: str, qty: int, price: float) -> str: if len(name) > 10: name = name[:9] + "…" return f"{name:<10}{qty:>3} x {price:>6.2f}"When Best isn’t best: for a real report, the tabulate package or a spreadsheet does the lining up. And a huge count or price still pushes its column, so check those too if they can grow.
Check yourself
Section titled “Check yourself”A few quick questions. Ones you miss are remembered, so you can find your weak spots later.
Flashcards
Section titled “Flashcards”Add these to your review deck. Each card comes back a little later every time you get it right.
Cheat sheet
Section titled “Cheat sheet”input()always gives back astr. Convert withint()orfloat()before sums.+adds numbers and joins text. Mixing them is aTypeError.type(x)shows a value’s type:int,float,str,bool,list…- f-strings:
f"{x}",f"{x:.2f}"(2 decimals),f"{s:<10}"(pad left),f"{n:>3}"(pad right). print(a, b, sep="-", end="")changes the space between values and the new line at the end.
Go deeper
Section titled “Go deeper”- An informal introduction to Python, in the Python tutorial: numbers, text and variables.
input()andprint()in the Python docs.- The format mini-language, for widths and decimal places.
- Next: Numbers, strings, slicing and f-strings.
Can you do these now?
If you’re not sure of any of these, scroll back to that part before moving on.
Status: Not started