Skip to content
Hello Python
3/8

Strings

Topic 3 of 8, with 4 concept checks. Immutability, formatting, and common string operations

Reason about immutable text transformations

Text operations

Treat every string operation as producing a value rather than changing the original text, then connect indexing, slicing, formatting, and normalization to that immutable model.

Core lesson 01

my_string[::-1] — slicing with a step of -1, implemented in C, is the idiomatic and fastest way.

Python slice syntax is `seq[start:stop:step]`. Omitting start/stop and using step -1 tells Python to walk the whole sequence backward. Because slicing is implemented in CPython's C layer, this is faster than a manual loop for reversing any sequence, not just strings.

Python example
s = "hello"
print(s[::-1])       # 'olleh'
print(s[::2])        # 'hlo'  -- every 2nd char
print(list(reversed(s)))  # alternative, returns an iterator

What to remember

How do you reverse a string idiomatically in Python?

Common footguns

  • Using `reversed(s)` and then trying to print it directly — it returns an iterator, not a string; wrap with `''.join(...)`.

Core lesson 02

Prefer f-strings for readability and speed. Use .format() for dynamically-built templates. % is legacy.

All three embed values into strings, but differ in mechanism. `%` is C-style printf formatting — oldest, least flexible. `.format()` uses `{}` placeholders resolved by position/name, useful when the template string itself is data (e.g. loaded from a file). f-strings (3.6+) let you write expressions directly inside `{}` and are evaluated at parse time, making them both the fastest and most readable for code you're writing directly.

Python example
name, score = "Ada", 95
print("%s scored %d" % (name, score))     # legacy
print("{} scored {}".format(name, score))  # flexible templates
print(f"{name} scored {score}")            # preferred
print(f"{score/100:.1%}")                  # inline formatting spec

What to remember

What's the difference between f-strings, .format(), and % formatting?

Common footguns

  • Using an f-string for a template string that isn't known until runtime (e.g. loaded from a config file) — f-strings evaluate immediately, so `.format()` is actually the right tool there.

Core lesson 03

Strings are immutable, so each += copies everything so far into a new string — O(n²) total for n iterations. Use ''.join(...) instead.

Because str is immutable, `s = s + "a"` (what += desugars to for strings) allocates a brand new string containing the old contents plus the new character, then rebinds `s` to it. Doing this n times means copying 1 + 2 + ... + n characters — quadratic total work. `''.join(parts)` instead computes the final size once and writes each piece exactly once.

Python example
# Slow: O(n^2)
s = ""
for i in range(5):
    s += str(i)
print(s)

# Fast: O(n)
parts = [str(i) for i in range(5)]
print("".join(parts))

What to remember

Why is repeatedly doing s += 'a' in a loop inefficient?

Common footguns

  • Not noticing this until the input gets large — for small n the slow version looks perfectly fine.
s = ""
for i in range(n):
    s += "x"   # each += copies ALL previous chars into a NEW string
              # 1+2+3+...+n  ~= O(n^2) total copies

"".join(parts)  # O(n) -- builds the result once

Core lesson 04

strip() trims both ends, lstrip() only the left, rstrip() only the right — all default to whitespace but accept a custom character set.

All three remove characters from the ends of a string, not the middle. By default they strip whitespace, but you can pass a string of characters to strip instead — and it's treated as a *set* of characters, not a literal prefix/suffix.

Python example
print('  hi  '.strip())       # 'hi'
print('  hi  '.lstrip())      # 'hi  '
print('  hi  '.rstrip())      # '  hi'
print('xxhixx'.strip('x'))    # 'hi'
print('xyhixy'.strip('xy'))   # 'hi' -- strips ANY of x or y, not the literal 'xy'

What to remember

What's the difference between .strip(), .lstrip(), and .rstrip()?

Common footguns

  • Expecting `.strip('xy')` to remove the literal substring 'xy' from the ends — it actually strips any combination of the characters 'x' and 'y'. For literal prefix/suffix removal, use `.removeprefix()`/`.removesuffix()` (3.9+).

Python lab

Browser Python lab

Runtime · idle

Python loads on your first run. Your code stays in this browser.

Best practices

  • Use f-strings for formatting in Python 3.6+.
  • Use ''.join(...) instead of += when building strings inside a loop.
  • Use .startswith()/.endswith() instead of slicing for prefix/suffix checks.
  • Use .casefold() rather than .lower() for robust case-insensitive comparisons across languages.

Apply the concept in Interview practice

Valid PalindromeeasyLeetCode #125 · O(n) time, O(1) space

Filter to alphanumeric lowercase characters, then compare with two pointers from both ends.

Open problem
Valid AnagrameasyLeetCode #242 · O(n) time

Compare Counter(s1) == Counter(s2), or sort both strings and compare.

Open problem
Longest Common PrefixeasyLeetCode #14 · O(n log n) time

Sort the list of strings; the common prefix can only be the shared prefix between the first and last strings after sorting.

Open problem
String CompressionmediumLeetCode #443 · O(n) time, O(1) extra space

Use two pointers to walk the character array in place, counting consecutive repeats and writing char+count back into the same array; return the new length.

Open problem
Reverse Words in a StringmediumLeetCode #151 · O(n) time

Split on whitespace (which also collapses repeated spaces), reverse the list of words, and join with a single space.

Open problem

Concept checks

Q01

How do you reverse a string idiomatically in Python?

Q02

What's the difference between f-strings, .format(), and % formatting?

Q03

Why is repeatedly doing s += 'a' in a loop inefficient?

Q04

What's the difference between .strip(), .lstrip(), and .rstrip()?