DEV Community

Cover image for Week 3: Lists, Dicts, and Why Data Structures Matter.
Om Kolhapure
Om Kolhapure

Posted on

Week 3: Lists, Dicts, and Why Data Structures Matter.

Weeks 1 and 2 were about learning the language.

Week 3 was about learning how to hold data.

Every meaningful program stores and organises information — lists of tasks, books of contacts, inventories of products. This week I stopped treating data as a side effect of my programs and started designing programs around data. Lists, dictionaries, nested structures, sets, tuples — six days that changed how I think about writing Python.

Here's what happened.


Day 15 — Lists Deep Dive: A Real To-Do App

Day 15 started with a question: what's the most practical thing I can build with a list?

A to-do app. Add tasks, remove them, mark them complete, filter by status. I built the whole thing using list methods — append(), remove(), pop(), index() — and kept the clean one-function-one-job structure I picked up in Week 2:

import sys

pending_tasks = []
completed_tasks = []


def main():
    print("\n\tWelcome to the app!")
    while True:
        while True:
            usr = input('''What would you like to do?(choose the corresponding number)\n1.Add Tasks
                        \n2.Mark complete\n3.View Tasks\n4.Remove Task\n5.Press 'q' to quit\n-> ''')

            if usr == "1" or usr == "2" or usr == "3" or usr == "4" or usr == "q":
                break
        match usr:
            case "1":
                add_task()
            case "2":
                mark_complete()
            case "3":
                filters()
            case "4":
                remove_task()
            case "q":
                sys.exit()


#Add
def add_task():
    '''Adds Task to pending_tasks'''
    new_task = input("Enter the new task: ").strip()
    pending_tasks.append(new_task)
    print("Task Added")
    print_tasks(pending_tasks)


#Remove
def remove_task():
    '''Removes the Task mentioned by the user'''
    rmove = input("Enter the task you want to remove: ").strip()
    if rmove in pending_tasks:
        pending_tasks.remove(rmove)
        print_tasks(pending_tasks)
    elif rmove in completed_tasks:
        completed_tasks.remove(rmove)
        print_tasks(completed_tasks)
    else:
        print("Task not found!")


#Mark Complete
def mark_complete():
    '''Transfers the task completed by the user from pending_tasks to completed_tasks'''
    complete = input("Enter the task you want to mark complete: ").strip()
    if complete in pending_tasks:
        completed_tasks.append(pending_tasks.pop(pending_tasks.index(complete)))
    elif complete in completed_tasks:
        print("Task already completed!")
    else:
        print("Task not Found!")

#Filter by status
def filters():
    '''Let's user filter between pending tasks and completed tasks'''
    filter_task = input("Which tasks do you want to see?(Pending/Completed): ").strip().lower()
    if filter_task == "pending":
        if pending_tasks:
            print_tasks(pending_tasks)
        else:
            print("No Task Found!")
    elif filter_task == "completed":
        if completed_tasks:
            print_tasks(completed_tasks)
        else:
            print("No Task Found!")
    else:
        print("Invalid input!")

# print tasks
def print_tasks(t):
    '''prints the list of tasks to the screen'''
    for tsk in t:
        print(tsk)

#calling the main function
main()
Enter fullscreen mode Exit fullscreen mode

The line that clicked hardest: completed_tasks.append(pending_tasks.pop(pending_tasks.index(complete))). That single chain finds the task by name, removes it from pending, and returns it — so it lands straight into completed_tasks. One line doing exactly the right job. That's when list methods started feeling like a real toolkit rather than a list of things to memorise.


Day 16 — List Comprehensions: Less Code, Same Power

Day 16 introduced one of Python's most beloved features — list comprehensions. Build a filtered or transformed list in a single, readable line instead of writing a full loop.

I built a number filter that splits 1–10 into even, odd, and prime — with a custom is_prime() function handling the prime logic:

def main():
    numbers = list(range(1,11))
    even_numbers = [num for num in numbers if num % 2 == 0]
    odd_numbers = [num for num in numbers if num % 2 != 0]
    prime_numbers = [num for num in numbers if is_prime(num)]
    print(even_numbers)
    print(odd_numbers)
    print(prime_numbers)


def is_prime(n):
    if n < 2:
        return False
    else:
        for i in range(2, int(pow(n, 0.5)) + 1):
            if n % i == 0:
                return False
    return True

main()
Enter fullscreen mode Exit fullscreen mode

Output:

[2, 4, 6, 8, 10]
[1, 3, 5, 7, 9]
[2, 3, 5, 7]
Enter fullscreen mode Exit fullscreen mode

Two things hit me at once here:

  • List comprehensions read like sentences: "give me every num from numbers if it's even." Once you see it, you can't unsee it — a regular for loop starts feeling like too many words.
  • The prime checker uses pow(n, 0.5) — the square root of n — to limit how many divisors it needs to test. That's a genuine algorithmic optimisation. Week 1 me would have looped all the way to n. Discovering that while building felt like a small unlock.

Day 17 — Dictionaries: A Contact Book

Dictionaries were the data structure I'd been reaching for since Week 1. Key-value pairs — the natural way to model real-world objects.

I built a contact book: name as the key, a nested dictionary of details as the value. Add contacts, view them, delete them:

#building a contact book
# contact -> information = mobile number, email, address
import sys
contacts = {}
app_running = True


def main():
    '''main function'''
    global app_running
    while app_running:
        print("\n\tWELCOME TO CONTACT BOOK\n")
        while True:
            ask_user = input('''What would you like to do?\n
                            (Choose the corresponding number of the task you want to perform)\n
                            1. Add/Save new contact\n
                            2. View contacts\n
                            3. Delete contact\n
                            (press 'q' to quit)\n -> ''').strip().lower()
            if ask_user == "1" or ask_user == "2" or ask_user == "3" or ask_user == "q":
                break

        match ask_user:
            case "1":
                save_contact()
            case "2":
                show_contacts()
            case "3":
                delete_contact()
            case "q":
                sys.exit()


def save_contact():
    '''Saves user's contact'''
    global app_running
    while app_running:
        if not app_running:
            break
        name = input("Enter name: ")
        user = {}
        contacts[name] = user
        while True:
            while True:
                user["mob"] = input("Enter Mobile No.: ").strip()
                user["email"] = input("Enter email: ").strip()
                if user["mob"].isdecimal():
                    break
                else:
                    print("Invalid Mobile No.")

                if "@" in user["email"]:
                    break
                else:
                    print("Invalid email!")
            user["add"] = input("Enter address: ").strip()   
            print("Contact saved...\n")
            show_contacts()
            break
        while True:
            response = input("Do you want to save another contact?(yes/no)? ")
            if response == "yes" or response == "no":
                break
        if response == "no":
           break


def show_contacts():
    '''Shows contact's of the user'''
    if contacts:
        print_contact()
    else:
        print("No Contacts found!")

def delete_contact():
    '''Deletes a contact'''
    show_contacts()
    delete = input("Enter the name of the you want to delete: ")
    if delete in contacts:
        del contacts[delete]
    else:
        print("No contact found!")


def print_contact():
    '''prints contacts'''
    for contact, user_info in contacts.items():
        print(f"Contact name: {contact}\nPhone No.: {user_info['mob']}\nEmail: {user_info['email']}\nAddress: {user_info['add']}\n")


#Calling the main function
main()
Enter fullscreen mode Exit fullscreen mode

The structure that made this work: contacts[name] = user where user is itself a dictionary of mob, email, and add. That's a dictionary inside a dictionary — my first proper nested data structure, and it arrived naturally because the problem required it.

.items() in print_contact() was also new — iterating over both keys and values simultaneously instead of just one. Once you know it exists, you can't stop using it.


Day 18 — Nested Data: An Inventory System

The contact book was one level of nesting. Day 18 went three levels deep: category → item → details:

#Building an inventory system
#category-> item-> details
global price
inventory = {
    "Electronics": {
        "Laptop": {"quantity": 10, "price": 999.99, "supplier": "TechCorp"},
        "Mouse":  {"quantity": 50, "price": 25.50,  "supplier": "GadgetInc"}
    },
    "Furniture": {
        "Chair": {"quantity": 20, "price": 150.00, "supplier": "ComfortCo"}
    }
}


def main():
    cat = input("Enter the category you want the total value of (furniture/electronics): ").title()
    total_value(inventory[cat])

def total_value(inventory_item):
    products = {}
    products = inventory_item
    total = 0
    for product_info in products.values():
        total += product_info["quantity"] * product_info["price"]
    print(total)


main()
Enter fullscreen mode Exit fullscreen mode

Ask for Electronics: (10 × 999.99) + (50 × 25.50) = 11,274.90.
Ask for Furniture: 20 × 150.00 = 3,000.00.

What struck me: I spent more time thinking about how to structure the dictionary than writing the functions. The shape of the data determined the shape of the code. That shift — designing data first, then writing logic around it — felt like a genuinely new layer of thinking.


Day 19 — Sets & Tuples

Day 19 was the two data structures I hadn't touched yet.

Tuples — like lists, but immutable. Once created, they can't be changed. Perfect for data that must stay fixed: config values, coordinates, question banks. The quiz project on Day 20 stores all questions and answers as tuples for exactly this reason — nothing can accidentally overwrite them mid-run.

Sets — unordered collections with no duplicates. The killer use case: deduplication. Have a list and want only the unique values? Convert to a set. Sets also have significantly faster membership checks than lists for large collections.

The mental model I settled on by end of day:

  • Need order + mutability? → List
  • Need key-value lookup? → Dictionary
  • Need immutable, fixed data? → Tuple
  • Need uniqueness + fast lookup? → Set

Day 20 — The Week 3 Project: A Python Quiz App

The end-of-week project brought together everything from the week — dictionaries, tuples, file I/O, try/except, randomisation, and a proper multi-file structure. The most architecturally considered thing I've built.

Two files: qdata.py holds all the quiz data, quiz.py runs the game.

qdata.py — The Data Layer

questions = (
    "How many parameters does a print statement have?",
    "Which functions returns the ASCII number associated with a char?",
    "What does the method dict.keys() return?",
    "What is the  difference between a list and a tuple?",
    "What is the full form of json?",
    "What does the function json.dumps() does?",
    "What does the function json.loads() does?",
    "In a dictionary, what does (dict_name['key']) this syntax return?",
    "Methods and functions are the same.",
    "What type of datatype does the input() function return?",
)

answers = (
    "3",
    "ord()",
    "view object",
    "A list can be modified but a tuple cannot be modified",
    "Javascript Object Notation",
    "Converts dictionary to a str",
    "Converts str to a dictionary",
    "Returns the value associated with the given key",
    "False",
    "str",
)

qna = {}
for i in range(len(questions)):
    qna[questions[i]] = answers[i]

options = [
    ["1", "2", "3", "4"],
    ["list()", "chr()", "ord()", "int()"],
    ["tuple of keys", "list of keys", "view object", "object"],
    ["They are both the same", "A list can be modified but a tuple cannot be modified", "A tuple can be modified but a list cannot be modified", "We can access an element in lists but not in tuples"],
    ["Java Subject Object Notes", "Javascript Object Notation", "Jackle and Son Oxygen Needs", "Java SON"],
    ["Closes the file", "Converts str to a dictionary", "Converts dictionary to a str", "Clears the file"],
    ["Creates a new file", "Converts dictionary to a str", "Converts str to a dictionary", "Opens the file"],
    ["Returns the key", "Returns the value associated with the given key", "Returns all the keys", "Returns the dictionary name"],
    ["True", "False", "Both True and False", "None of the above"],
    ["float", "integer", "character", "str"]
]

markers = ["A", "B", "C", "D"]
Enter fullscreen mode Exit fullscreen mode

Questions and answers are tuples — immutable, so nothing can accidentally overwrite them at runtime. The qna dictionary maps each question directly to its correct answer, built dynamically with a for loop. Clean and easy to extend — add a question to questions, an answer to answers, options to options, and the game picks it up automatically.

quiz.py — The Game Engine

from pathlib import Path
import random
import qdata
import sys

global val
global score
num = list(range(0, 10))
file = Path("score.txt")

def main():
    while True:
        ask_user = input("Wanna play the fun python quiz?(yes/no) ").strip().lower()
        if ask_user == "yes" or ask_user == "no":
            break
    if ask_user == "yes":
        while True:
            score = 0
            random.shuffle(num)
            for n in num:
                val = n
                marked_options = dict(zip(qdata.markers, qdata.options[val]))
                display_question(val)
                display_option(marked_options)
                score = user_choice(val, score, marked_options)
            high_score(score)
            while True:
                usr = input("Wanna play again(yes/no)? ").strip().lower()
                if usr == "yes" or usr == "no":
                    break
            if usr == "no":
                sys.exit()
    else:
        sys.exit()


def display_question(val):
    print(qdata.questions[val])

def display_option(marked_options):
    for marker,option in marked_options.items():
        print(f"{marker}. {option}")

def user_choice(val, score, marked_options):
    while True:
        try:
            user_ans = input("Choose your option: ").strip().upper()
            if marked_options[user_ans] == qdata.qna[qdata.questions[val]]:
                score += 4
                print("You got it right!")
            else:
                score -= 1
                print("You are wrong:(")
            print(f"Current Score: {score}")
            return score
        except KeyError:
            print("Choose A/ B/ C/ D!!")

def high_score(score):
    h_score = 0
    if h_score < score:
        h_score = score
        file.write_text(f"High score: {h_score}")
        print(file.read_text())
    else:
        file.write_text(f"High scores: {h_score}")

main()
Enter fullscreen mode Exit fullscreen mode

Everything I've learned in three weeks lives inside this project:

  • Tuples for immutable question and answer data
  • Dictionaries for the qna lookup table and marked_options per question
  • random.shuffle() so questions arrive in a different order every game
  • dict(zip()) to pair A/B/C/D markers to options on the fly
  • try/except KeyError to catch invalid answers gracefully instead of crashing
  • pathlib + file I/O to persist the high score between sessions
  • Multi-file structurequiz.py imports qdata as a module, keeping data and logic completely separate

My current high score saved to score.txt: 35.


📁 What I Built This Week

Project File Concepts Used
To-Do List App todo_list.py Lists, append, remove, pop, index, filtering
Number Filter number_filter.py List comprehensions, prime algorithm, pow()
Contact Book contact.py Dictionaries, nested dicts, .items(), del
Inventory System inventory.py Nested dictionaries, .values(), data-first design
Python Quiz App python-quiz/quiz.py + qdata.py Tuples, dicts, zip(), shuffle(), try/except, file I/O, multi-file modules

All the code is on GitHub — every week, every file:
👉 github.com/Omk4314/progress-on-python


What Actually Clicked This Week

  • Data structures aren't just storage — they're design decisions. Choosing between a list, dictionary, tuple, or set shapes how the entire program works.
  • Nested dictionaries feel natural once you need them. The contact book required them. I didn't force the structure — the problem suggested it.
  • List comprehensions are more readable, not just shorter. [num for num in numbers if is_prime(num)] reads like a plain description of what you want.
  • Separating data from logic is powerful. qdata.py and quiz.py as two files meant I could change every question without touching the game engine once.
  • try/except KeyError is the right tool when dictionary lookups might fail on bad user input. Cleaner than checking if key in dict every time.

What I Want to Learn Next

Week 4 is on the horizon and I already know what's coming:

  • Object-Oriented Programming — classes and instances. The to-do app and contact book are straining against the limits of functions-and-globals. Objects feel like the natural next step.
  • Inheritance — sharing behaviour without copying code
  • __str__ and __repr__ — making objects print like real things
  • Larger multi-file projects — the quiz app gave me a taste of real structure

Three weeks in. The programs are starting to look like software.

If you're following along or learning Python yourself, drop your projects in the comments — beginner work deserves to be seen.

See you in Week 4. 🐍


Week 3 complete. I finally understand why everyone says "it's all about the data structures."

Top comments (0)