Jako Heiberg
HomeInsightsMusingsPortfolioAboutContact
Jako Heiberg

From DBase III+ to edge computing.
The journey continues.

Pages

  • About
  • Portfolio
  • Resume / CV
  • Insights
  • Musings
  • Archive
  • Contact

© 2026 Jako Heiberg · Cape Town, South Africa · UTC+2

Built with React + Cloudflare Workers. No PHP was harmed.

Back to Insights
Insights

Python From Scratch — Part 2: Variables, Numbers & Strings

`input()` hands you back the string "25", never the number 25 -- and that one fact is behind more first-week Python bugs than anything else. This part covers variables, numbers and strings, plus the type-conversion trick that stops `"3" + 3` from ruining your afternoon.

July 28, 2026
9 min read
Python

Part 2 of 23 · Level: Beginner · From Hello World to Neural Networks

In Part 1 you made Python say hello. That is a fun party trick exactly once. To build anything real you need to remember things -- a name, a total, a score -- and do arithmetic on them without hard-coding every value by hand. That is this part: variables, numbers, and strings, the three ingredients in roughly every program ever written.

Variables: names for your stuff

A variable is a name you stick on a value so you can refer to it later. You create one with =, which in Python means "assign", not "is equal to".

name = "Ada"
age = 36
print(name, "is", age)

Output:

Ada is 36

The = does not compare -- it points the name name at the string "Ada". Read it right-to-left: take "Ada", and call it name.

Names have a few hard rules. They must start with a letter or underscore, can contain letters, digits and underscores, and are case-sensitive (age and Age are two different variables -- a fun way to lose an afternoon). They cannot be Python keywords like class or for.

Beyond the rules there is a convention, and Python takes conventions seriously: use snake_case -- lowercase words joined by underscores.

first_name = "Ada"
items_in_cart = 3
is_logged_in = True

Not firstName, not FirstName. Those run fine, but they mark you as a tourist. Pick names that say what they hold: x is acceptable for a throwaway, days_until_launch earns its keystrokes.

Reassignment is allowed and normal -- a variable is a label, not a life sentence. You can even point it at a completely different type of value:

score = 10
score = score + 5
print(score)     # => 15

score = "done"   # totally legal in Python
print(score)     # => done

That last move raises eyebrows in some languages. Python shrugs. More on why at the end.

Numbers: ints, floats, and the two kinds of division

Python has two number types you will meet constantly: int (whole numbers) and float (numbers with a decimal point).

apples = 4       # int
price = 4.99     # float

The usual arithmetic works as you would expect:

print(7 + 2)     # => 9
print(7 - 2)     # => 5
print(7 * 2)     # => 14
print(2 ** 8)    # => 256   (2 to the power of 8)

** is exponentiation -- "two to the eighth". Handy, and it saves you importing anything.

Division is where Python has an opinion worth knowing. Plain / always gives you a float, even when the numbers divide evenly:

print(10 / 2)    # => 5.0   (a float -- note the .0)
print(7 / 2)     # => 3.5

If you want the whole-number part, use //, floor division; and if you want the remainder, use %, modulo:

print(7 // 2)    # => 3    (how many whole 2s fit in 7)
print(7 % 2)     # => 1    (what is left over)

% looks obscure until you use it, then it is everywhere. "Is this number even?" is n % 2 == 0. "Every tenth item" is i % 10 == 0. It is the workhorse of "do something on a cycle".

One thing to internalise: mix an int and a float in any operation and the result floats up to a float.

print(3 + 0.0)   # => 3.0
print(4 * 2.5)   # => 10.0

Floats also carry the usual binary-versus-decimal baggage -- 0.1 + 0.2 is famously 0.30000000000000004, not 0.3. That is not a Python bug; it is how floating point works everywhere. File it away; we do not need to fix it yet.

Strings: text you can push around

A string is text. Wrap it in single or double quotes -- Python does not care which, as long as they match.

greeting = "hello"
name = 'Ada'

Pick one style and stay consistent. The one time it genuinely matters: if your text contains a quote, wrap it in the other kind -- "it's fine" needs double quotes so the apostrophe does not end the string early.

You can glue strings together with + (concatenation) and repeat them with *:

print("foo" + "bar")   # => foobar
print("ha" * 3)        # => hahaha

But concatenation gets ugly fast once variables are involved. The modern way -- and the one to reach for by default -- is the f-string: put an f before the opening quote and drop variables straight into {curly braces}.

name = "Ada"
age = 36
print(f"{name} is {age} years old")

Output:

Ada is 36 years old

You can even run small expressions inside the braces:

print(f"Next year she will be {age + 1}")   # => Next year she will be 37

f-strings are cleaner, faster, and far harder to mess up than stitching pieces together with +. Use them.

Strings also come with methods -- functions attached to the value, called with a dot. A few you will use daily:

text = "  Hello, World  "
print(text.upper())    # => "  HELLO, WORLD  "
print(text.lower())    # => "  hello, world  "
print(text.strip())    # => "Hello, World"   (trims outer whitespace)
print(len(text))       # => 15

len() is not a method (no dot) -- it is a built-in function that reports how many characters are in the string. It will come back for lists later in the series.

One quiet but important fact: strings are immutable. Methods like .upper() do not change the original -- they hand you a new string. So text.upper() on its own is thrown away unless you catch it: shouty = text.upper().

Reaching into a string: indexing and slicing

Every character in a string has a position, counting from zero -- the first character is index 0, not 1. Grab one with square brackets:

word = "Python"
print(word[0])    # => P
print(word[1])    # => y

Negative indexes count from the end, which is genuinely convenient:

print(word[-1])   # => n   (last character)
print(word[-2])   # => o

Slicing takes a range with [start:stop]. The start is included, the stop is not -- a quirk that trips up everyone exactly once, then feels natural forever.

print(word[0:3])   # => Pyt   (positions 0, 1, 2 -- stop is excluded)
print(word[2:])    # => thon  (from 2 to the end)
print(word[:2])    # => Py    (from the start up to 2)

Because stop is excluded, word[:3] and word[3:] split the string cleanly with no overlap and no gap. That is the whole reason for the "excluded" rule, and once it clicks you stop fighting it.

Talking to the user: input() and type conversion

input() pauses the program, waits for the user to type something and press enter, and hands back what they typed. Its optional argument is the prompt shown to them.

name = input("What is your name? ")
print(f"Hi, {name}!")

Here is the gotcha that catches every single beginner, so let us catch it now: input() always returns a string. Always. Even if the user types 25, you get the string "25", not the number 25. Watch it bite:

age = input("Your age? ")   # user types 25
print(age + 1)              # boom

Output:

TypeError: can only concatenate str (not "int") to str

Python is not being difficult -- "25" + 1 genuinely has no obvious meaning. Should it be "251" or 26? Rather than guess, Python stops. The fix is type conversion: wrap the string in int() or float() to turn it into a real number.

age = int(input("Your age? "))    # "25" -> 25
print(age + 1)                     # => 26

price = float(input("Price? "))    # "4.99" -> 4.99
print(price * 2)                   # => 9.98

Convert as early as possible -- right where the input arrives -- so the rest of your code works with real numbers. If the user types something that is not a number, int("hello") raises its own error; handling that gracefully is a job for the later part on error handling.

While we are here, the same gotcha in miniature, with no input involved:

print("3" + 3)     # TypeError: can only concatenate str... to str
print(3 + 3)       # => 6
print("3" + "3")   # => "33"   (string concatenation -- both are strings)

The + operator does two completely different jobs depending on the types on either side: add numbers, or join strings. Mix the two and Python refuses. This is a feature -- it catches a whole category of bugs before they ever run.

type() and Python's "we'll figure it out later" typing

If you are ever unsure what you are holding, ask. type() tells you.

print(type(4))        # => <class 'int'>
print(type(4.0))      # => <class 'float'>
print(type("4"))      # => <class 'str'>
print(type(True))     # => <class 'bool'>

Notice we never declared any of those types -- we assigned values and Python worked out what they were. That is dynamic typing: a variable's type is decided by whatever value it currently holds, and it can change when you reassign. Remember score going from 10 to "done" earlier? Perfectly legal, because the type rides with the value, not with the name.

This makes Python quick to write and, occasionally, quick to shoot yourself in the foot -- nothing stops you accidentally putting a string where you expected a number until it blows up at runtime. type() is your flashlight for exactly those moments.

Next up

You can now store data, do arithmetic, and wrangle text -- the raw material of every program. In Part 3: Booleans & Control Flow, we teach your code to make decisions: if, else, comparison operators, and the True/False values that drive them (that % trick for "is it even?" is about to earn its keep). Part 1 gave your program a voice; Part 3 gives it a brain.

Jako Heiberg

Software developer with 40+ years of building things that work. Full-stack, FastAPI, React. Based in Cape Town, working remotely, worldwide.

Read next

From Hello World to Neural Networks

Python From Scratch — Part 3: Booleans & Control Flow

Every `if` you write rests on a value that's either `True` or `False` — but `==` and `is` are not the same question, and the day you confuse them on the number 257 is the day you find out why. Booleans, truthiness, and Python's whitespace-is-the-syntax rule, up close.

Related Posts

Insights

Python From Scratch — Part 3: Booleans & Control Flow

Every `if` you write rests on a value that's either `True` or `False` — but `==` and `is` are not the same question, and the day you confuse them on the number 257 is the day you find out why. Booleans, truthiness, and Python's whitespace-is-the-syntax rule, up close.

August 12, 2026
8 min read
Insights

Python From Scratch — Part 1: Hello, World

Getting Python installed and running is the part that quietly stops more beginners than loops, classes, or neural networks ever will. Part 1 takes you from nothing installed — on Windows, macOS, or Linux — to your first running program, with a warning about the apostrophe that bites on line one.

July 23, 2026
8 min read
Insights

FastAPI vs Flask: I've Used Both, Here's What They Don't Tell You

Flask will do exactly what you tell it to and nothing more, which sounds like a compliment until you realise how much you forgot to tell it. FastAPI has opinions, and after shipping production APIs with both, I've come around to thinking that's the point.

May 7, 2026
5 min read