Exercism - Protein Translation
This post shows you how to get Protein Translation exercise of Exercism.
Preparation
Before we click on our next exercise, let’s see what concepts of DART we need to consider

So we need to use the following concepts.
Classes
Classes define blueprints for objects. They can contain methods, fields, and constants that work together to solve a problem.
class ProteinTranslation {
// Class methods and fields go here
List<String> translate(String rna) {
// Translation logic
return [];
}
}
void main() {
// Use class methods
ProteinTranslation translator = ProteinTranslation();
List<String> proteins = translator.translate('AUGUUUUCU');
print(proteins); // ['Methionine', 'Phenylalanine', 'Serine']
}
Static Const Maps
Static const maps are class-level constants that can be accessed without creating an instance. They’re perfect for lookup tables like codon-to-amino-acid mappings.
class ProteinTranslation {
// Static const map - shared by all instances
static const _codons = {
'AUG': 'Methionine',
'UUU': 'Phenylalanine',
'UUC': 'Phenylalanine',
};
// Access without instance
String? lookup(String codon) {
return _codons[codon];
}
}
void main() {
// Access static const map
String? aminoAcid = ProteinTranslation._codons['AUG'];
print(aminoAcid); // Methionine
}
Lists
Lists are ordered collections of elements. You can add elements, check length, and iterate through them.
void main() {
// Create empty list
List<String> proteins = [];
// Add elements
proteins.add('Methionine');
proteins.add('Phenylalanine');
print(proteins); // [Methionine, Phenylalanine]
// Check if empty
print(proteins.isEmpty); // false
print(proteins.isNotEmpty); // true
// Get length
print(proteins.length); // 2
}
String substring()
The substring() method extracts a portion of a string. It takes a start index and optionally an end index. It’s perfect for extracting codons (3-character sequences) from RNA strings.
void main() {
String rna = "AUGUUUUCU";
// Get substring with start and end index
String codon1 = rna.substring(0, 3);
print(codon1); // "AUG"
String codon2 = rna.substring(3, 6);
print(codon2); // "UUU"
// Extract codons in a loop
for (int i = 0; i < rna.length; i += 3) {
if (i + 3 <= rna.length) {
String codon = rna.substring(i, i + 3);
print(codon); // AUG, UUU, UCU
}
}
}
For Loops with Step
For loops can increment by values other than 1 using compound assignment operators. This is perfect for processing strings in chunks (like 3-character codons).
void main() {
String rna = "AUGUUUUCU";
// Loop with step of 3
for (int i = 0; i < rna.length; i += 3) {
print('Index: $i');
// Index: 0, 3, 6
}
// Extract codons
for (int i = 0; i < rna.length; i += 3) {
if (i + 3 <= rna.length) {
String codon = rna.substring(i, i + 3);
print(codon); // AUG, UUU, UCU
}
}
}
Map containsKey()
The containsKey() method checks if a map contains a specific key. It’s useful for validating that a codon exists in the translation table.
void main() {
Map<String, String> codons = {
'AUG': 'Methionine',
'UUU': 'Phenylalanine',
};
// Check if key exists
if (codons.containsKey('AUG')) {
print('Found AUG');
}
if (!codons.containsKey('XYZ')) {
print('XYZ not found');
}
// Use for validation
String codon = 'AUG';
if (!codons.containsKey(codon)) {
throw ArgumentError('Invalid codon: $codon');
}
}
Map Lookup with Null Assertion (!)
When you’re certain a map key exists, you can use the null assertion operator (!) to tell Dart that the value won’t be null. This is safe after checking with containsKey().
void main() {
Map<String, String> codons = {
'AUG': 'Methionine',
'UUU': 'Phenylalanine',
};
// Null assertion operator
String aminoAcid = codons['AUG']!;
print(aminoAcid); // Methionine
// Safe after containsKey check
String codon = 'AUG';
if (codons.containsKey(codon)) {
String aminoAcid = codons[codon]!; // Safe to use !
print(aminoAcid); // Methionine
}
}
ArgumentError
ArgumentError is thrown when a function receives an invalid argument. You can throw it to indicate that the input doesn’t meet the function’s requirements.
void main() {
void translate(String rna) {
// Validate input
if (rna.isEmpty) {
throw ArgumentError('RNA sequence cannot be empty');
}
if (rna.length % 3 != 0) {
throw ArgumentError('Invalid RNA sequence length');
}
// Process...
}
try {
translate('AUG'); // OK
translate('AU'); // Throws ArgumentError
} catch (e) {
print(e); // ArgumentError: Invalid RNA sequence length
}
}
Break Statement
The break statement exits a loop immediately. It’s useful for stopping processing when a STOP codon is encountered.
void main() {
List<String> codons = ['AUG', 'UUU', 'UAA', 'UCU'];
List<String> proteins = [];
for (String codon in codons) {
if (codon == 'UAA') {
break; // Exit loop immediately
}
proteins.add('Protein');
}
print(proteins); // [Protein, Protein] - stops before UAA
}
String isEmpty and isNotEmpty
The isEmpty property checks if a string has no characters. isNotEmpty is the opposite. They’re useful for validating input.
void main() {
String empty = '';
String notEmpty = 'AUG';
// Check if empty
print(empty.isEmpty); // true
print(notEmpty.isEmpty); // false
// Check if not empty
print(empty.isNotEmpty); // false
print(notEmpty.isNotEmpty); // true
// Use for validation
String rna = '';
if (rna.isEmpty) {
return []; // Return empty list
}
}
String length
The length property returns the number of characters in a string. It’s essential for bounds checking when extracting substrings.
void main() {
String rna = "AUGUUUUCU";
// Get length
print(rna.length); // 9
// Check bounds before substring
int i = 6;
if (i + 3 <= rna.length) {
String codon = rna.substring(i, i + 3);
print(codon); // UCU
} else {
print('Not enough characters');
}
}
Comparison Operators
Comparison operators (==, !=) compare values and return boolean results. They’re used to check if a codon is a STOP codon.
void main() {
String aminoAcid = 'STOP';
// Equality check
if (aminoAcid == 'STOP') {
print('Stop translation');
}
// Not equal
if (aminoAcid != 'STOP') {
print('Continue translation');
}
// Use in translation
String translation = 'STOP';
if (translation == 'STOP') {
break; // Stop processing
} else {
proteins.add(translation); // Add to list
}
}
Introduction
Your job is to translate RNA sequences into proteins.
RNA strands are made up of three-nucleotide sequences called codons. Each codon translates to an amino acid. When joined together, those amino acids make a protein.
In the real world, there are 64 codons, which in turn correspond to 20 amino acids. However, for this exercise, you’ll only use a few of the possible 64. They are listed below:
| Codon | Amino Acid |
|---|---|
| AUG | Methionine |
| UUU, UUC | Phenylalanine |
| UUA, UUG | Leucine |
| UCU, UCC, UCA, UCG | Serine |
| UAU, UAC | Tyrosine |
| UGU, UGC | Cysteine |
| UGG | Tryptophan |
| UAA, UAG, UGA | STOP |
Example
For example, the RNA string “AUGUUUUCU” has three codons: “AUG”, “UUU” and “UCU”. These map to Methionine, Phenylalanine, and Serine.
”STOP” Codons
You’ll note from the table above that there are three “STOP” codons. If you encounter any of these codons, ignore the rest of the sequence — the protein is complete.
For example, “AUGUUUUCUUAAAUG” contains a STOP codon (“UAA”). Once we reach that point, we stop processing. We therefore only consider the part before it (i.e. “AUGUUUUCU”), not any further codons after it (i.e. “AUG”).
Instructions
Your task is to translate RNA sequences into proteins.
How can we translate RNA to proteins?
To translate RNA sequences into proteins:
- Create codon map: Map each codon to its corresponding amino acid (including STOP codons)
- Handle empty input: Return empty list if RNA string is empty
- Process in chunks of 3: Extract codons by taking 3 characters at a time
- Validate sequence: Check that each codon exists in the map
- Translate codon: Look up the amino acid for each codon
- Stop on STOP: If the amino acid is “STOP”, break the loop immediately
- Collect proteins: Add each amino acid to the result list
- Return result: Return the list of amino acids
The key insight is processing the RNA string in chunks of 3 characters (codons), looking up each codon in a translation table, and stopping immediately when a STOP codon is encountered.
For example, with RNA “AUGUUUUCUUAA”:
- Extract “AUG” → Methionine → Add to list
- Extract “UUU” → Phenylalanine → Add to list
- Extract “UCU” → Serine → Add to list
- Extract “UAA” → STOP → Break loop
- Result: [‘Methionine’, ‘Phenylalanine’, ‘Serine’]
Solution
class ProteinTranslation {
static const _m = {
'AUG': 'Methionine', 'UUU': 'Phenylalanine', 'UUC': 'Phenylalanine',
'UUA': 'Leucine', 'UUG': 'Leucine', 'UCU': 'Serine', 'UCC': 'Serine',
'UCA': 'Serine', 'UCG': 'Serine', 'UAU': 'Tyrosine', 'UAC': 'Tyrosine',
'UGU': 'Cysteine', 'UGC': 'Cysteine', 'UGG': 'Tryptophan',
'UAA': 'STOP', 'UAG': 'STOP', 'UGA': 'STOP',
};
List<String> translate(String rna) {
if (rna.isEmpty) return [];
final p = <String>[];
for (var i = 0; i < rna.length; i += 3) {
if (i + 3 > rna.length) throw ArgumentError('Invalid RNA sequence length');
final c = rna.substring(i, i + 3);
if (!_m.containsKey(c)) throw ArgumentError('Invalid codon: $c');
final t = _m[c]!;
if (t == 'STOP') break;
p.add(t);
}
return p;
}
}
Let’s break down the solution:
-
class ProteinTranslation- Translation class:- Encapsulates the protein translation logic
- Contains the codon-to-amino-acid mapping
-
static const _m- Codon map:- Static const map shared by all instances
- Maps each codon to its amino acid
- Includes all codons and their translations
- Includes STOP codons (UAA, UAG, UGA)
-
List<String> translate(String rna)- Translation method:- Takes an RNA string as input
- Returns a list of amino acid names
- Processes the RNA string codon by codon
-
if (rna.isEmpty) return []- Handle empty input:- Returns empty list immediately if RNA string is empty
- Early return for edge case
-
final p = <String>[]- Result list:- Creates an empty list to store amino acids
- Will be populated as we translate codons
-
for (var i = 0; i < rna.length; i += 3)- Loop through codons:- Iterates through RNA string in steps of 3
istarts at 0, increments by 3 each iteration- Processes each 3-character codon
-
if (i + 3 > rna.length)- Validate sequence length:- Checks if there are enough characters for a complete codon
- Throws
ArgumentErrorif sequence is incomplete - Prevents index out of bounds errors
-
final c = rna.substring(i, i + 3)- Extract codon:- Extracts 3 characters starting at index
i - Creates a codon string (e.g., “AUG”, “UUU”)
- Extracts 3 characters starting at index
-
if (!_m.containsKey(c))- Validate codon:- Checks if the codon exists in the translation map
- Throws
ArgumentErrorwith descriptive message if invalid - Ensures only valid codons are processed
-
final t = _m[c]!- Look up amino acid:- Retrieves the amino acid for the codon
- Uses null assertion operator (
!) since we’ve validated withcontainsKey() - Returns either an amino acid name or “STOP”
-
if (t == 'STOP') break- Stop on STOP codon:- Checks if the translation is “STOP”
- Immediately exits the loop if STOP codon encountered
- Ignores any remaining codons in the sequence
-
p.add(t)- Add amino acid:- Adds the amino acid to the result list
- Only executed if not a STOP codon
-
return p- Return result:- Returns the list of translated amino acids
- Empty if input was empty or only STOP codons were found
The solution efficiently translates RNA sequences into proteins by processing codons in chunks of 3, validating each codon, and stopping immediately when a STOP codon is encountered.
A video tutorial for this exercise is coming soon! In the meantime, check out my YouTube channel for more Dart and Flutter tutorials. 😉
Visit My YouTube Channel