| name | python-bio-control-flow |
| description | Write if/elif/for/while loops over DNA/RNA/protein strings: codon iteration, stop-codon/motif scanning, GC-content classification. Use when looping over sequences, extracting codons, or debugging an off-by-one loop. |
| tool_type | python |
| primary_tool | Python |
Control Flow for Bioinformatics
When to Use
- Iterating over a DNA/RNA/protein string codon-by-codon or residue-by-residue.
- Scanning a sequence or reading frame for a stop codon, start codon, or arbitrary motif.
- Classifying sequences (DNA vs RNA vs protein, GC-content bucket, purine/pyrimidine).
- Filtering a batch of reads/sequences by length, GC%, or composition using
if/elif/continue.
- Debugging an off-by-one codon slice, a
break that only exits one loop, or a while that never terminates.
Version Compatibility
Pure Python standard library only — Python ≥3.8 (walrus operator and f-strings used below need ≥3.8). No third-party packages required.
Prerequisites
- Comfortable with Python strings, slicing (
seq[i:i+3]), set, and dict.
- Know basic sequence concepts: codon = 3 nt, reading frame, GC content, purine (A/G) vs pyrimidine (C/T/U).
Goal: iterate a DNA sequence in complete, non-overlapping codons.
Approach: use range(0, len(dna) - 2, 3) (not range(len(dna))) so the loop never starts a codon it can't finish, and translate using a codon table with an explicit stop-codon break.
CODON_TABLE = {
'TTT': 'F', 'TTC': 'F', 'TTA': 'L', 'TTG': 'L',
'CTT': 'L', 'CTC': 'L', 'CTA': 'L', 'CTG': 'L',
'ATT': 'I', 'ATC': 'I', 'ATA': 'I', 'ATG': 'M',
'GTT': 'V', 'GTC': 'V', 'GTA': 'V', 'GTG': 'V',
'TCT': 'S', 'TCC': 'S', 'TCA': 'S', 'TCG': 'S',
'CCT': 'P', 'CCC': 'P', 'CCA': 'P', 'CCG': 'P',
'ACT': 'T', 'ACC': 'T', 'ACA': 'T', 'ACG': 'T',
'GCT': 'A', 'GCC': , : , : ,
: , : , : , : ,
: , : , : , : ,
: , : , : , : ,
: , : , : , : ,
: , : , : , : ,
: , : , : , : ,
: , : , : , : ,
: , : , : , : ,
}
() -> :
dna = dna.upper()
protein = []
i (, (dna) - , ):
codon = dna[i:i + ]
amino_acid = CODON_TABLE.get(codon, )
amino_acid == :
protein.append(amino_acid)
.join(protein)
translate() ==
Goal: find the first in-frame stop codon, and separately find every occurrence of an arbitrary motif (which may overlap and isn't frame-locked).
Approach: a frame-locked scan advances the index by 3 each step with while; a motif scan advances by 1 using str.find's start argument so overlapping hits aren't missed.
def first_stop_codon(dna: str, frame: int = 0) -> int | None:
"""Return the 0-based position of the first in-frame stop codon, or None if none found."""
stop_codons = {"TAA", "TAG", "TGA"}
pos = frame
while pos <= len(dna) - 3:
if dna[pos:pos + 3] in stop_codons:
return pos
pos += 3
return None
def find_motif_positions(seq: str, motif: str) -> list[int]:
"""Return every 0-based start position of motif in seq, including overlaps."""
positions = []
pos = seq.find(motif)
while pos != -1:
positions.append(pos)
pos = seq.find(motif, pos + 1)
return positions
assert first_stop_codon("ATGGCCGATCGATAGCCATAGTTAACG") == 15
assert find_motif_positions("ATGCGATGATCGATGCATG", "ATG") == [0, 6, 12, 16]
Goal: classify a sequence's identity and GC-content bucket, and validate that every character is a legal base — three related if/elif and for-else patterns bioinformatics code uses constantly.
Approach: subset tests (unique <= set("ATGC")) for identity; a sorted threshold table for GC bucketing; for...else to report "all valid" only when no break fired.
def detect_sequence_type(sequence: str) -> str:
"""Classify a sequence as DNA, RNA, protein, or Unknown from its alphabet."""
unique = set(sequence.upper())
if unique <= set("ATGC"):
return "DNA"
elif unique <= set("AUGC"):
return "RNA"
elif unique <= set("ACDEFGHIKLMNPQRSTVWY"):
return "Protein"
return "Unknown"
GC_CLASSES = [(30, "AT-rich"), (50, "Moderate"), (60, "High GC")]
def classify_gc(sequence: str) -> tuple[float, str]:
"""Return (gc_percent, label); label is the first bucket whose threshold isn't exceeded."""
s = sequence.upper()
gc = (s.count('G') + s.count('C')) / len(s) * 100
for threshold, label in GC_CLASSES:
if gc < threshold:
return gc, label
return gc, "Very high GC"
def validate_sequence() -> :
i, nuc (sequence):
nuc valid_bases:
()
:
()
detect_sequence_type() ==
classify_gc()[] ==
Pitfalls
- Off-by-one in codon loops:
range(0, len(seq) - 2, 3) stops so the last slice is still a full 3-char codon; range(0, len(seq), 3) produces an incomplete final codon when len(seq) % 3 != 0.
elif vs separate if: use elif for mutually exclusive classification (one GC bucket, one sequence type); use separate if statements for independent filters (length check AND GC check).
break exits only the innermost loop: in nested frame-scanning loops (e.g. looping over 3 reading frames, each scanning codons), a break in the inner loop does not stop the outer one — use a flag, return, or restructure into a function.
while without progress: every while loop must modify its condition variable or hit a break; forgetting pos += 3 (or using += 1 in a frame-locked scan) either loops forever or misses the frame.
- Motif scan off-by-N:
seq.find(motif, pos + 1) finds overlapping motifs; seq.find(motif, pos + len(motif)) silently skips overlaps — pick deliberately.
pass does nothing: it's a syntactic placeholder only — don't use it where you mean continue (skip this iteration) or break.
- Mutable default arguments:
def f(bases=[]) shares one list across every call; use def f(bases=None) and initialize inside, or a frozenset default as shown above.
- 1-based vs 0-based reporting: Python indices are 0-based; bioinformatics coordinates (VCF, GenBank, user-facing reports) are conventionally 1-based — add 1 only when printing, keep 0-based internally for slicing.
See Also
bio-sequence-manipulation-codon-usage — codon frequency/usage bias tables built on the same iteration pattern.
bio-sequence-manipulation-transcription-translation — full transcription/translation pipeline (Biopython Seq.translate).
bio-sequence-manipulation-motif-search — regex- and Biopython-based motif finding beyond plain str.find.
bio-sequence-io-filter-sequences — filtering FASTQ/FASTA records by length/quality at scale.