The Python str.startswith() method returns True when a string begins with the given prefix and False otherwise. Reading log files is a good example, where we keep the lines that start with ERROR and skip the lines that start with #.
line = "ERROR disk is full"
is_error = line.startswith("ERROR") # True
is_info = line.startswith("INFO") # False
We filter a small log with a tuple of prefixes, skip a date field with start, ignore case, and compare startswith() with removeprefix() and slicing. The code was checked on Python 3.14.
1. Syntax and Return Value
Like its sibling endswith(), the method takes a prefix and two optional indexes, and it is case-sensitive.
str.startswith(prefix[, start[, end]]) -> bool
- prefix is a string or a tuple of strings.
- start is the index where the check begins (default 0).
- end is the index where the check stops; the character at end is not included.
The return value is a bool. The method returns either True, when the string (or the slice str[start:end]) begins with the prefix or with any prefix in the tuple, or False otherwise.
2. Filtering Log Lines with a Tuple of Prefixes
A tuple lets one call match several prefixes. In a log file, that picks out every warning and error line in a single list comprehension.
log = ["INFO started", "WARN low memory", "ERROR disk is full", "INFO stopped"]
problems = [line for line in log if line.startswith(("WARN", "ERROR"))]
# ['WARN low memory', 'ERROR disk is full']
As with endswith(), a list of prefixes must be wrapped with tuple(prefixes), or the call raises TypeError.
3. Checking from a Position
When a line has a fixed-width field in front, such as a date, start moves the check past it. Because end is exclusive, a range that is too short cuts the prefix and the result becomes False.
line = "2026-10-09 ERROR disk is full"
at_11 = line.startswith("ERROR", 11) # True
short = line.startswith("ERROR", 11, 14) # False, the slice is "ERR"
4. Case-Insensitive Checks and Comment Lines
User input and file content rarely have a fixed case or fixed indentation. We normalize the string first with lower() or lstrip(), and call startswith() on the result.
asks_help = "Help me".lower().startswith("help") # True
config = ["# timeout in seconds", "timeout=30", "", " # retries", "retries=3"]
settings =
# ['timeout=30', 'retries=3']
5. startswith() vs removeprefix() vs Slicing
These approaches all look at the beginning of a string, but they return different things. We use startswith() for a yes-or-no answer and removeprefix() (Python 3.9+) when we need the rest of the string.
url = "https://example.com"
secure = url.startswith("https://") # True
host = url.removeprefix("https://") # 'example.com'
same = url[:8] == "https://" # True, but the 8 must match the prefix length
| Approach | Returns | Watch out for |
|---|---|---|
| s.startswith(p) | True or False | Case-sensitive |
| s.removeprefix(p) | The string without the prefix | Returns the string unchanged when the prefix is missing |
| s[:n] == p | True or False | A wrong n gives a silent bug |
| re.match(pattern, s) | A match object or None | Only worth it for patterns, not fixed text |
6. Routing Chat Commands by Prefix
A chat bot treats messages that start with a slash as commands. The startswith() check routes the message, and partition() splits the command name from its arguments in one call.
def parse(message):
if not message.startswith("/"):
return None, message
command, _, args = message[1:].partition(" ")
return command.lower(), args
r1 = parse("/remind 10m tea") # ('remind', '10m tea')
r2 = parse("/HELP") # ('help', '')
r3 = parse("hello there") # (None, 'hello there')
The same pattern works for any line-based protocol, such as reading commands from a file or a socket, where a prefix decides how the rest of the line is handled.
7. Prefix Check Questions
A plain startswith() call stops being enough once the prefix is a pattern or the data is bytes.
7.1. Does startswith() work with bytes?
Yes. A bytes object has its own startswith(), so a file signature check reads data.startswith(b”%PDF”). The prefix must also be bytes.
7.2. How do we check whether a string starts with a number?
We test the first character with isdigit(). The slice s[:1] returns an empty string instead of raising IndexError when s is empty.
starts_num = "3 apples"[:1].isdigit() # True
empty_ok = ""[:1].isdigit() # False
7.3. When should we use a regular expression instead?
When the prefix is a pattern rather than fixed text, such as “two letters followed by a dash”, re.match(r”[A-Z]{2}-“, s) is the right tool. For fixed text, startswith() is shorter and faster.
8. Conclusion
For log filters and config readers, startswith() with a tuple of prefixes is shorter and safer than slicing. We normalize the line with lower() or lstrip() before the check, and we switch to removeprefix() when we need the rest of the text.
9. References
Happy Learning !!