Exercism - Food Chain
This post shows you how to get Food Chain 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.
Enums with Fields
Enums can have fields that store data for each enum value. They’re perfect for representing animals with their names and reactions.
enum AnimalType {
fly('fly', ''),
spider('spider', 'It wriggled and jiggled and tickled inside her.'),
bird('bird', 'How absurd to swallow a bird!');
final String name;
final String reaction;
const AnimalType(this.name, this.reaction);
}
void main() {
AnimalType animal = AnimalType.spider;
print(animal.name); // "spider"
print(animal.reaction); // "It wriggled and jiggled and tickled inside her."
}
Enum Values
Enum values can be accessed using .values, which returns a list of all enum values in declaration order. You can index into this list to get specific values.
enum AnimalType {
fly, spider, bird, cat, dog, goat, cow, horse;
}
void main() {
// Get all enum values
List<AnimalType> allAnimals = AnimalType.values;
print(allAnimals); // [AnimalType.fly, AnimalType.spider, ...]
// Access by index
AnimalType first = AnimalType.values[0]; // fly
AnimalType second = AnimalType.values[1]; // spider
// Use in loops
for (int i = 0; i < AnimalType.values.length; i++) {
AnimalType animal = AnimalType.values[i];
print(animal);
}
}
Const Constructors
Const constructors allow enum values to have constant fields. The const keyword ensures the values are compile-time constants.
enum AnimalType {
fly('fly', ''),
spider('spider', 'It wriggled and jiggled and tickled inside her.');
final String name;
final String reaction;
// Const constructor
const AnimalType(this.name, this.reaction);
}
void main() {
// Values are compile-time constants
AnimalType animal = AnimalType.fly;
print(animal.name); // "fly"
}
Enum Getters
Enums can have getter properties that compute values based on the enum’s fields or identity. They’re perfect for checking conditions.
enum AnimalType {
fly('fly', ''),
spider('spider', 'It wriggled and jiggled and tickled inside her.'),
horse('horse', "She's dead, of course!");
final String name;
final String reaction;
const AnimalType(this.name, this.reaction);
// Getters
bool get hasReaction => reaction.isNotEmpty;
bool get isFinal => this == AnimalType.horse;
bool get needsSuffix => this == AnimalType.spider;
}
void main() {
AnimalType spider = AnimalType.spider;
print(spider.hasReaction); // true
print(spider.needsSuffix); // true
AnimalType fly = AnimalType.fly;
print(fly.hasReaction); // false
}
List addAll() Method
The addAll() method adds all elements from an iterable to a list. It’s perfect for adding multiple lines to a verse.
void main() {
List<String> lines = [];
// Add single element
lines.add('Line 1');
// Add multiple elements
lines.addAll(['Line 2', 'Line 3', 'Line 4']);
print(lines); // [Line 1, Line 2, Line 3, Line 4]
// Add from another list
List<String> moreLines = ['Line 5', 'Line 6'];
lines.addAll(moreLines);
print(lines); // [Line 1, Line 2, Line 3, Line 4, Line 5, Line 6]
}
List isNotEmpty Property
The isNotEmpty property checks if a list has at least one element. It’s the opposite of isEmpty.
void main() {
List<String> lines = [];
// Check if not empty
if (lines.isNotEmpty) {
lines.add(''); // Add separator
}
lines.add('New line');
// Use for adding separators
List<String> result = [];
for (int i = 0; i < 3; i++) {
if (result.isNotEmpty) result.add(''); // Add separator between verses
result.addAll(['Line 1', 'Line 2']);
}
}
List sublist() Method
The sublist() method creates a new list containing elements from a specified range. It’s perfect for getting animals up to a certain verse.
void main() {
List<String> animals = ['fly', 'spider', 'bird', 'cat', 'dog'];
// Get sublist from start to index
List<String> firstThree = animals.sublist(0, 3);
print(firstThree); // ['fly', 'spider', 'bird']
// Get sublist from start to end
List<String> all = animals.sublist(0);
print(all); // ['fly', 'spider', 'bird', 'cat', 'dog']
// Use with enum values
List<AnimalType> allAnimals = AnimalType.values;
List<AnimalType> upToVerse3 = allAnimals.sublist(0, 3);
print(upToVerse3); // [fly, spider, bird]
}
Reverse Iteration
Reverse iteration processes elements from the end to the beginning. It’s achieved by starting from the last index and decrementing.
void main() {
List<String> animals = ['fly', 'spider', 'bird', 'cat'];
// Iterate backwards
for (int i = animals.length - 1; i > 0; i--) {
String current = animals[i];
String previous = animals[i - 1];
print('$current catches $previous');
// cat catches bird
// bird catches spider
// spider catches fly
}
// Use for building chains
List<String> chain = [];
for (int i = animals.length - 1; i > 0; i--) {
chain.add('${animals[i]} catches ${animals[i - 1]}');
}
}
String Interpolation
String interpolation allows you to embed expressions and variables directly within strings using ${expression} or $variable.
void main() {
String animal = 'spider';
String previous = 'fly';
// Basic interpolation
String line = "She swallowed the $animal to catch the $previous.";
print(line); // "She swallowed the spider to catch the fly."
// Expression interpolation
int verse = 3;
String intro = "I know an old lady who swallowed a ${AnimalType.values[verse - 1].name}.";
print(intro); // "I know an old lady who swallowed a bird."
// Conditional in interpolation
bool needsSuffix = true;
String suffix = needsSuffix ? ' that wriggled and jiggled and tickled inside her' : '';
String line2 = "She swallowed the spider to catch the fly$suffix.";
print(line2); // "She swallowed the spider to catch the fly that wriggled and jiggled and tickled inside her."
}
Helper Methods
Helper methods (often private methods starting with _) encapsulate reusable logic, making code more organized and maintainable.
class FoodChain {
// Public method
List<String> recite(int startVerse, int endVerse) {
List<String> result = [];
for (int verse = startVerse; verse <= endVerse; verse++) {
if (result.isNotEmpty) result.add('');
result.addAll(_buildVerse(verse)); // Use helper
}
return result;
}
// Private helper method
List<String> _buildVerse(int verseNumber) {
// Build single verse
return ['Line 1', 'Line 2'];
}
}
For Loops with Range
For loops can iterate through a range of numbers. They’re perfect for generating multiple verses.
void main() {
List<String> result = [];
int startVerse = 1;
int endVerse = 3;
// Loop through verse range
for (int verse = startVerse; verse <= endVerse; verse++) {
if (result.isNotEmpty) result.add(''); // Add separator
result.addAll(buildVerse(verse));
}
// Example: verses 1, 2, 3
// verse 1: add verse 1 lines
// verse 2: add '', then verse 2 lines
// verse 3: add '', then verse 3 lines
}
Conditional Logic
Conditional statements allow you to execute different code based on conditions. They’re essential for handling special cases like the final verse or animals with reactions.
void main() {
AnimalType animal = AnimalType.horse;
// Check if final verse
if (animal.isFinal) {
print(animal.reaction); // "She's dead, of course!"
return; // No chain for final verse
}
// Check if has reaction
if (animal.hasReaction) {
print(animal.reaction);
}
// Check if needs special suffix
AnimalType previous = AnimalType.spider;
String suffix = previous.needsSuffix
? ' that wriggled and jiggled and tickled inside her'
: '';
print("She swallowed the bird to catch the spider$suffix.");
}
Introduction
Generate the lyrics of the song ‘I Know an Old Lady Who Swallowed a Fly’.
While you could copy/paste the lyrics, or read them from a file, this problem is much more interesting if you approach it algorithmically.
This is a cumulative song of unknown origin.
This is one of many common variants.
Example Verses
Verse 1:
I know an old lady who swallowed a fly.
I don't know why she swallowed the fly. Perhaps she'll die.
Verse 2:
I know an old lady who swallowed a spider.
It wriggled and jiggled and tickled inside her.
She swallowed the spider to catch the fly.
I don't know why she swallowed the fly. Perhaps she'll die.
Verse 3:
I know an old lady who swallowed a bird.
How absurd to swallow a bird!
She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her.
She swallowed the spider to catch the fly.
I don't know why she swallowed the fly. Perhaps she'll die.
Verse 8 (Final):
I know an old lady who swallowed a horse.
She's dead, of course!
Instructions
Your task is to generate the lyrics algorithmically. The song follows a pattern:
- Each verse introduces a new animal
- Most verses include a reaction line
- Verses build a chain backwards (newest animal catches previous)
- Spider needs special suffix when mentioned in chain
- Final verse (horse) has no chain, just reaction
How do we generate the food chain lyrics?
To generate the lyrics:
- Define animals: Create enum with all animals, their names, and reactions
- Build verses: For each verse number:
- Get the animal for that verse
- Add intro line (“I know an old lady who swallowed a [animal]”)
- If final verse (horse): add reaction and return
- If has reaction: add reaction line
- Build chain backwards: iterate from current animal down to fly
- Add special suffix for spider when it’s the previous animal
- Add final line (“I don’t know why she swallowed the fly…”)
- Combine verses: Add empty line between verses, combine all lines
The key insight is using reverse iteration to build the chain backwards, and using enum getters to handle special cases (reactions, final verse, spider suffix).
Solution
enum AnimalType {
fly('fly', ''),
spider('spider', 'It wriggled and jiggled and tickled inside her.'),
bird('bird', 'How absurd to swallow a bird!'),
cat('cat', 'Imagine that, to swallow a cat!'),
dog('dog', 'What a hog, to swallow a dog!'),
goat('goat', 'Just opened her throat and swallowed a goat!'),
cow('cow', "I don't know how she swallowed a cow!"),
horse('horse', "She's dead, of course!");
final String name;
final String reaction;
const AnimalType(this.name, this.reaction);
bool get hasReaction => reaction.isNotEmpty;
bool get isFinal => this == AnimalType.horse;
bool get needsSuffix => this == AnimalType.spider;
}
class FoodChain {
List<String> recite(int startVerse, int endVerse) {
final result = <String>[];
for (int verse = startVerse; verse <= endVerse; verse++) {
if (result.isNotEmpty) result.add('');
result.addAll(_buildVerse(verse));
}
return result;
}
List<String> _buildVerse(int verseNumber) {
final animal = AnimalType.values[verseNumber - 1];
final lines = <String>[];
lines.add("I know an old lady who swallowed a ${animal.name}.");
if (animal.isFinal) {
lines.add(animal.reaction);
return lines;
}
if (animal.hasReaction) {
lines.add(animal.reaction);
}
final animals = AnimalType.values.sublist(0, verseNumber);
for (int i = animals.length - 1; i > 0; i--) {
final current = animals[i];
final previous = animals[i - 1];
final suffix = previous.needsSuffix
? ' that wriggled and jiggled and tickled inside her'
: '';
lines.add("She swallowed the ${current.name} to catch the ${previous.name}$suffix.");
}
lines.add("I don't know why she swallowed the fly. Perhaps she'll die.");
return lines;
}
}
Let’s break down the solution:
-
enum AnimalType- Animal enum:- Defines all 8 animals in order
- Each has a name and reaction string
- Uses const constructor for compile-time constants
-
fly('fly', '')- Animal definitions:- First parameter: animal name
- Second parameter: reaction line (empty for fly)
- Example:
spider('spider', 'It wriggled...')
-
final String nameandfinal String reaction- Enum fields:- Store data for each enum value
- Accessible on each enum instance
-
const AnimalType(this.name, this.reaction)- Const constructor:- Makes enum values compile-time constants
- Initializes name and reaction fields
-
bool get hasReaction => reaction.isNotEmpty- Reaction getter:- Checks if animal has a reaction line
- Returns true if reaction is not empty
- Example: fly → false, spider → true
-
bool get isFinal => this == AnimalType.horse- Final verse getter:- Checks if this is the final animal (horse)
- Used to handle special case (no chain)
-
bool get needsSuffix => this == AnimalType.spider- Suffix getter:- Checks if animal needs special suffix
- Only spider needs ” that wriggled and jiggled and tickled inside her”
- Used when spider is the previous animal in chain
-
class FoodChain- Main class:- Encapsulates lyric generation logic
- Contains public
recite()and private_buildVerse()methods
-
List<String> recite(int startVerse, int endVerse)- Public method:- Takes verse range (start to end, inclusive)
- Returns list of all lines for those verses
- Adds empty lines between verses
-
final result = <String>[]- Result list:- Creates empty list to collect all lines
- Will contain lines from all requested verses
-
for (int verse = startVerse; verse <= endVerse; verse++)- Verse loop:- Iterates through each verse in range
- Inclusive range (includes both start and end)
-
if (result.isNotEmpty) result.add('')- Add separator:- Adds empty line between verses
- Only adds if result already has content
- Prevents leading empty line
-
result.addAll(_buildVerse(verse))- Add verse lines:- Calls helper method to build single verse
- Adds all lines from that verse to result
addAll()adds multiple elements at once
-
List<String> _buildVerse(int verseNumber)- Verse builder:- Private helper method
- Builds lines for a single verse
- Returns list of lines for that verse
-
final animal = AnimalType.values[verseNumber - 1]- Get animal:- Gets animal for this verse number
- Verse 1 → index 0 (fly)
- Verse 2 → index 1 (spider)
- Uses
- 1because verses are 1-indexed, arrays are 0-indexed
-
final lines = <String>[]- Verse lines:- Creates list to collect lines for this verse
- Will be returned at end of method
-
lines.add("I know an old lady who swallowed a ${animal.name}.")- Intro line:- Adds the opening line for the verse
- Uses string interpolation to include animal name
- Example: “I know an old lady who swallowed a spider.”
-
if (animal.isFinal)- Check final verse:- If this is the horse (final verse)
- Special handling: no chain, just reaction
-
lines.add(animal.reaction)andreturn lines- Final verse handling:- Adds reaction line (“She’s dead, of course!”)
- Returns immediately (no chain for final verse)
-
if (animal.hasReaction)- Add reaction:- If animal has a reaction line, add it
- Example: spider → “It wriggled and jiggled and tickled inside her.”
-
final animals = AnimalType.values.sublist(0, verseNumber)- Get animals up to verse:- Gets all animals from start to current verse
- Example: verse 3 → [fly, spider, bird]
- Used to build the chain
-
for (int i = animals.length - 1; i > 0; i--)- Reverse iteration:- Iterates backwards through animals
- Starts from last (current animal)
- Goes down to index 1 (stops before fly)
- Example: [fly, spider, bird] → i=2, then i=1
-
final current = animals[i]andfinal previous = animals[i - 1]- Get pair:current: animal at position i (being swallowed)previous: animal at position i-1 (being caught)- Example: i=2 → current=bird, previous=spider
-
final suffix = previous.needsSuffix ? '...' : ''- Spider suffix:- Checks if previous animal is spider
- If yes, adds special suffix
- If no, empty string (no suffix)
-
lines.add("She swallowed the ${current.name} to catch the ${previous.name}$suffix.")- Chain line:- Builds chain line using string interpolation
- Includes current and previous animal names
- Adds suffix if previous is spider
- Example: “She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her.”
-
lines.add("I don't know why she swallowed the fly. Perhaps she'll die.")- Final line:- Adds the closing line for non-final verses
- Always the same for all verses (except final)
-
return lines- Return verse:- Returns all lines for this verse
- Will be added to result in
recite()method
The solution elegantly generates the cumulative song lyrics using enums to represent animals, reverse iteration to build chains backwards, and conditional logic to handle special cases (reactions, final verse, spider suffix). The structure makes it easy to add or modify animals.
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