Substring Frequency in Python: Occurrence Counting with count()

The str.count(sub[, start[, end]]) method returns the integer number of non-overlapping occurrences of substring sub in the range [start:end].

The Non-Overlapping Invariant: Python scans strictly left-to-right and consumes matched characters, meaning overlapping patterns are not counted twice. For example, "aaaa".count("aa") returns 2, not 3. If the target substring is absent, count() returns 0 without raising an exception.

1. Method Signature & Parameters 🔝

The standard CPython signature for str.count() is:

str.count(sub, start=0, end=sys.maxsize, /)
Parameter Type Default Description
sub str Required The target substring to search for. Must be a string.
start int 0 Optional. 0-based beginning index (inclusive). Supports negative indices counting backward from the end.
end int End of string Optional. 0-based termination index (exclusive). Supports negative indices.
Return Value: Returns a non-negative integer (int ≥ 0). If sub does not appear within the evaluated range, 0 is returned.

2. Basic Occurrence Counting 🔝

Counting occurrences of single characters or multi-character phrases:

Example: Standard Counting
text = "Welcome to plus2net.com. Enjoy learning Python at plus2net!"

# Count substring occurrences
site_count = text.count("plus2net")
letter_o_count = text.count("o")

print("Occurrences of 'plus2net':", site_count)
print("Occurrences of 'o':       ", letter_o_count)

Expected Output:

Occurrences of 'plus2net': 2
Occurrences of 'o':        5

Notice that count() is strictly case-sensitive: searching for "python" in "Python" yields 0.

3. The Non-Overlapping Mechanic 🔝

The most important algorithmic rule of str.count() is that matches never overlap. Once Python matches an occurrence, it advances its internal cursor to the character immediately following the match:

Example: Overlapping vs. Non-Overlapping Substrings
sequence = "aaaa"

# Non-overlapping count: matches indices [0:2] and [2:4]
count_aa = sequence.count("aa")
print("'aaaa'.count('aa'):", count_aa)  # Output: 2

# 'banana' contains two overlapping 'ana' sequences: index 1 and index 3
fruit = "banana"
print("'banana'.count('ana'):", fruit.count("ana"))  # Output: 1

Expected Output:

'aaaa'.count('aa'): 2
'banana'.count('ana'): 1
How to Count Overlapping Matches: If your algorithm requires overlapping occurrences (e.g. biological DNA k-mer frequencies), use a regular expression lookahead assertion:
import re; len(re.findall(r'(?=(ana))', 'banana')) → 2

4. Slicing with start and end Offsets 🔝

You can constrain the count to a specific substring window without creating an intermediate slice string:

Example: Bounded Slicing
phrase = "ab_ab_ab_ab"

# Entire string
print("Full count:        ", phrase.count("ab"))

# From index 3 to the end
print("From index 3:      ", phrase.count("ab", 3))

# Range [3:8]
print("Range [3:8]:       ", phrase.count("ab", 3, 8))

# Negative indices: last 5 characters
print("Last 5 characters: ", phrase.count("ab", -5))

Expected Output:

Full count:         4
From index 3:       3
Range [3:8]:        2
Last 5 characters:  2

Passing phrase.count("ab", 3, 8) is functionally equivalent to phrase[3:8].count("ab"), but avoids allocating memory for the intermediate substring slice.

5. The Empty String Boundary Nuance 🔝

An interesting mathematical edge case in CPython is counting the empty string "":

Empty String Occurrence Formula
word = "code"
print("Length:       ", len(word))
print("count(''):    ", word.count(""))
print("''.count(''): ", "".count(""))

Expected Output:

Length:        4
count(''):     5
''.count(''):  1

For any string of length N, the empty string occurs at N + 1 positions (before each character, between every adjacent pair, and after the final character).

6. Real-World Engineering Recipes 🔝

Recipe 1: Bioinformatics GC-Content Ratio Calculator

Calculating Guanine-Cytosine content in DNA genomic sequences:

def calculate_gc_content(dna_sequence: str) -> float:
    seq_upper = dna_sequence.upper()
    total_bases = len(seq_upper)
    if total_bases == 0:
        return 0.0
    g_count = seq_upper.count("G")
    c_count = seq_upper.count("C")
    return ((g_count + c_count) / total_bases) * 100.0

gene = "ATGCGATCGATCGATCGATCGATCGATCGATAGCTAGCTAGCTA"
gc = calculate_gc_content(gene)
print(f"GC-Content: {gc:.2f}%")

Expected Output:

GC-Content: 47.73%
Recipe 2: Log File Error Severity Auditor

Scanning server audit logs for warning and error occurrence thresholds:

def audit_log_severities(log_content: str) -> dict[str, int]:
    return {
        "CRITICAL": log_content.count("CRITICAL:"),
        "ERROR": log_content.count("ERROR:"),
        "WARNING": log_content.count("WARNING:"),
        "INFO": log_content.count("INFO:")
    }

server_logs = """
2026-10-13 INFO: Service started
2026-10-13 WARNING: High memory usage
2026-10-13 ERROR: DB Connection timed out
2026-10-13 ERROR: Failed to retry DB connection
2026-10-13 CRITICAL: Out of memory, process terminated
"""
stats = audit_log_severities(server_logs)
print("Audit summary:", stats)

Expected Output:

Audit summary: {'CRITICAL': 1, 'ERROR': 2, 'WARNING': 1, 'INFO': 1}
Recipe 3: Syntactic Bracket Balance Validator

Rapidly checking whether mathematical or code expressions have matching opening and closing delimiters:

def check_delimiter_parity(expr: str) -> bool:
    # Quick count heuristic: opening count must equal closing count
    parens_ok = expr.count("(") == expr.count(")")
    brackets_ok = expr.count("[") == expr.count("]")
    braces_ok = expr.count("{") == expr.count("}")
    return parens_ok and brackets_ok and braces_ok

print("Valid expr:  ", check_delimiter_parity("((a + b) * [c - d])"))
print("Invalid expr:", check_delimiter_parity("((a + b) * [c - d]"))

Expected Output:

Valid expr:   True
Invalid expr: False
Recipe 4: Counting Overlapping Substrings with Regex

When overlapping occurrences must be counted, regular expression lookahead captures every occurrence:

import re

def count_overlapping(text: str, pattern: str) -> int:
    # (?=(...)) lookahead assertion matches without consuming characters
    escaped = re.escape(pattern)
    return len(re.findall(rf"(?=({escaped}))", text))

sample = "aaaa"
print("str.count('aa'):           ", sample.count("aa"))
print("count_overlapping('aa'):   ", count_overlapping(sample, "aa"))

Expected Output:

str.count('aa'):            2
count_overlapping('aa'):    3

7. Common Pitfalls & Edge Cases 🔝

Pitfall 1: Case Sensitivity Trap

str.count() performs exact character matching. It will not find uppercase variations. If case-insensitive frequency is needed, normalize first: text.lower().count("python").

Pitfall 2: Overlapping Matching Surprise

Relying on str.count() to count occurrences in repetitive motifs (like DNA codons or sliding window patterns) will undercount if occurrences share common characters. Use a sliding window or regex lookaheads when overlaps are significant.

8. Production Implementation & Self-Checking Assertions 🔝

Below is a production-grade utility class, SubstringCounter, demonstrating GC calculation, log counting, bracket balance validation, and automated unit assertions:

Production SubstringCounter Class with Unit Assertions
class SubstringCounter:
    @staticmethod
    def count_case_insensitive(text: str, sub: str) -> int:
        """Counts substring occurrences ignoring case."""
        return text.lower().count(sub.lower())

    @staticmethod
    def gc_percentage(dna: str) -> float:
        """Computes GC content percentage."""
        s = dna.upper()
        if not s:
            return 0.0
        return ((s.count("G") + s.count("C")) / len(s)) * 100.0

    @staticmethod
    def is_balanced_parens(expr: str) -> bool:
        """Checks parentheses parity via count."""
        return expr.count("(") == expr.count(")")


# ==========================================
# SELF-CHECKING UNIT ASSERTIONS
# ==========================================
counter = SubstringCounter()

# 1. Non-overlapping counting
assert "aaaa".count("aa") == 2
assert "banana".count("ana") == 1

# 2. Slice bounds [start:end]
assert "abc_abc_abc".count("abc", 4) == 2
assert "abc_abc_abc".count("abc", 4, 7) == 1

# 3. Empty string occurrence rule (N + 1)
assert "hello".count("") == 6
assert "".count("") == 1

# 4. Case-insensitive counting
assert counter.count_case_insensitive("Python python PYTHON", "python") == 3

# 5. GC content calculation
assert round(counter.gc_percentage("GCGCAT"), 2) == 66.67
assert counter.gc_percentage("") == 0.0

# 6. Parentheses parity
assert counter.is_balanced_parens("(a + (b * c))") is True
assert counter.is_balanced_parens("(a + b") is False

print("Case-insensitive count: ", counter.count_case_insensitive("Dev dev DEV", "dev"))
print("GC percentage:          ", f"{counter.gc_percentage('ATGC'):.1f}%")
print("All str.count() unit assertions passed successfully!")

Practice in Google Colab & GitHub 🔝

Open in Google Colab View on GitHub

Run all substring occurrence counting examples interactively, contrast non-overlapping count() with regex lookahead, calculate genomic GC content, and audit log files in Google Colab.




Subscribe to our YouTube Channel here



plus2net.com







Python Video Tutorials
Python SQLite Video Tutorials
Python MySQL Video Tutorials
Python Tkinter Video Tutorials
✖
We use cookies to improve your browsing experience. . Learn more
HTML MySQL PHP JavaScript ASP Photoshop Articles Contact us
© 2000-2026 plus2net.com All rights reserved worldwide Privacy Policy Disclaimer