Module 5 · Build your Study Planner and practise independently · Lesson 17 of 20
Build your Study Planner: add, list and complete tasks
Can you build the planner without copying the whole answer at once? Work through three checkpoints: a menu, a task list, and a completion command.
Plan the behaviour before the code
The first version keeps tasks in memory. A learner can type a to add, l to list, d to mark a numbered task done, and q to quit. Commands are the lowercase letters shown in the menu. Blank titles and invalid task numbers are rejected. Closing this version loses its tasks; the next lesson adds saving.
read command
│
├─ a ──> validate title ──> add task
├─ l ──> show numbered tasks
├─ d ──> validate number ──> mark item done
└─ q / end of input ──> finish
After a, l or d: return to the menuBefore looking at the implementation, draw three task boxes on paper. Give each a Title and an IsDone flag. The list holds references to those objects. The displayed task numbers start at 1; the list indexes start at 0.
This version keeps the StudyTask declaration small and has no explicit constructor. C# supplies a parameterless constructor. The form new StudyTask { Title = title.Trim() } creates an object and then sets its writable Title property; the braces are an object initializer. IsDone starts false, the default bool value.
The screen shows task 1. Which list index selects it after validating the number?
Show answer
Correct answer: B
Subtract one from the learner-facing number: 1 - 1 = 0.
Option A: Index 1 means the second item.
Option C: Count is past the last valid index.
Option D: Negative indexes are invalid.
Hint: User numbering and list indexing start at different values.
Build it in small passes
The condition while (true) keeps the menu loop running. The break statement leaves that loop when q is entered or input ends. command == null detects end of input, and || accepts either exit condition. A return inside AddTask leaves that method and returns to the menu; it does not leave the outer loop.
Create a separate console project in a new folder named StudyPlannerApp. Build one checkpoint at a time; the complete program below is a reference to compare with after an attempt.
| Checkpoint | Build these pieces | Try it before continuing |
|---|---|---|
| 1. A menu that stops | The menu loop and q/end-of-input branch | Enter q. You should see Goodbye. and the process should finish. |
| 2. Tasks you can add and list | StudyTask, AddTask and ShowTasks; connect a and l in the menu | List an empty plan, reject a blank title, then add Read C# and list it. |
| 3. A task you can complete | MarkDone; connect d in the menu | Reject task 0 without changing state. Complete task 1, then list it again. |
At checkpoint 1, include only the menu and its exit branch; do not call AddTask, ShowTasks or MarkDone yet. At checkpoint 2, add the StudyTask class and the two methods before connecting a and l. At checkpoint 3, add MarkDone before connecting d. This keeps every checkpoint buildable without empty placeholder methods. Run each row’s check before adding the next command.
Try the complete session
Read the program. Before running, predict the list after entering these lines, one at a time:
a Read C# l d 1 l q
Complete Program.cs — compare after your checkpoints
This is a complete replacement for Program.cs. Keep one copy of each method; do not append it to your current program.
using System;
using System.Collections.Generic;
List<StudyTask> tasks = new List<StudyTask>();
Console.WriteLine("Study Planner");
while (true)
{
Console.WriteLine("a=add l=list d=done q=quit");
string? command = Console.ReadLine();
if (command == null || command == "q")
{
Console.WriteLine("Goodbye.");
break;
}
if (command == "a") AddTask(tasks);
else if (command == "l") ShowTasks(tasks);
else if (command == "d") MarkDone(tasks);
else Console.WriteLine("Unknown command.");
}
static void AddTask(List<StudyTask> tasks)
{
Console.WriteLine("Task title:");
string? title = Console.ReadLine();
if (string.IsNullOrWhiteSpace(title))
{
Console.WriteLine("Title required.");
return;
}
tasks.Add(new StudyTask { Title = title.Trim() });
Console.WriteLine("Added.");
}
static void ShowTasks(List<StudyTask> tasks)
{
if (tasks.Count == 0)
{
Console.WriteLine("No tasks yet.");
return;
}
for (int i = 0; i < tasks.Count; i++)
{
string status = "todo";
if (tasks[i].IsDone) status = "done";
Console.WriteLine($"{i + 1}. [{status}] {tasks[i].Title}");
}
}
static void MarkDone(List<StudyTask> tasks)
{
Console.WriteLine("Task number:");
string? text = Console.ReadLine();
if (!int.TryParse(text, out int number) || number < 1 || number > tasks.Count)
{
Console.WriteLine("Invalid task number.");
return;
}
tasks[number - 1].IsDone = true;
Console.WriteLine("Completed.");
}
class StudyTask
{
public string Title { get; set; } = "";
public bool IsDone { get; set; }
}In the predicted session, the first list shows 1. [todo] Read C#; the second shows 1. [done] Read C#. The MarkDone method changes the object stored in the list. It does not remove the task.
Why does AddTask return early when the title is blank?
Show answer
Correct answer: A
The method stops before tasks.Add. Control returns to the menu loop.
Option B: return here exits AddTask, not the outer program.
Option C: There is no Clear call.
Option D: This version has no persistence.
Check the contract
| Try this | Expected result |
|---|---|
| List immediately | No tasks yet. |
| Add a title containing only spaces | Title required; no item added. |
| Add Read C#, then list | One todo item. |
| Complete task 0, text abc or a number beyond Count | Invalid task number; existing state preserved. |
| Complete task 1, then list | The same item is now done. |
| End input at the menu | The application exits. |
Which check best verifies that an invalid task number is harmless?
Show answer
Correct answer: B
The requirement is about preserving state, so inspect that state after the rejected action.
Option A: A menu alone does not show whether data changed.
Option C: Compilation cannot prove the validation behaviour.
Option D: A name change does not test anything.
Your turn: add a useful command
Add c to count completed tasks without changing any tasks. Test one todo task and one done task; the result should be 1. Use a foreach loop and increase a counter only when IsDone is true. You already know every required building block. Add the command to the menu text as well as to the decision chain.
Explain your application
Describe one complete journey from reading d to changing IsDone. Name the input validation, the conversion from number to index, the changed object and the return to the menu. Keep this first version small enough that you can explain each method.
Reference: Microsoft — List<T>.
Quick reference
- Write the behaviour before the implementation.
- Build the menu, then add one command at a time.
- Validate a task number before subtracting one and indexing.
- Keep each method focused.
- Test what happens to state after invalid input.
TechInterviewNotes Editorial Team · Updated 27 Oct 2026 ·