Perfect 💪 — Let’s begin Day 1 of your “Deep Python Strings in 3 Days” journey.
We’ll start with internals, memory behavior, string methods, and 8 fundamental exercises that build strong intuition.
🩵 DAY 1 – Python String Internals & Core Operations
🎯 Goal
Understand what actually happens inside Python when you work with strings — memory, immutability, slicing, interning — and then implement 8 foundation-level string exercises.
🧠 PART 1 – String Internals (Deep Dive)
🔹 1.1 What is a String in Python?
- Strings in Python are immutable sequences of Unicode characters.
- Each string is stored internally as a
PyUnicodeObject(in CPython). - It holds:
- The length
- The hash value
- A pointer to character data
- Encoding details (1/2/4-byte per char depending on the widest codepoint)
Think of strings as read-only arrays of Unicode code points.
🔹 1.2 Immutability in Action
s = "hello"
print(id(s))
s += " world"
print(id(s)) # new object ID → new memory allocation
✅ Explanation:
- Each operation like concatenation or replace creates a new string object.
- That’s why
"".join(list_of_strings)is faster than repeated+=.
🔹 1.3 String Interning
Python interns some strings automatically for performance.
a = "python"
b = "python"
print(a is b) # True → both point to same memory
x = "python_rocks!"
y = "python_rocks!"
print(x is y) # False → not interned by default
import sys
y = sys.intern(y)
print(x is y) # True after manual interning
✅ Interning saves memory and speeds up equality checks, but never rely on it for logic (== is correct, not is).
🔹 1.4 String Slicing (copy behavior)
s = "abcdef"
slice1 = s[1:4] # "bcd"
print(slice1)
print(id(s), id(slice1)) # different objects
✅ Slicing always creates a new string, but Python optimizes small slices (copy on write style).
🔹 1.5 Encoding and Decoding
s = "नमस्ते"
b = s.encode("utf-8")
print(b) # bytes form
print(b.decode("utf-8")) # back to str
- Python’s
stris Unicode, not bytes. - Always
.encode()for file or network I/O.
🔹 1.6 Memory Efficiency Example
Bad ❌
result = ""
for i in range(10000):
result += "a"
Good ✅
result = "".join(["a" for i in range(10000)])
🧰 PART 2 – Core String Methods
Below are groups of related methods and what happens behind the scenes:
| Category | Examples | What Happens Internally |
|---|---|---|
| Case | upper(), lower(), capitalize() | Iterates all chars, creates a new copy |
| Whitespace | strip(), lstrip(), rstrip() | Scans from ends to remove characters |
| Search | find(), rfind(), startswith(), endswith() | Efficient C substring search |
| Replace | replace(), split(), join() | Single pass scanning; new string built |
| Validation | isalpha(), isdigit(), isalnum(), isspace() | Checks each character property |
| Formatting | format(), f"", % | Creates new formatted string |
💡 Tip: help(str) in REPL → see all available methods.
🧩 PART 3 – Hands-On Exercises
We’ll now implement 8 foundational problems.
Write and test these in day1_basics.py.
1️⃣ Is It a Vowel?
def is_vowel(ch: str) -> bool:
return len(ch) == 1 and ch.lower() in "aeiou"
print(is_vowel("a"))
print(is_vowel("b"))
2️⃣ Reverse String
def reverse_string(s: str) -> str:
return s[::-1]
print(reverse_string("hello"))
Behind the scenes:s[::-1] creates a new copy by traversing from end to start.
3️⃣ Remove Vowels from a String
def remove_vowels(s: str) -> str:
vowels = set("aeiouAEIOU")
return "".join(ch for ch in s if ch not in vowels)
print(remove_vowels("Python Rocks"))
4️⃣ Replace Spaces with Hyphens
def replace_spaces(s: str) -> str:
return s.replace(" ", "-")
print(replace_spaces("Deep Python Strings"))
5️⃣ Valid Anagram (LeetCode)
from collections import Counter
def is_anagram(s: str, t: str) -> bool:
return Counter(s) == Counter(t)
print(is_anagram("listen", "silent")) # ✅
print(is_anagram("rat", "car")) # ❌
6️⃣ Remove Adjacent Duplicates
def remove_adjacent_duplicates(s: str) -> str:
stack = []
for ch in s:
if stack and stack[-1] == ch:
stack.pop()
else:
stack.append(ch)
return "".join(stack)
print(remove_adjacent_duplicates("abbaca")) # "ca"
7️⃣ Pangram Checker
def is_pangram(s: str) -> bool:
letters = {ch.lower() for ch in s if ch.isalpha()}
return len(letters) == 26
print(is_pangram("The quick brown fox jumps over a lazy dog"))
8️⃣ Longest Common Prefix
def longest_common_prefix(strs):
if not strs:
return ""
prefix = strs[0]
for s in strs[1:]:
while not s.startswith(prefix):
prefix = prefix[:-1]
if not prefix:
return ""
return prefix
print(longest_common_prefix(["flower","flow","flight"])) # "fl"
🧩 Bonus Mini Challenge
👉 normalize_string(s)
Task:
Implement a function that:
- Removes non-alphanumeric characters
- Converts to lowercase
- Replaces spaces with underscores
def normalize_string(s: str) -> str:
return "".join(
ch.lower() if ch.isalnum() else "_" if ch == " " else ""
for ch in s
)
print(normalize_string("Hello World! 2025"))
# "hello_world_2025"
📘 Day 1 Summary
| Concept | Key Takeaways |
|---|---|
| Immutability | Every modification → new object |
| Interning | Same memory reused for small strings |
| join vs += | join is faster; precomputes size |
| Encoding | Unicode under the hood; use .encode() |
| Methods | Most are O(n), return new strings |
| Patterns learned | Stack-based cleanup, prefix comparison, Counter-based equality |
🧠 End-of-Day Reflection Questions
- Why is Python string immutable?
- What is interning and why does it exist?
- Why is
"".join(list)faster than concatenation in a loop? - What’s the complexity of
s.find(sub)? - How would you manually reverse a string without slicing?
Would you like me to now generate the interactive Jupyter Notebook version of Day 1 (with markdown explanations + runnable code cells) so you can run and practice directly?