Python Lesson 3: Strings & String Methods (Indexing, Slicing, F-Strings, split/join/strip)
Learn how to work with text in Python. This lesson covers string indexing and slicing (with diagrams), f-strings for clean formatting, and the essential split, join, and strip methods for cleaning and reshaping text. We also cover common mistakes and why f-strings beat plus signs for concatenation. #python #pythontutorial #stringmethods #learntocode #coding
Introduction
Every technical interview eventually touches strings. Whether you're reversing words, validating a password, parsing a log line, or checking if two words are anagrams, the underlying skill being tested is the same: can you manipulate text precisely, without off-by-one errors?
Strings look simple because we read text every day without thinking about it. But in code, a string is a sequence with positions, boundaries, and rules ā and most beginner bugs (and a surprising number of interview mistakes) come from misunderstanding those rules. Interviewers use string problems specifically because they reveal whether you understand indexing, slicing boundaries, and how to reshape text efficiently, without needing you to know an obscure algorithm first.
This lesson covers the five tools that make up 90% of everyday string work in Python: indexing, slicing, f-strings, split/join, and strip. Once these are second nature, you'll read and write string-processing code much faster, and you'll stop tripping over the two most common bugs in the entire language.
Problem Overview
There isn't a single "problem" to solve here ā this is a foundations lesson. The goal is to answer five practical questions you'll hit constantly when working with text in Python:
- How do I get a single character out of a string?
- How do I get a range of characters out of a string?
- How do I build a formatted sentence out of variables, cleanly?
- How do I break a string into pieces, and glue pieces back into a string?
- How do I remove accidental whitespace from user input?
Understanding these mechanics is what lets you later solve real problems ā reversing a string, checking palindromes, parsing CSV lines, cleaning form input ā without having to think hard about the mechanics themselves.
Example
word = "apple"
word[0] # 'a'
word[-1] # 'e'
word[1:4] # 'ppl'
name = "Sam"
age = 9
print(f"{name} is {age} years old.") # Sam is 9 years old.
sentence = "I like cake"
words = sentence.split() # ['I', 'like', 'cake']
rejoined = " ".join(words) # 'I like cake'
messy = " hello "
messy.strip() # 'hello'
Walking through the outputs:
word[0]returns'a'because indexing starts at position 0, not 1.word[-1]returns'e'because negative indices count backward from the end, and-1always means "the last character."word[1:4]returns'ppl'ā it starts at position 1 and stops before position 4, so the character at index 4 ('e') is excluded.- The f-string swaps
{name}and{age}for their real values automatically, including converting the integer9to text. split()breaks the sentence into a list at every space.join()does the reverse, gluing the list back together using the string you call it on as the "glue."strip()removes only the leading and trailing spaces, leaving the interior untouched.
Intuition
Before memorizing syntax, it helps to build a mental model. Think of a string as a strip of tape with each character sitting in a numbered slot, starting at slot 0. That single fact ā counting starts at zero ā is responsible for more beginner bugs than anything else in this lesson, so it's worth internalizing early.
Once you accept that characters have seat numbers, two operations naturally follow:
- Indexing answers "give me the letter at seat N."
- Slicing answers "give me everything between seat N and seat M."
The tricky part of slicing isn't the starting position ā it's that the ending position is a boundary, not an inclusion. Picture scissors cutting right before the ending index. Everything up to that cut is included; the character sitting exactly at the ending index is left out. This is why word[1:4] on "apple" gives you "ppl" and not "pple".
Once you can extract and slice text, the next natural need is combining text with variables ā that's what f-strings solve. Then, once you're working with real-world data (sentences, CSV rows, log lines), you need to break text apart (split) and put it back together (join) ā and clean up sloppy input (strip). Each tool exists to solve a specific, recurring pain point; none of it is arbitrary.
Brute Force Solution
There isn't really a "brute force vs optimal" split for basic string operations ā indexing and slicing are already O(1) and O(k) respectively, which is as good as it gets. But it's worth contrasting the naive way developers do string formatting and combination before they learn the idiomatic tools, versus the modern approach.
Naive approach ā manual concatenation with +:
name = "Sam"
age = 9
message = "Name: " + name + ", Age: " + str(age)
- Idea: Glue substrings together one
+at a time. - Algorithm: Convert every non-string value with
str(), then chain everything with+. - Advantages: Works in every version of Python, no new syntax to learn.
- Disadvantages: Gets cluttered fast with multiple variables, easy to forget a
str()conversion (which throws aTypeError), and harder to read than the sentence it's building. - Time complexity: O(n) per concatenation, and each
+on strings creates a new string object, so chaining many concatenations in a loop degrades toward O(n²) for n pieces. - Space complexity: O(n) for the final string, but O(n) intermediate garbage is created along the way.
Optimal Solution
The optimal approach is to use each tool for the job it was designed for:
| Task | Tool | Why it's optimal |
|---|---|---|
| Get one character | s[i] |
Direct O(1) lookup by position |
| Get a range | s[i:j] |
Single O(k) operation, no manual loop |
| Build formatted text | f-string | Handles type conversion, reads like English |
| Break text into pieces | s.split(sep) |
One call replaces a manual loop over characters |
| Glue pieces together | sep.join(list) |
O(n) total, avoids the O(n²) trap of repeated + |
| Remove edge whitespace | s.strip() |
Purpose-built, doesn't touch the middle of the string |
The key insight for join specifically: because + concatenation creates a new string object every time, joining pieces one at a time in a loop is quadratic. str.join() is implemented to compute the total length once and build the result in a single pass, which is why it's the idiomatic (and faster) choice whenever you're combining more than a couple of strings.
For slicing, it helps to visualize the boundary explicitly:
a p p l e
0 1 2 3 4
^ ^
start stop (excluded)
word[1:4] -> "ppl"
Python Code
def demo_string_tools() -> None:
"""Show indexing, slicing, f-strings, split, join, and strip in action."""
word = "apple"
# Indexing: single character by position
first_letter = word[0]
last_letter = word[-1]
# Slicing: a range of characters, stop index excluded
middle_slice = word[1:4]
# F-strings: formatted output with variables
name, age = "Sam", 9
greeting = f"{name} is {age} years old."
# Split / join: break apart and glue back together
sentence = "I like cake"
words = sentence.split()
rejoined = " ".join(words)
# Strip: trim only leading/trailing whitespace
messy_input = " hello "
cleaned = messy_input.strip()
print(first_letter, last_letter, middle_slice)
print(greeting)
print(words, "->", rejoined)
print(repr(cleaned))
if __name__ == "__main__":
demo_string_tools()
Code Walkthrough
word[0]andword[-1]ā direct index lookups. Zero is the first slot;-1wraps around to the last slot regardless of the string's length, so it works even if you don't know how long the string is.word[1:4]ā a slice expressionword[start:stop]. It copies characters fromstartup to but not includingstop.f"{name} is {age} years old."ā thefprefix tells Python to evaluate anything inside{}and insert its string representation. No manualstr()conversion needed forage.sentence.split()ā called with no arguments, it splits on any whitespace and discards empty results, returning a list of words." ".join(words)ā the string before.joinis the separator; it's inserted between each element of the list you pass in.messy_input.strip()ā removes whitespace from both ends only. No arguments means "strip whitespace"; you can pass a string of characters to strip something else instead.
Dry Run
Trace word = "apple", word[1:4]:
| Position | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| Character | a | p | p | l | e |
- Start at index 1 ā character
p. - Include index 2 ā character
p. - Include index 3 ā character
l. - Stop before index 4 ā character
eexcluded. - Result:
"ppl".
Trace "I like cake".split() then " ".join(...):
split()scans for whitespace: finds a space after"I"and after"like".- Produces
["I", "like", "cake"]. " ".join([...])inserts" "between each pair of adjacent elements:"I" + " " + "like" + " " + "cake".- Result:
"I like cake"ā identical to the original because the separator matched.
Complexity Analysis
- Indexing (
s[i]): O(1) time, O(1) space ā Python strings are stored contiguously, so any position is a direct memory offset. - Slicing (
s[i:j]): O(k) time and space, wherekis the length of the slice, because a new string object is created. - F-strings: O(n) time where n is the total output length; space is O(n) for the resulting string.
split(): O(n) time to scan the whole string once; O(n) space for the resulting list of substrings.join(): O(n) time total across all pieces (single pass, pre-computed length), O(n) space for the result ā this is why it beats repeated+concatenation, which is O(n²) in the worst case.strip(): O(n) time in the worst case (checking from both ends inward), O(k) space for the trimmed result.
These are correct because none of these operations do nested work over the string ā each makes a single linear pass (or a constant-time jump, for indexing), which is the best possible complexity for operations that must touch every relevant character at least once.
Alternative Solutions
str.format()instead of f-strings:"{} is {} years old".format(name, age)ā works, and is common in older codebases, but is more verbose and separates the placeholders from their values, making longer strings harder to read.%formatting:"%s is %d years old" % (name, age)ā the oldest style, still seen in logging code, but requires matching format specifiers to types and is easy to get wrong.- Manual loops instead of
split/join: you could iterate character by character and build a list yourself, but you'd be reimplementing what Python already does correctly and efficiently ā there's no advantage, only more code to maintain.
F-strings are the modern default recommendation for new code; str.format() and % are worth recognizing when reading existing code, not for writing new code.
Edge Cases
- Empty string:
""[0]raisesIndexErrorā always check length before indexing into unknown input. - Out-of-range index:
word[10]on a 5-character string raisesIndexError, butword[10:20](a slice) simply returns""ā slicing never raises for out-of-range bounds. - Slicing with negative numbers:
word[-3:-1]counts from the end on both sides ā worth tracing on paper the first few times. - Splitting a string with no matching separator:
"hello".split(",")returns["hello"], the whole string as a single-element list. - Strings with only whitespace:
" ".strip()returns"", not an error. - Joining an empty list:
" ".join([])returns"". - Multiple consecutive spaces:
"a b".split()(no argument) correctly collapses runs of whitespace, but"a b".split(" ")(explicit space) does not, and produces["a", "", "b"]ā a subtle but common gotcha.
Common Mistakes
- Assuming indexing starts at 1. It starts at 0 ā
word[1]gets the second character, not the first. - Forgetting the
fbefore the quotes. Without it,"{name}"prints literally as the text{name}, not the variable's value. - Expecting a slice's stop index to be included.
word[1:4]excludes index 4 ā a frequent source of off-by-one bugs. - Expecting
strip()to clean whitespace in the middle of a string. It only trims the edges;"a b".strip()still has the double space in the middle. - Concatenating many values with
+instead of an f-string orjoin. It's error-prone (forgettingstr()on numbers) and doesn't scale cleanly. - Passing
" "explicitly tosplitwhen you meant "split on any whitespace."split()andsplit(" ")behave differently on strings with multiple consecutive spaces. - Trying to modify a string in place. Strings are immutable in Python ā
word[0] = "A"raisesTypeError. You always build a new string instead.
Interview Questions
- "What's the difference between
s.split()ands.split(' ')?" - "Why is string concatenation with
+in a loop considered inefficient? What would you use instead?" - "How would you reverse a string using slicing?" (
word[::-1]) - "What happens if you slice past the end of a string? Does it raise an error?"
- "Why are Python strings immutable, and what does that mean for performance when building large strings?"
- "How would you check if a string is a palindrome using only indexing and slicing?"
Similar Problems
- Reverse a String (LeetCode 344) ā directly tests slicing intuition (
s[::-1]). - Valid Palindrome (LeetCode 125) ā combines cleaning input (similar to
strip/filtering) with indexing from both ends. - Valid Anagram (LeetCode 242) ā often solved by sorting or counting characters, but string indexing is the foundation.
- Reverse Words in a String (LeetCode 151) ā a direct real-world use of
splitandjointogether. - First Unique Character in a String (LeetCode 387) ā relies on iterating and indexing efficiently.
Key Takeaways
- Indexing retrieves a single character; slicing retrieves a range, and the range's ending index is always excluded.
- Negative indices count from the end, with
-1always meaning the last character. - F-strings are the clean, modern way to combine variables and text ā no manual
str()conversions required. split()andjoin()are inverses of each other: one breaks text apart, the other glues it back together.strip()only ever touches the leading and trailing edges of a string, never the middle.
Watch the Video
If you'd like to see these five tools demonstrated with diagrams and live code, watch the full lesson here: {LINK}
About the Series
This lesson is part of the Daily Python LeetCode series ā short, focused lessons that build up the exact Python fundamentals and problem-solving patterns you need for technical interviews. Each lesson takes a core concept, explains the intuition behind it, and connects it to real interview questions, so you're not just memorizing syntax but actually understanding why it works.
Call To Action
If this helped clarify how Python strings really work, subscribe on YouTube so you don't miss the next lesson, and subscribe to the Substack newsletter for write-ups like this one delivered straight to your inbox. Drop a comment with a string operation you'd like explained next, and share this with anyone starting their Python journey.
The solution
name = "Sam"
age = 9
message = f"{name} is {age} years old"
print(message)Ready to try it yourself? Solve General problems with instant feedback in the practice sandbox.
Practice now ā


