Yesterday "Age: " + 36 was a TypeError — Python refused to glue a string to an int. The escape was str(36). That works, but it's noisy when you have several variables to splice into one sentence. Imagine: "User " + str(name) + " is " + str(age) + " years old, lives in " + city. Quite a lot of plus signs.
And easy to forget the spaces. Or one of the str() calls. It looks like a bug factory.
Python has a cleaner answer: f-strings. Prefix the opening quote with the letter f, then write {variable} anywhere inside the string. Python substitutes the value at the moment the string is built:
name = "Ada"
age = 36
print(f"{name} is {age}")
# Ada is 36The f does the magic? Without it, {name} would be literal braces?
Without the f, Python sees {name} as four literal characters: open-brace, n, a, m, e, close-brace. Drop the f by accident and your output looks like {name} is {age} — a silent bug, no error raised. The f is the switch that turns those braces into substitution markers.
Can the braces hold an expression, or only a variable?
Any expression. f"{name.upper()} is {age * 2}" works — Python evaluates name.upper() and age * 2 and substitutes the results. The braces aren't restricted to bare names.
And the values can be any type? Numbers, strings, anything?
Anything Python can convert to a string — which is essentially everything. The f-string calls str() on each value automatically, so you skip the str() calls you'd need with +. Numbers, strings, lists, even your own custom types — they all just appear.
So one line, multiple values, no plus signs, no str(). This is what I expected programming to feel like.
That's the daily-driver string tool in modern Python. Reach for it any time you're building a string from values.
An f-string is a string literal with two extra powers:
f (or F){...} is evaluated as Python code and substituted into the stringname = "Ada"
age = 36
print(f"{name} is {age}") # Ada is 36{} is an expressionYou're not limited to variable names. Any expression works:
x = 5
print(f"x squared is {x * x}") # x squared is 25
print(f"upper case: {name.upper()}") # upper case: ADA
print(f"sum: {3 + 4}") # sum: 7: mini-language)After the expression, an optional colon introduces formatting hints:
| Spec | Meaning | Example | Output |
|---|---|---|---|
:.2f | float with 2 decimal places | f"{3.14159:.2f}" | 3.14 |
:, | thousands separator | f"{1000000:,}" | 1,000,000 |
:>10 | right-align in 10-char field | f"{'hi':>10}" | hi |
:<10 | left-align in 10-char field | f"{'hi':<10}" | hi |
:0>3 | pad with zeros to 3 chars | f"{7:0>3}" | 007 |
This lesson doesn't require format specifiers — they're a one-page mini-language you can pick up later as needed.
f is a silent bugname = "Ada"
print("{name} is here") # {name} is here ← no error, wrong output
print(f"{name} is here") # Ada is here ← correctNo f, no error — Python sees {name} as plain characters. This is a common slip; check the prefix any time the output has literal braces.
Double the brace to print a literal:
print(f"{{name}}: {name}") # {name}: Ada+ is still usefulf-strings replace the common case of building text from values. For pure string concatenation with no variables — joining two known strings — + is fine and arguably clearer:
path = base + "/" + filename # fine
path = f"{base}/{filename}" # also fine — pick the one that reads cleanestYesterday "Age: " + 36 was a TypeError — Python refused to glue a string to an int. The escape was str(36). That works, but it's noisy when you have several variables to splice into one sentence. Imagine: "User " + str(name) + " is " + str(age) + " years old, lives in " + city. Quite a lot of plus signs.
And easy to forget the spaces. Or one of the str() calls. It looks like a bug factory.
Python has a cleaner answer: f-strings. Prefix the opening quote with the letter f, then write {variable} anywhere inside the string. Python substitutes the value at the moment the string is built:
name = "Ada"
age = 36
print(f"{name} is {age}")
# Ada is 36The f does the magic? Without it, {name} would be literal braces?
Without the f, Python sees {name} as four literal characters: open-brace, n, a, m, e, close-brace. Drop the f by accident and your output looks like {name} is {age} — a silent bug, no error raised. The f is the switch that turns those braces into substitution markers.
Can the braces hold an expression, or only a variable?
Any expression. f"{name.upper()} is {age * 2}" works — Python evaluates name.upper() and age * 2 and substitutes the results. The braces aren't restricted to bare names.
And the values can be any type? Numbers, strings, anything?
Anything Python can convert to a string — which is essentially everything. The f-string calls str() on each value automatically, so you skip the str() calls you'd need with +. Numbers, strings, lists, even your own custom types — they all just appear.
So one line, multiple values, no plus signs, no str(). This is what I expected programming to feel like.
That's the daily-driver string tool in modern Python. Reach for it any time you're building a string from values.
An f-string is a string literal with two extra powers:
f (or F){...} is evaluated as Python code and substituted into the stringname = "Ada"
age = 36
print(f"{name} is {age}") # Ada is 36{} is an expressionYou're not limited to variable names. Any expression works:
x = 5
print(f"x squared is {x * x}") # x squared is 25
print(f"upper case: {name.upper()}") # upper case: ADA
print(f"sum: {3 + 4}") # sum: 7: mini-language)After the expression, an optional colon introduces formatting hints:
| Spec | Meaning | Example | Output |
|---|---|---|---|
:.2f | float with 2 decimal places | f"{3.14159:.2f}" | 3.14 |
:, | thousands separator | f"{1000000:,}" | 1,000,000 |
:>10 | right-align in 10-char field | f"{'hi':>10}" | hi |
:<10 | left-align in 10-char field | f"{'hi':<10}" | hi |
:0>3 | pad with zeros to 3 chars | f"{7:0>3}" | 007 |
This lesson doesn't require format specifiers — they're a one-page mini-language you can pick up later as needed.
f is a silent bugname = "Ada"
print("{name} is here") # {name} is here ← no error, wrong output
print(f"{name} is here") # Ada is here ← correctNo f, no error — Python sees {name} as plain characters. This is a common slip; check the prefix any time the output has literal braces.
Double the brace to print a literal:
print(f"{{name}}: {name}") # {name}: Ada+ is still usefulf-strings replace the common case of building text from values. For pure string concatenation with no variables — joining two known strings — + is fine and arguably clearer:
path = base + "/" + filename # fine
path = f"{base}/{filename}" # also fine — pick the one that reads cleanestCreate a free account to get started. Paid plans unlock all tracks.