warming up your workspace

DNA to protein, by coding the central dogma

Every cell in your body runs the same program: read DNA, make protein. Biologists call it the central dogma, and it sounds like deep chemistry. Computationally, it is almost embarrassingly simple: copy a string with one letter swapped, then walk it three characters at a time through a lookup table. Build it and the machinery of life becomes a program you can read.

The one idea

DNA stores instructions as a sequence of four bases: A, C, G, T. To build a protein, the cell does two steps. Transcription copies a gene into RNA, identical except T becomes U. Translation reads the RNA in groups of three bases called codons, and each codon maps to one amino acid, the beads that string together into a protein. The genetic code is just a dictionary from 64 codons to 20 amino acids, plus start and stop signals.

Transcription: one substitution

DNA to RNA is a single letter swap.

def transcribe(dna):
    return dna.replace("T", "U")

print(transcribe("ATGGCCTAA"))   # ATG GCC TAA
# AUGGCCUAA

That is genuinely all transcription is, at the sequence level. The biology is elaborate; the information transformation is one substitution.

Translation: a sliding window over a codon table

Now the real work. We need the codon table (a slice shown here; the full one has all 64), then we walk the RNA three bases at a time, starting at the AUG start codon and stopping at the first stop codon.

CODONS = {
    "AUG": "M",  # Methionine, also the START codon
    "GCC": "A", "GCU": "A", "GCA": "A", "GCG": "A",  # Alanine
    "UUU": "F", "UUC": "F",                          # Phenylalanine
    "GGU": "G", "GGC": "G", "GGA": "G", "GGG": "G",  # Glycine
    "AAA": "K", "AAG": "K",                          # Lysine
    "UAA": "*", "UAG": "*", "UGA": "*",              # STOP
}

def translate(rna):
    start = rna.find("AUG")
    if start == -1:
        return ""
    protein = []
    for i in range(start, len(rna) - 2, 3):
        codon = rna[i:i + 3]
        amino = CODONS.get(codon, "?")
        if amino == "*":          # stop codon: end the protein
            break
        protein.append(amino)
    return "".join(protein)

Run the central dogma end to end

Take a strand of DNA, transcribe it, translate it.

gene = "TACGATGGCCTTTGGCAAATAAGGG"   # a DNA strand (leading bases, then a gene)
rna = transcribe(gene)
print("RNA:    ", rna)
print("protein:", translate(rna))
RNA:     UACGAUGGCCUUUGGCAAAUAAGGG
protein: MAFGK

Read what happened: translation ignored the leading UACGA junk, found the AUG start, then read codons in frame, GCC, UUU, GGC, AAA, giving M-A-F-G-K, and halted at the UAA stop, leaving the trailing GGG unread. That is a real, if short, protein, produced by the same two operations every one of your cells performs billions of times a second.

Why the small details are the whole game

Two things in that code carry deep biology. First, the reading frame: codons are non-overlapping triples, and where you start changes everything. Start one base over and AUGGCC becomes UGGCCU, a completely different protein. This is why a single inserted base, a frameshift mutation, can be catastrophic: it reshuffles every codon downstream. Second, the code is redundant: four different codons all mean Alanine. That redundancy is a buffer, many single-letter mutations change the codon but not the amino acid, so the protein is unharmed.

You just encoded the process behind genetics, evolution, and every genetic disease, as a substitution and a dictionary lookup. That is the promise of computational biology: the mechanisms of life are algorithms, and you can run them. Building them from the sequence up is exactly how the bioinformatics track works.