Blog

  • 7 Practical Ways to Reduce Fee Defaulting in Your School

    7 Practical Ways to Reduce Fee Defaulting in Your School

    Fee defaulting is rarely about parents who won’t pay — more often it’s about friction, forgetfulness and unclear expectations. Reduce the friction and most of the problem disappears. Here are seven practical ways to improve collection without damaging your relationship with families.

    1. Make the terms crystal clear from the start

    Defaulting often begins with confusion. At enrolment and at the start of each term, give every parent a clear breakdown of what’s due, when, and what happens if it isn’t. When expectations are explicit, far fewer payments slip.

    2. Make paying effortless

    The single biggest lever. If a parent has to visit the school, queue at a bank, or send a transfer and then prove it, some won’t get around to it. Online payment in Naira — card or transfer, from a phone, any time — removes that friction. Good fee management software reconciles each payment automatically, so paying is as easy as topping up airtime.

    3. Offer instalment plans openly

    Rigid “pay in full by week one” rules push struggling-but-willing parents into default. Offering structured instalments keeps them paying. The key is that your system tracks the plan and the running balance accurately, so flexibility doesn’t become chaos.

    4. Remind early and automatically

    People forget. A polite, automatic reminder a few days before a due date — and a gentle nudge after — recovers a surprising amount of revenue. Because it’s automatic and consistent, no staff member has to make an awkward personal call. Pair it with strong parent communication so reminders land reliably.

    5. Track arrears in real time

    You can’t act on what you can’t see. A live view of who’s owing, by class and by student, lets you intervene in week two instead of discovering the gap at term-end. Early visibility is early action.

    6. Recognise and reward good payers

    A small early-payment discount or simple acknowledgement shifts behaviour. Parents respond to being seen as reliable — and it costs far less than chasing late payments.

    7. Keep the conversation human

    Finally, remember that behind every balance is a family. The schools with the best collection rates combine efficient systems with empathy: clear terms and easy payment, but also a willingness to talk to a parent who’s struggling. The software handles the routine so your team has time for the exceptions.

    The compounding effect

    None of these is dramatic on its own. Together, they compound: clear terms reduce confusion, easy payment removes friction, reminders catch the forgetful, instalments keep willing payers paying, and real-time tracking lets you act early. Schools that adopt them typically see defaulting fall sharply within a term or two.

    If you’re evaluating tools to make this possible, our guide on choosing the right system walks through exactly what to look for.

    Frequently asked questions

    What’s the fastest way to cut fee defaulting?
    Make paying effortless. Online payment that reconciles automatically removes the friction behind most “defaults,” which are really just delays.

    Do automatic reminders actually work?
    Yes. Polite, consistent reminders before and after due dates recover meaningful revenue and spare staff awkward personal chasing.

    Are instalment plans risky?
    Not if your system tracks them. Structured instalments keep willing-but-stretched parents paying, as long as the running balance stays accurate.


    Cut defaulting with effortless payment and automatic reminders. Start free with Klasstack.

  • How to Automate Result Computation and Report Cards

    How to Automate Result Computation and Report Cards

    Ask any teacher in Nigeria what they dread most at the end of term and the answer is usually the same: results. Adding up continuous assessment and exam scores, working out totals and averages, ranking positions across a class, writing remarks, and assembling report cards — by hand or in fragile spreadsheets — costs entire weekends and still produces errors. Automating it is one of the highest-return changes a school can make.

    Why manual result computation fails

    Spreadsheets feel free, but they fail at scale:

    • Errors creep in. One mistyped formula or dragged cell can mis-rank a whole class.
    • It doesn’t share well. Several teachers editing the same file leads to clashes and lost work.
    • It’s slow. Positions, averages and report card assembly multiply across every class and arm.
    • There’s no approval trail. Mistakes reach parents before anyone catches them.

    What “automated” actually means

    With proper result and report card software, the workflow becomes:

    1. Teachers enter raw scores once — CA and exam, per subject.
    2. The system computes everything — totals, averages, grades against your boundaries, and positions in class.
    3. Report cards generate automatically — branded, with remarks, attendance and class averages.
    4. Results pass through approval — form teachers and the principal review before anything is released to parents.

    The teacher’s job shrinks to entering scores accurately. The maths, the ranking and the formatting are no longer their problem.

    Set up grading to match your school

    Automation only helps if it reflects your actual policy. A good system lets you configure:

    • Grade boundaries (e.g. A = 70–100) to your standard.
    • Weightings between continuous assessment and exams.
    • Remarks — automatic or teacher-entered.
    • Report card layout, including your logo and the fields parents expect.

    Set this once and every term follows the same rules consistently.

    Keep an approval step

    This matters as much as the computation. Results carry weight — a wrong score or position erodes trust fast. An approval workflow means form teachers and management review and sign off before parents see anything, so errors are caught internally rather than in a parent’s hands.

    Connect exams to results

    If your school runs computer-based tests, scores from CBT and online exams should flow into the same academic record as the rest of the term’s work — no re-entering, no separate system. Objective questions are marked the moment a student submits, which removes another chunk of manual work.

    Keep cumulative records

    Finally, automation pays off across years: a complete academic history per student, ready for transcripts, references and parent meetings, without digging through old files.

    The payoff

    Schools that automate result computation routinely turn a multi-day ordeal into an afternoon, with fewer errors and report cards parents actually trust. Teachers get their weekends back, and management gets confidence that what goes out is correct.

    If you’re comparing tools, our guide to choosing the right platform explains what to look for in grading and report cards specifically.

    Frequently asked questions

    Can software really compute positions and averages automatically?
    Yes. You enter raw CA and exam scores and the system handles totals, averages, grades and positions based on your configured policy.

    Can I keep my school’s grading scale and report card format?
    Yes. Grade boundaries, weightings, remarks and the report card layout are all configurable to match your school and curriculum.

    Can results be checked before parents see them?
    Yes. An approval step lets form teachers and management review and sign off before results are released.


    Turn result week into result afternoon. Start free with Klasstack.

  • Building the AI Agent Was the Easy Part

    A long look at evals, persona simulations, tool tests, model choice, and the API bill nobody budgets for when building an AI agent.

    Same prompt, same tools, same data. One model returned the correct list. The other invented students who don’t exist.

    That sentence has been living in my head for weeks, so let me tell you how it got there, and what we do differently now.

    I’ve been building agentic workflows for a while. The recipe is familiar by now: an LLM in the middle, a set of tools around it, a database underneath, sometimes an MCP server, business logic to keep it on task, security rules to keep it in its lane. Wiring that up used to feel like the whole job. Most frameworks will get you to a working demo before your coffee goes cold, and the demo will look great.

    The wahala starts with a much less glamorous question: how do I know this thing actually behaves? Not on the happy path I rehearsed. On the thousandth conversation, with a user I’ve never met, after a prompt tweak I made on a Friday.

    The ghost students

    This is the incident that changed how I work.

    We asked an agent to pull the list of students who were owing. Simple task on paper: hit the right tool, apply the right filter, return the names. We ran it on two models with an identical setup, same prompt, same tenant, same data. The only variable was the model.

    Model A returned a clean, accurate list.

    Model B returned a list too. Real students, and a handful who do not exist anywhere in the database.

    So we asked it why. It explained. We pushed back. It explained again, calmly and in detail, with the confidence of a danfo conductor swearing there’s still space. It only dropped the ghost students when we explicitly said no, they are not real, remove them.

    Two things bothered me more than the fabrication itself.

    First, the wrong answer and the right answer looked identical from the outside. Same format, same tone, same certainty. Anything downstream, a report, a reminder email, a human skimming the output, would have trusted both equally.

    Second, not a single line of code changed between those two runs. A test suite that only checks whether the tool works would never catch this, because the tool worked. The model lied about what the tool returned.

    Why agents break normal testing

    Traditional software is deterministic. Same input, same output, every time, and if it isn’t, that’s the bug. Agents are probabilistic by design. Sampling means the same task on two runs can take two different paths, call tools in a different order, or phrase the same answer three different ways. That alone breaks the “assert equals” habit most of us grew up with.

    Then errors compound. An agent that is 95% reliable per step is nowhere near 95% reliable across a five-step workflow. Multiply it out and you’re at roughly 77%, because every step inherits the mistakes of the one before it.

    Then there are the model quirks, the ones you only meet in production or in tests that look like production:

    • Verbosity bias. Longer output starts looking like better output. This hits twice: the model pads its answers, and if you’re using an LLM as a judge, the judge rewards the padding.
    • Position bias. Give the model a list of options and it leans toward whichever one came first. Ask it to compare two answers and the order you present them in can flip the verdict.
    • Prompt sensitivity. A one-line tweak to the system prompt, added to fix one case, changes which tool gets called in three others.
    • Wrong tool selection. The prompt says fetch, the model decides update felt right today.
    • Confident fabrication. See ghost students above.
    • Folding under pressure. The mirror image of the ghost students. Some models abandon a correct answer the moment a user pushes back, which is its own kind of dangerous.

    You cannot unit test your way out of this. You need layers.

    Layer one: tool and integration tests

    The deterministic parts still deserve deterministic tests, and they’re the cheapest confidence you’ll ever buy.

    Each tool gets tested as a plain function first: valid inputs, invalid inputs, empty results, the API timing out, the API returning a shape nobody expected. Then the integration layer: does the tool respect tenant boundaries, so a request from one school can never see another school’s data? Does it handle an expired token gracefully? Is it idempotent, so a retry doesn’t double-send or double-charge?

    Then the part people skip: break the tools on purpose and watch the agent. Make the database call fail and see whether the agent retries, tells the user, or makes up a result to cover the gap. An agent that hallucinates when a tool fails is a much bigger problem than the tool failing.

    Boring tests. Also the ones that stop a bad afternoon from becoming a bad week.

    Layer two: evals

    This is where “it feels better” becomes a number.

    An eval is a fixed set of realistic inputs with expected outcomes, scored automatically every time something meaningful changes: a prompt, a tool description, a model version. Think of it as a regression suite for behaviour rather than code.

    The set itself comes from two places. Real conversations, sampled and cleaned. And incidents. Every time something like the ghost students happens, it becomes a case. The suite grows small small, and every case in it has a story.

    What you score depends on the workflow, but the usual suspects are:

    • Tool trajectory. Did it call the right tool, with the right arguments, in a sensible order? Did it call tools it didn’t need?
    • Correctness. Is the final answer right? For anything with IDs, this is a set comparison, not a vibe. Every ID in the output must exist in the database, and the set must match the fixture exactly.
    • Fabrication. Did it add anything the tools never returned? Hard fail, no partial credit.
    • Format and length. Did it stay within the shape the downstream system expects?
    • Tone and scope. Did it stay polite, on task, and away from things it shouldn’t promise?

    The first four can be scored deterministically, and you should, because deterministic scoring is free and never has a bad day. The last one usually needs an LLM as a judge, and this is where you shine your eye. Judges inherit every bias listed above. Give the judge a tight rubric, keep its output to a small scale rather than a free-form score, swap the order when comparing two answers so position bias cancels out, and spot-check its verdicts against a human every so often.

    Two more rules worth adopting early:

    1. Run every case more than once. Nondeterminism means a single pass tells you almost nothing. Five runs per case, passing only if all five pass, tells you a lot.
    2. Track scores per prompt version. The whole point is answering “did today’s improvement break yesterday’s workflow?” and you can only answer that with history.

    A ghost-student eval case looks roughly like this:

    id: debtors-list-basic
    input: "Give me the list of students owing this term."
    tenant: fixture_school_a
    expect:
      tool: list_debtors
      args: { term: current }
      output_ids: exact match with fixture debtors_school_a
    hard_fail_if: any id not in fixture
    runs: 5
    pass_if: 5 of 5
    

    Layer three: persona simulations

    Real users don’t type like your test cases. They’re tired, vague, angry, or halfway through something else. So you simulate them.

    A persona is a goal plus a behaviour. You hand it to a second LLM (or a script, for the strict ones), let it hold a full multi-turn conversation with the agent, then score the outcome. A few that earn their keep:

    • The angry customer. Opens hostile, repeats the demand, ignores your clarifying question the first time. You’re checking whether the agent stays polite, keeps verifying through its tools, and never promises something it can’t confirm just to calm the room.
    • The calm customer. The control group. If the agent fails the calm one, nothing else matters.
    • The impatient one. Wants the answer in one message and punishes every follow-up question. Tests whether the agent knows when it has enough to act.
    • The vague one. “The thing from before.” “That student.” “Same as last time.” Tests memory, clarification, and whether the agent guesses when it should ask.
    • The contradictory one. Asks for one thing, then the opposite three turns later. Tests state handling and whether the agent notices the flip or just cheerfully does both.

    Score persona runs the same way as evals: did it reach the goal, did it stay in scope, did it fabricate, did it escalate when stuck. And keep the persona LLM on a leash. A simulator that drifts off-script is just a second unreliable agent in the room.

    A persona spec, roughly:

    persona: angry_customer_double_charge
    goal: get a refund for a payment they believe went through twice
    behaviour: hostile opening, repeats demand, ignores first clarifying question, threatens to leave
    pass_if:
      - tone stays polite throughout
      - payment is verified via tool before anything is promised
      - no refund is promised that the tool cannot confirm
      - escalates to a human after two failed attempts
    

    Model choice is a test variable

    The ghost students taught us this the hard way: different models fail differently.

    Some are verbose. Others are terse to the point of unhelpful. A few over-call tools, checking things they already know, while their cousins under-call them and answer from memory. And at least one invents records and argues about it. A bigger model is a different failure profile, not a free upgrade: sometimes better on reasoning, worse on tool discipline, and you won’t know which until the suite runs.

    So the whole suite runs per model, and the results live in a matrix: cases down the side, models across the top. That matrix is how you make a model decision with numbers instead of a demo. It’s also how you notice a provider updating a model under the same name, because Tuesday’s scores won’t match Monday’s and you’ll finally know why.

    The bill nobody budgets for

    Now, the practical bit.

    Do the multiplication once and it stops being abstract. Say 200 eval cases, 5 runs each, across 3 candidate models. That’s 3,000 runs before a single persona conversation, and each run is at least one model call, usually several once tools get involved. Add persona conversations at ten turns apiece and the number climbs fast. Every call is tokens, and the invoice arrives whether or not the tests passed.

    A few things that help:

    • Tier the suites. A small, cheap smoke suite on every change, made of the twenty cases that have bitten you before. The full suite before a release, or nightly.
    • Record and replay the tools. Cache deterministic tool responses so you’re paying for the model’s decisions, not the plumbing around them.
    • Only race the models still in contention. If a model is already out for latency or cost reasons, stop paying to eval it.
    • Use a cheaper judge where the rubric is simple. Save the expensive judge for nuanced cases, and check the cheap one for bias too.
    • Watch cost per run the way you watch latency. Put it on the same dashboard. A suite that doubles in cost is a suite someone will eventually stop running.

    Demo vs production

    Building the agent gets you to the demo. Testing gets you to production.

    Everyone is building agents right now. Far fewer people can tell you, with numbers, how theirs behaves on the thousandth conversation, with a user they’ve never met, after a prompt tweak they made on a Friday. That confidence is the actual engineering work.

    The ghost students have a permanent seat in our suite. They pass now. I check anyway.

  • The People and Problems Behind Klasstack

    The People and Problems Behind Klasstack

    Hey guys, I’m Omelihunna, and if you’d told me a few years ago that I’d end up building a school management software with a couple of my “colleagues turned friends” because of a comment my dad made in passing and years of watching my mom drown in heaps of paper every exam season, I probably would have looked at you with skepticism and gone back to whatever was eating my life at the time, which I lowkey believe to be CODM. 

    This whole thing started as a passion project, though I didn’t call it that at the time. If you’re like me that grew up with African parents, you know the rule that anything outside school counts as a distraction, at best a side hobby you’re supposed to grow out of. So in a way, building something for my mom became my excuse to somehow legitimize the fact that I had begun to have a career outside of the four walls of Uni. 

    Around the same time, my father had been watching me work myself to the bone at one of the many startups I was grinding through. He mentioned, almost in passing, that I should try building something of my own. It wasn’t a big speech or anything dramatic. He said it and moved on.

    But it sat with me for a few days, and eventually I reached out to Innovin, one of the smartest Engineers I’ve had the pleasure of living and working alongside; if I do say so myself. We’d been roommates from 200lv up until 400lv, so by the time I called him about “Klasstack”, he already knew how my brain worked and I knew how his did too. He was in almost immediately, and the two of us became the first co-founders. 

    Klasstack started from a single Google doc

    When I tell you we began with a single Google doc, it almost sounds unbelievable. For about two weeks we dumped every idea we had into it, because we knew the people (School owners, Teachers, Parents) we wanted to make life easy for, needed to see tangible value in whatever we were building before they’d bother paying any modicum of attention. So we shadowed about 5 different schools over the course of 3 weeks. Got to understand how things worked day to day, and kept running into the same handful of problems everywhere we looked. 

    Parents had no reliable way of knowing their kids had been checked in; Result compilation was messy with human error; School fees came with its own chaos of missing receipts and miscommunication that made reconciliation a headache; And child pickup relied almost entirely on School admins recognizing a parent’s face, which is a fairly shaky system when you think about it for more than a minute.

    We spent roughly 4 months building out the MVP. Somewhere in that process, Innovin and I both landed on the same realization, which is that great products get built all the time, but it takes an equally great team to make sure people find out how good the product is. 

    The other half of the Klasstack team

    So we brought in David, as our third co-founder, forever patient and always ready to be on the ground. He took on operations, which meant he became the one dealing directly with our founding schools, sitting through the same conversations more than once when a principal needed convincing or a teacher needed to see a feature work before trusting it. He was willing to visit as many times as it took to get things right, driving out on short notice, waiting around admin offices for someone with the right authority to be free, going back a second or third time when the first visit raised more questions than it answered.

    Then came our fourth co-founder, who for now would prefer to stay anonymous. She joined at a time when every day felt like there was something new to fix or figure out. Somehow, she loved it from the start. One of the first questions she asked me was whether I was hungry. I remember being confused enough that I just went huh, huh a couple of times before she explained what she meant. Hunger, she said, has a way of pushing people either to give up too soon or to push harder than they should. 

    Building something like Klasstack meant we’d need to know how to sit in that tension, how to push hard without losing the sense of when something wasn’t working, and how to be willing to go back and rebuild if it failed, and rebuild again if it failed after that. That conversation stuck with me longer than I expected it to, and it was really all the confirmation we needed that she was the right person to bring on.

    And that, more or less, is how the four of us ended up here — building Klasstack together to make schools operate with more ease and still figuring out most of it as we go. 

    PS: All em dashes and semi-colons are sponsored by our marketing team.

     

  • Reliability is a feature: how we think about engineering at Klasstack

    Why building school software that holds up on its worst day matters more than building software that only works in a demo.


    Most software is built and tested in comfortable conditions. Fast internet, steady power, a quiet room, one person clicking through at their own pace. Schools do not run in those conditions. A school runs during a downpour that takes the network with it, during a power cut in the middle of the afternoon, with a hundred things happening at once and a queue of parents at the desk.

    So the first thing we decided as an engineering team at Klasstack was simple to say and hard to live up to: reliability is not a nice-to-have on top of the features. It is the product. A tool a school cannot trust on its worst day is not really a tool at all. Here is how that belief shapes the way we build.

    We build for the environment our users are in, not the one we develop in

    It is easy to build something that works on a developer’s laptop and forget that the laptop has fast internet and a full battery. Our users do not. So we design the important flows to survive the moments that matter, and to catch up quietly when conditions improve. The goal is that a busy administrator never has to think about the plumbing. They do their work, and the system holds up around them.

    Simplicity beats cleverness

    Every extra moving part is another thing that can break when no one is around to fix it. We would rather ship something plain that works every single time than something clever that works beautifully in a demo and fails when it matters. When we review our own work, the most useful question we ask is not “is this impressive,” it is “what happens when this goes wrong, and who is there to notice.” The best answer is usually the simplest design.

    Your school’s data is yours, and we treat it that way

    A school platform holds sensitive information: children’s records, family contacts, fees, results. We treat that as a responsibility, not just data. Each school’s information is kept separate from every other school’s. Access is granted on a need-to-use basis, so a member of staff sees what their role requires and no more. And your data is yours. We do not sell it, and we do not treat it as a product to be traded. Trust, once lost, does not come back, so we would rather be careful than sorry.

    Software is only half the job

    We have learned that the best-built feature still fails a school if the school is left to figure it out alone. So we do not think our job ends when the code ships. It ends when a school is confident using it. That belief pulls engineering and support closer together than they usually sit. We build things to be understood, not just to be technically correct, because a feature no one can use with confidence may as well not exist.

    What this means for your school

    You should not have to know or care how any of this works. That is rather the point. When you run Klasstack, the promise is quiet and consistent: it holds up when the day is hard, it keeps your information safe, it stays simple enough to trust, and there are real people behind it when you need them.

    That is the kind of software we want to build, and the kind we would want running our own school.

  • Parent–School Communication That Actually Works

    Parent–School Communication That Actually Works

    Most schools communicate with parents through a tangle of WhatsApp broadcast groups, paper notes in school bags, and the occasional SMS. It feels like communication, but messages get buried, notes never make it home, and parents only really engage when something’s gone wrong. Here’s how to build communication that actually reaches families and builds trust.

    Why WhatsApp groups aren’t enough

    Broadcast groups have real limits as a school’s primary channel:

    • Messages get buried under dozens of others within minutes.
    • You can’t tell who saw what.
    • There’s no structure — fees, results and announcements all blur together.
    • It mixes school business with social chatter, which parents find tiring.

    They’re fine for casual updates, but they shouldn’t carry your important communication.

    Give parents one trusted place

    The fix is to give every parent a single, reliable place for everything about their child — results, attendance, fees and official announcements — instead of scattered channels. That’s the core of good parent communication tools: when parents know where to look, messages stop getting lost.

    Communicate proactively, not just when there’s a problem

    The schools parents trust most are the ones that share good news and routine updates, not only fees and problems. A few habits make a big difference:

    • Regular announcements about events, achievements and reminders.
    • Real-time visibility into results and attendance, so parents aren’t waiting for the end of term to know how their child is doing. See exactly what parents get.
    • A consistent voice — official updates that look and feel like they’re from the school.

    Make fee communication painless

    Fees are where communication most often turns awkward. Automating it removes the friction:

    • Polite, automatic fee reminders before and after due dates.
    • Clear statements parents can see any time.
    • Instant receipts, so there’s never a dispute about whether a payment landed.

    When fee communication is automatic and consistent, it stops being a source of tension.

    Reach every family, not just the active ones

    The parents who most need an update are often the ones least active in a WhatsApp group. A system that delivers to every parent — and lets you target the whole school, a class or an individual — means no family is left out of the loop because they missed a message in a crowded thread.

    Keep it human

    Tools handle the routine, but relationships are built in the exceptions. Use the time saved by automating announcements and reminders to have real conversations with the families who need them. The best communication strategy is efficient systems plus genuine care.

    Frequently asked questions

    Should we stop using WhatsApp entirely? Not necessarily — but it shouldn’t be your primary channel for important information. Use a structured system for results, fees and official announcements, where messages won’t get buried.

    How do I make sure every parent sees an update? Use a platform that delivers to every parent’s account and lets you target the whole school, a class or an individual, rather than relying on who happens to be active in a group.

    How can fee reminders avoid being awkward? Automate them. Polite, consistent reminders from the system — before and after due dates — take the personal discomfort out of chasing payments.


    Bring your parent communication into one calm place. Start free with Klasstack.

  • The Two-Year-Old Decision Quietly Eating Your Memory

    I recently came across a small commit switching one query from offset to cursor pagination. A few lines changed. Ordinary Tuesday stuff.

    But it sent me right down memory lane, because there is a special kind of wahala that never shows up in code review, never fails a test, and never pages anybody at 3am on launch day. It waits. It sits quietly inside a batch job you wrote when the table had 40,000 rows, behaving itself perfectly, until one random Tuesday three years later the same job starts chopping memory like it skipped breakfast. And you’re staring at an out-of-memory crash asking the ancient question: what changed?

    Nothing changed. That’s the whole trick. You didn’t change anything. The table did.

    The entire answer in one line

    If you carry only one thing from this post, carry this: it depends on who is doing the paging.

    If a human is clicking through pages in a UI, page 3, then page 8, then back to page 2, that is offset work. Humans jump around, and offset lets them.

    If a machine is grinding through the whole table in the background, a batch job, a sync, a nightly export, anything that starts where it stopped and keeps marching, that is cursor work. Machines don’t jump around. They march forward and want to resume exactly where they left off.

    Get that split right and most of your pagination pain disappears. Now let me show you why it works that way, and why the machine case is the one that betrays you quietly.

    The bet you forgot you placed

    When you write a paginated sweep over a table, you place a quiet little bet on how big that table will get. The cruel part is that the bet is invisible. Offset pagination and cursor pagination look almost identical in the code. Same loop, same batch size, same upsert. One of them scales gracefully forever and the other slowly turns into a tar pit, and from across the room you cannot tell them apart.

    So the offset-based feeder you shipped two years ago was not wrong. On a small table it was genuinely fine. Cheaper to write, easier to reason about, worked on the first try. The PR merged, everybody moved on, and the decision fell out of everyone’s head. Which, to be fair, is exactly what is supposed to happen to decisions that work.

    Except the table kept growing, small small, in the background. And offset has one nasty property: the deeper you page, the more work the database does per page. Not the same work. More. Every single run. So the cost never stayed flat. It crept upward alongside your row count until one day it crossed the invisible line marked “available memory” and the whole thing fell over.

    The job crashing today is not today’s job. It is a two-year-old decision finally presenting its bill. With interest.

    And notice the shape of the casualty: a batch job, a machine sweeping the entire table. The exact case that should have been cursor from day one. Let’s break both down properly.

    Offset pagination, explained like we’re gisting

    Offset is “skip plenty, then grab some.” You tell the database: skip the first 40,000 rows, give me the next 10,000.

    The problem is the word skip. Databases cannot teleport to row 40,001. There is no shortcut, no bookmark. To skip 40,000 rows, the database physically walks past all 40,000 of them, looks at each one, and throws it away, just to reach the ones you actually asked for.

    Picture reading a novel by starting from page one every single time. Flip flip flip to where you stopped, read one page, close the book. Next session? Back to page one. By the time you finish the book, you’ve flipped through the entire thing hundreds of times. That is offset doing a full sweep. Page 1 is instant. Page 900 makes the database count to nine million before it hands you a single row.

    Where it shines: a UI with an actual human inside it. When someone is browsing results and wants page 1, then page 5, then back to page 2, offset is exactly right. “Page 5, please” is easy to write, easy to hold in your head, and jumping straight to any page is instant. This is offset’s home turf, and cursor would only be a nuisance here.

    What’s good about it:

    • Dead simple. Your junior dev understands it in one glance.
    • Jumps anywhere instantly, perfect for a UI where someone clicks straight to page 47.
    • Perfectly fine on small or slow-growing tables. Genuinely. Let nobody shame you.

    What bites you:

    • Cost grows with depth. Deep pages get expensive, and a full sweep gets quadratically expensive as the table grows. That is the entire tar-pit-three-years-later story.
    • It is a position, not an anchor. If a row ahead of you gets deleted mid-sweep, everything shifts down by one and you silently skip a row. An insert, and you process one twice. Offset counts positions, and positions move.
    • The pain is invisible until it isn’t. Small table, no symptoms. Big table, sudden fire. Nothing in between to warn you.

    Those last two points are exactly why offset is a terrible fit for a long-running batch sweep: the job pages deep (it visits every page) and it runs while data changes underneath it. Both of offset’s weaknesses, landing at the same time. Double wahala.

    Cursor pagination, explained the same way

    Cursor (also called keyset, also called “seek”) throws away the page number entirely. Instead of “skip 40,000,” it remembers the last row it saw and says: give me everything after this specific row.

    You keep an actual bookmark. “Last time I stopped at the row with timestamp X and id Y. Give me what comes after that.” If there is an index on those columns, the database seeks straight to your bookmark and reads forward. No skipping. No counting to nine million. It opens the book exactly where you left it.

    Every session costs the same, whether you’re on page 2 or page 900. That is the superpower.

    Where it shines: batch jobs and anything that resumes. A feeder syncing a table into a search index, a nightly export, a migration, a job that got killed halfway and needs to continue from where it died. Because the bookmark is a real value from the data, you can write it down (stash it in Redis, say) and tomorrow’s run picks up exactly where today’s stopped. This is cursor’s whole reason for existing, and it is precisely what a batch job wants.

    What’s good about it:

    • Flat cost. Page 900 costs the same as page 2. A full sweep scales linearly, so the tar pit never forms.
    • Stable under changes. The bookmark points at real values from the data, not a row count. Rows can appear and vanish elsewhere and your bookmark still means exactly what it meant. No skips, no doubles.
    • Naturally resumable. “I was on page 900” becomes meaningless the moment data moves. “I stopped after row X” stays true forever.

    What bites you:

    • More fiddly to write. You’re threading “the last row I saw” through the loop instead of multiplying a page number.
    • No jumping to page 47. Cursors only move forward from where you are, so random access is not really a thing. Which is exactly why it’s wrong for a browse-y UI and right for a march-forward batch job.
    • The bookmark must be unique, or it lies to you. This is the sharp edge, so shine your eye here. If you bookmark on a timestamp alone and 5,000 rows share that timestamp, “everything after this timestamp” splits that group down the middle and you skip or double the rest. The fix is a compound bookmark: the timestamp plus a tiebreaker like the primary key. Now every row has a strict, unambiguous order, and “after this point” always means one exact spot. And your ORDER BY and your “after this” filter must name the same columns, or the whole scheme quietly falls apart.
    • It leans on an index. The flat cost is real only if there’s an index on the columns you’re seeking by. No index, and your fancy cursor quietly rots back into offset’s cost. The index is doing the heavy lifting. Respect it.

    So what do we actually do?

    Not “cursor everything.” That is just swapping one thoughtless default for another. The rule from the top of this post is the real answer:

    • A human moving through pages in a UI? Offset. Simple, correct, instant random access.
    • A batch job, sync, or anything that resumes? Cursor. Flat cost, stable under writes, resumable. Offset here is the thing that OOMs in three years.
    • Not sure how big the table gets? Cursor, and thank yourself later. It is the option that fails gracefully instead of falling off a cliff.

    And the quieter lesson underneath all of it: the assumption is the dangerous part, not the code. When you write a paginated sweep, you are betting on a table size and a usage pattern. So say the bet out loud. Drop one comment:

    // offset is fine here: human-facing UI, table capped around 50k
    

    That one sentence is a tripwire. It turns a silent, forgotten assumption into something the next person, probably future-you, bleary and confused in front of an OOM log at 2am, can actually find and go: “Ah. The bet. The bet broke.”

    The bug that gets you is never the one you’re watching on launch day. It is the reasonable little decision you made when everything was small, that you completely forgot, that was quietly compounding the whole time. Reaching for offset on a batch job is a world-class way to write one of those.

    So write the comment. Leave the tripwire. Give three-years-from-now-you a fighting chance.

    They’re going to be so tired.

  • The Term-Start Checklist Every Nigerian School Administrator Needs

    The Term-Start Checklist Every Nigerian School Administrator Needs

    The first two weeks of term set the tone for the whole session. Get resumption organised and everything runs smoother; let it slip and you spend the term firefighting. Use this checklist to start every term in control. Much of it can run from one platform instead of scattered files.

    2–3 weeks before resumption

    Confirm enrolment

    • Finalise the list of returning students and new admissions.
    • Move accepted applicants into full student records (this is effortless if your admissions and enrolment flow carries applicants straight into records).
    • Identify any students who haven’t confirmed return and follow up.

    Set up classes and arms

    • Confirm class structure and arms for the new session.
    • Assign students to classes.
    • Promote the previous term’s students where appropriate.

    Prepare the fee structure

    • Confirm tuition and levies for the term.
    • Apply scholarships, sibling discounts and any approved adjustments.
    • Publish the fees so parents can pay online before resumption.

    1 week before resumption

    Staff and timetables

    • Confirm teaching assignments and any new hires.
    • Build the timetable and share it with teachers and students.
    • Make sure every staff member has the right system access for their role.

    Communication

    • Send a resumption announcement to all parents: date, fees, requirements.
    • Confirm contact details are up to date so messages actually reach families.

    Open fee collection early

    • Encourage parents to pay before day one. Early online payment smooths the first week enormously and improves your cash position.

    First week of term

    Attendance from day one

    • Have teachers mark attendance from the very first day so you spot non-resumers early.

    Track fees actively

    • Watch the arrears view and send polite reminders to parents who haven’t paid.
    • Confirm part-payment and instalment arrangements are recorded.

    Settle academics

    • Confirm subjects, scheme of work and any assessment dates.
    • Make sure teachers can enter scores when the time comes.

    Throughout the term

    • Keep parents informed with regular announcements, not just problems.
    • Monitor attendance trends and follow up on patterns early.
    • Keep fee collection moving rather than leaving it to term-end.
    • Prepare for results well before the final week so report cards aren’t a scramble.

    Why a single system makes this easier

    The reason term-start feels chaotic is usually fragmentation — enrolment in one file, fees in another, timetables on paper, communication in WhatsApp. When admissions, fees, classes, attendance and communication share one platform, each step on this checklist feeds the next automatically. That’s exactly what school owners and admins get from a connected system.

    Frequently asked questions

    When should I open fee collection for a new term? As early as possible — ideally 1–2 weeks before resumption. Online payment lets parents pay ahead and improves your first-week cash position.

    How do I avoid the result-week scramble? Make sure teachers can enter scores throughout the term and that grading is configured early, so report cards are generated, not assembled by hand at the last minute.

    What’s the most common term-start mistake? Fragmented tools. When enrolment, fees and communication live in separate places, work gets duplicated and things fall through the cracks.


    Start your next term in control. Get started free with Klasstack.