Skip to content

Strings & String Formatting Interview Questions & Answers

15 questions Updated 2026-06-18 Share:

Python interview questions on f-strings vs .format vs %, str vs bytes, common string methods, join vs +=, raw strings, and the format spec mini-language.

Read the in-depth guidePython Strings and Formatting Explained — f-strings, str vs bytes, and join(opens in new tab)
15 of 15

All three interpolate values into strings. % is the oldest C-style syntax. str.format() uses {} placeholders and is more flexible. f-strings (Python 3.6+) embed expressions inline and are the fastest and most readable.

name, n = "Ada", 3
"Hi %s, %d items" % (name, n)        # old % style
"Hi {}, {} items".format(name, n)    # str.format
f"Hi {name}, {n} items"              # f-string — preferred
f"{n * 2 = }"                        # "n * 2 = 6"  — self-documenting

Rule of thumb: prefer f-strings for new code — they evaluate expressions directly and avoid the argument-ordering errors of % and .format().

A str is a sequence of Unicode code points (text); bytes is a sequence of raw bytes (0-255). You convert between them with encode (str -> bytes) and decode (bytes -> str), specifying an encoding like UTF-8.

s = "café"
b = s.encode("utf-8")    # b'caf\xc3\xa9'  — 5 bytes (é is 2)
b.decode("utf-8")        # "café"  — back to text
len(s), len(b)           # (4, 5)
s + b                    # TypeError — can't mix str and bytes

Why it matters: files and network sockets deal in bytes, your program logic in str. Rule of thumb: decode bytes to str as early as possible and encode back to bytes only at the I/O boundary.

split breaks a string into a list on a separator; strip removes leading/trailing whitespace (or given characters); join glues an iterable of strings together with a separator. All return new strings since str is immutable.

"  a,b,c  ".strip()            # "a,b,c"
"a,b,c".split(",")            # ["a", "b", "c"]
",".join(["a", "b", "c"])     # "a,b,c"
"Hello".lower(), "Hi".upper() # ("hello", "HI")
"hello".replace("l", "L")     # "heLLo"

Rule of thumb: sep.join(list) is the inverse of text.split(sep), and chaining these covers most everyday text wrangling.

Strings are immutable, so each += creates a brand-new string and copies everything so far — turning a loop into O(n^2) work and lots of garbage. join allocates the result once, making it O(n).

# Slow — new string every iteration
result = ""
for word in words:
    result += word

# Fast — single allocation
result = "".join(words)

Rule of thumb: collect pieces in a list (or generator) and call "".join(...) — it's the Python equivalent of a StringBuilder.

A raw string (r"...") tells Python not to process backslash escapes, so \n, \t, etc. stay as literal backslash-plus-character. They're ideal for regular expressions and Windows file paths.

print("a\tb")    # a    b   — \t is a tab
print(r"a\tb")   # a\tb     — backslash kept literally

import re
re.findall(r"\d+", "x12y3")   # ['12', '3']  — no double-backslashing
path = r"C:\Users\name"       # backslashes stay intact

Rule of thumb: reach for r"..." whenever your string is full of backslashes — it avoids the noise and bugs of escaping every one.

Inside {} (or after : in format), a format spec controls alignment, width, and precision: {value:[fill][align][width][,][.precision][type]}. It works in f-strings and str.format alike.

f"{42:5}"        # "   42"    — width 5, right-aligned (default for numbers)
f"{'hi':<5}|"    # "hi   |"   — left-align in width 5
f"{'hi':^5}|"    # " hi  |"   — center
f"{42:05}"       # "00042"    — zero-padded
f"{3.14159:.2f}" # "3.14"     — 2 decimal places
f"{1234567:,}"   # "1,234,567"  — thousands separator
f"{0.25:.1%}"    # "25.0%"    — percentage

Rule of thumb: .2f controls decimals, a number sets width, and </>/^ set alignment — combine them for clean tabular output.

A str object cannot be changed in place — every "modification" returns a new string. You can't assign to an index. This enables string hashing (so strings can be dict keys/set members) and safe sharing.

s = "hello"
s[0] = "H"          # TypeError — item assignment not allowed
s = "H" + s[1:]     # "Hello" — build a new string instead
{"key": 1}          # works because str is hashable/immutable

Rule of thumb: treat strings as read-only values; to "edit" one, create a new string (or work in a list of chars and "".join at the end).

Beyond lower/upper, there's title, capitalize, casefold (aggressive lowercasing for caseless matching), and a family of is* predicates: isdigit, isalpha, isalnum, isspace, isidentifier.

"hello world".title()    # "Hello World"
"ß".casefold()           # "ss" — better than lower() for matching
"123".isdigit()          # True
"abc1".isalnum()         # True
"  ".isspace()           # True

Rule of thumb: use casefold() for case-insensitive comparison and the is* methods for quick input validation.

Unicode lets the same glyph be encoded differently — e.g. "é" as one code point or as "e" + combining accent. They look identical but differ byte-for-byte. Use unicodedata.normalize to canonicalize before comparing.

import unicodedata
a = "café"                       # precomposed é
b = "café"                 # e + combining accent
a == b                           # False!
unicodedata.normalize("NFC", a) == unicodedata.normalize("NFC", b)  # True

Rule of thumb: normalize user/text input to a canonical form (NFC) before comparing, deduping, or using strings as keys.

Slicing uses s[start:stop:step] and returns a new string. Omitted bounds default to the ends; a negative step walks backwards (the classic reverse trick).

s = "abcdef"
s[1:4]     # "bcd"
s[:3]      # "abc"
s[-2:]     # "ef"
s[::2]     # "ace"  — every other char
s[::-1]    # "fedcba"  — reversed

Rule of thumb: s[::-1] reverses, s[a:b] never errors on out-of-range bounds, and slices always copy (cheap for strings).

Conversion flags pick which dunder to call: !r uses repr(), !s uses str(), !a uses ascii(). The = specifier (3.8+) prints both the expression text and its value — great for debugging.

name = "Ada"
f"{name!r}"      # "'Ada'"   — repr, keeps the quotes
f"{name!s}"      # "Ada"     — str
x = 5
f"{x = }"        # "x = 5"   — self-documenting debug
f"{x=:.2f}"      # "x=5.00"  — combine with a format spec

Rule of thumb: use !r in logs/errors to show quoting clearly, and f"{var=}" for quick print-debugging.

Triple quotes ("""...""") span multiple lines and preserve newlines — used for docstrings and block text. Adjacent string literals are joined at compile time (implicit concatenation), handy for splitting long literals.

doc = """line 1
line 2"""              # contains a real newline

msg = ("part one "     # implicit concatenation —
       "part two")      # becomes "part one part two"

# gotcha: a forgotten comma in a list silently concatenates!
items = ["a" "b", "c"]  # ['ab', 'c']

Rule of thumb: triple quotes for multi-line/docstrings; implicit concat for readable long literals — but watch for missing commas in lists.

Build a translation table with str.maketrans and apply it with translate — one pass for many character mappings or deletions, faster and cleaner than chained replace calls.

table = str.maketrans("aeiou", "AEIOU")
"hello world".translate(table)        # "hEllO wOrld"

drop = str.maketrans("", "", "!?.")   # third arg = chars to delete
"h!e?l.lo".translate(drop)            # "hello"

Rule of thumb: for multiple single-char substitutions/removals, translate beats stacking .replace(); for substrings or patterns, use replace or re.sub.

partition(sep) splits on the first occurrence and always returns a 3-tuple (before, sep, after) — even when the separator is missing (then sep and after are empty). This avoids the unpacking errors split can cause.

"key=value=x".split("=", 1)   # ['key', 'value=x']
"key=value".partition("=")    # ('key', '=', 'value')
"novalue".partition("=")      # ('novalue', '', '')  — safe, no error
"a.b.c".rpartition(".")       # ('a.b', '.', 'c')  — from the right

Rule of thumb: use partition/rpartition for clean "split once into head/sep/tail" with guaranteed three parts; use split when you want a variable-length list.

More ways to practice

The self-quiz is live. Join our channel for updates, new content & tech tips.

Join our WhatsApp Channel