Featured resource
2026 Tech Forecast
2026 Tech Forecast

1,500+ tech insiders, business leaders, and Pluralsight Authors share their predictions on what’s shifting fastest and how to stay ahead.

Download the forecast
  • Lab
    • Libraries: If you want this lab, consider one of these libraries.
    • Core Tech
Labs

Reproducible Failure to Verified TypeScript Fix

GloboTicket's admin dashboard runs a nightly batch job that replays a queue of customer orders through a checkout ledger to compute each order's total. A support ticket reports that whenever more than one order runs in the same batch, every order after the first comes out overcharged by a growing amount. The calculation module builds each order's total with a running accumulator, and something is letting one order's numbers bleed into the next. Your job is to make the failure happen the same way every time, follow it from the compiled JavaScript back to the original TypeScript line, pause at the moment the accumulator goes wrong to see exactly what it holds, cut the framework's own call frames out of the trace so only your code is left, fix the accumulator so each order starts clean, and write up the evidence that proves it.

Lab platform
Lab Info
Level
Intermediate
Last updated
Nov 04, 2026
Duration
45m

Contact sales

By clicking submit, you agree to our Privacy Policy and Terms of Use, and consent to receive marketing emails from Pluralsight.
Table of Contents
  1. Challenge

    Step 1: Meet the Overcharging Nightly Batch Job

    The ticket

    GloboTicket's admin dashboard runs a nightly batch job that replays every order sitting in the checkout queue through a ledger calculator, calculateOrderTotal, to work out what each customer owes. A support ticket landed this morning: whenever a batch contains more than one order, every order after the first comes back overcharged, and the overcharge grows the longer the batch runs. The first order in any batch is always correct, which is the strongest clue you have before you open a debugger. info> Feeling stuck? Check out the matching solution/stepN/ folder for the step you're on to see a working implementation. Give it a try on your own first. The solution folder is your safety net, not your starting point. ### Where the money is calculated

    src/checkoutLedger.ts exposes calculateOrderTotal(order). For each order it walks the line items, builds up a subtotal, applies the order's discount rate, and rounds the result to cents. That subtotal is a running accumulator, a value that grows as it walks the items. Accumulators are the right tool for this job, but only if each order gets a fresh one. If the accumulator is declared in the wrong place, one order's total can start from where the last order's total left off, and every order after the first inherits a debt it never asked for. ### Why 'just re-run it' isn't enough

    A bug that only shows up on the second order of a multi-order batch is easy to lose: run one order at a time in a REPL and it looks perfectly fine. You need a harness that replays the same sequence of orders, in the same order, every time, so the failure is not a one-off you half-remember but a fact you can point at. That harness lives in src/batchRunner.ts, and building it is the first thing you'll do. ### The plan

    This lab follows the failure through five moves:

    1. Reproduce: build a batch runner that replays a fixed order queue so the overcharge happens identically on every run.
    2. Inspect: capture a breakpoint-style snapshot of the accumulator's locals and call stack at the moment it goes wrong.
    3. Locate: map the compiled stack frame where things go wrong back to the exact line of TypeScript that produced it.
    4. Isolate: strip framework-owned frames out of the raw stack trace so only your own code is left to blame.
    5. Fix and prove: give each order its own accumulator, then assemble a report that ties the culprit frame, the captured evidence, and the corrected totals together.

    Each step builds the tool the next one needs. By the time you reach the fix, you won't be guessing at a cause; you'll already have it in hand. ### Running the application

    The lab includes a small dashboard, src/App.tsx, that calls the functions you write. In the Terminal, confirm you're in the folder that contains package.json, then start it:

    npm run dev
    

    The dev server listens on port 5173. Switch to the Web Browser tab to see the Run Nightly Batch and Diagnose & Verify buttons. Until you finish the tasks, parts of the page show empty states, which is expected.

    warning> The lab has a single terminal, so the running server keeps it busy. Press Ctrl+C to stop the server before you run any of the commands in the next section, then start it again when you want to look at the page. ### Checking your progress as you work

    Each task is graded automatically when you complete it, but you can run the same checks yourself at any time. In the Terminal, run any of these:

    • One task, with the task name in quotes:

      bash runTest.sh "task 2.1"
      
    • The whole suite:

      npx --no-install vitest run --reporter=dot
      
    • The TypeScript type check on its own:

      npm run typecheck
      

    Expect failures at first. The starter code is unfinished on purpose, so each task's check fails until you complete that task. If you run the whole suite before you start, you see many failures. That is the normal starting point and does not mean your setup is wrong.

    As you complete each task, its check changes from failing to passing. When all ten tasks are done, the whole suite passes.

    When a task passes, the summary shows Tests 1 passed. When it fails, the message after AssertionError names the task and tells you what to change. A clean type check prints no errors after the two npm lines. The task checks don't look at types, so run the type check yourself before you finish a task.

  2. Challenge

    Step 2: Reproduce the Checkout Ledger Failure on Demand

    Making the failure repeatable

    Before you can debug anything, you need the failure to happen the same way twice. src/batchRunner.ts already exports SAMPLE_ORDERS, a fixed queue of orders that mirrors what a real nightly batch would see. Your job is to replay that exact queue through calculateOrderTotal in the same order every time, so the overcharge is no longer something you half-remember from one run, but something you can reproduce on demand.

    Return the array instead of logging it, so you and the checks can inspect exactly which totals a run produced. A total's position in the array always identifies the order that produced it.

    To check your work, use the commands in "Checking your progress as you work" in Step 1, and press Ctrl+C first if the dev server is running. ### Seeing where the numbers diverge

    A single array of totals tells you that something is wrong, but not where it starts going wrong. Tagging each total with the ID of the order that produced it turns the batch's output into a trace you can read top to bottom, watching the moment the first order's clean total gives way to the second order's inflated one.

    Keep this pass separate from runNightlyBatch so the trace reads on its own. ### Watching the overcharge happen

    The ledger in src/checkoutLedger.ts is already running with the bug in it, so the batch you just built can show you the failure. Start the application as described in Step 1, open the Web Browser tab, and click Run Nightly Batch. The queue holds three orders, and their correct totals are $22.50, $19.00 and $32.00. You will see order-1 come out right, then order-2 at $42.75 and order-3 at $77.00.

    Reload the page and click once more: you get the same three numbers. Click twice without reloading and the totals climb even higher, because nothing ever resets the accumulator. That is the failure you will trace, inspect and fix in the next steps.

  3. Challenge

    Step 3: Capture Breakpoint Evidence from the Ledger

    A breakpoint you can replay

    A real debugger lets you pause at a line and inspect the locals and the call stack right there. You can get the same value out of a plain function: given a label, a bag of locals, and a call stack, freeze all three into one object you can inspect, store, or compare later. That object is a BreakpointSnapshot, and captureBreakpoint in src/breakpoint.ts is how you build one.

    Copying locals and callStack means a later change to the caller's variables can't alter a snapshot you've already captured.

    To check your work, use the commands in "Checking your progress as you work" in Step 1, and press Ctrl+C first if the dev server is running. ### Wiring the snapshot into the ledger's own evidence

    checkoutLedger.ts's applyDiscount step already records a DiscountStepRecord (the order ID, the subtotal it was handed, the discount rate and a raw call stack) into a log every time it runs, via getDiscountStepLog(). That log is real evidence pulled straight from a live run, not a guess. captureLedgerBreakpoints, still in src/breakpoint.ts, turns every record in that log into a full BreakpointSnapshot using captureBreakpoint, giving you one inspectable snapshot per order in the batch.

    Reading the real log, rather than guessing, means each snapshot carries the values that were live at that point, in the order the batch ran.

  4. Challenge

    Step 4: Read the Stack Trace and Name the Culprit

    From compiled line to source line

    When a batch runs, any stack trace it produces points at compiled JavaScript, not the TypeScript you actually wrote. A source map is the bridge back: a table that records, for a given compiled file/line/column, which original file, line, column and symbol produced it. src/sourceMap.ts already has that table built for you as COMPILED_TO_SOURCE_MAP; you just need the lookup that uses it.

    Returning undefined for a miss keeps an unmapped frame distinguishable from a real match.

    To check your work, use the commands in "Checking your progress as you work" in Step 1, and press Ctrl+C first if the dev server is running. ### Cutting the noise out of a stack trace

    A raw call stack captured during a test run is full of frames that belong to the test runner and language internals, not to your code. None of those frames caused the bug, and reading past them to find the one that did wastes the evidence your snapshots hold. src/stackFilter.ts already lists the marker strings that identify framework-owned frames in FRAMEWORK_MARKERS; you'll use them to clear those frames out.

    Return a new array so the original raw trace stays available when you need the full record. ### Naming the one frame responsible

    Once the framework frames are gone, what's left should point at your own code, specifically at the function where the accumulator's contaminated value is actually read: applyDiscount. Turning a filtered trace into a single named culprit frame is what closes the loop between 'the failure happens' and 'here is the line responsible for it.'

    Filtering first means framework noise can never be picked as the culprit, and the fallback returns a best answer instead of nothing.

  5. Challenge

    Step 5: Correct the Accumulator and Prove the Fix

    Your evidence now points at one cause, and this step closes the loop: you fix the bug, then prove the fix with the same evidence you used to find it. You remove the shared accumulator, then prove the fix in two ways: with a report that ties the culprit frame, the snapshots and the corrected totals together, and with a recomputed batch that shows nothing leaks between orders. By the end, the dashboard reports the corrected totals as verified.

    The fix itself

    Everything so far has pointed at the same thing: calculateOrderTotal's subtotal is declared somewhere that survives between calls, so order two starts adding on top of order one's total instead of from zero. The fix is narrow: move the accumulator's declaration inside the function so it's created fresh on every call. It's still the fix, not a workaround, because it removes the shared state entirely rather than resetting it defensively.

    To check your work, use the commands in "Checking your progress as you work" in Step 1, and press Ctrl+C first if the dev server is running. ### Writing down the proof

    A fix that isn't documented is a fix someone else has to rediscover the hard way. src/diagnosisReport.ts is where the culprit frame from step 4, the breakpoint evidence from step 3, and the corrected totals from the fix all come together into one object, a record that says not just 'it's fixed' but 'here is the exact evidence that it's fixed.'

    Setting verified only when all three inputs are complete means a report can't claim to be verified on thin evidence. ### Proving it holds under a second run

    The strongest evidence that the shared-accumulator bug is gone isn't a single correct-looking total; it's running the batch twice and getting the identical totals both times. If any state were still leaking between orders, a second run would disagree with the first. verifyIndependentTotals makes that comparison explicit, giving the diagnosis report a check it can point to rather than an assertion to take on faith.

    Comparing within a small tolerance, rather than exact equality, avoids false failures from floating-point rounding.

  6. Challenge

    Step 6: Run the Application

    Every task is done, and each one was graded as you completed it. What you haven't done yet is watch the whole application run at once, which is the only place the pieces you wrote separately become a single working program.

    Run the final checks

    Stop the dev server with Ctrl+C if it is still running, then run these commands in the Terminal to confirm that the types and the whole suite pass (see Step 1 for the other commands):

    npm run typecheck
    npx --no-install vitest run --reporter=dot
    

    The suite ends with Tests 17 passed (17), and the type check prints no errors.

    If you want to see what a failure looks like, break something on purpose, watch the matching check fail and then put it back. ### Start the application

    In the Terminal, start the dev server again:

    npm run dev
    

    Switch to the Web Browser tab and click Run Nightly Batch. The totals now read $22.50, $19.00 and $32.00, and they stay the same no matter how many times you click. Click Diagnose & Verify to see the report your code builds: a culprit frame that names applyDiscount, the breakpoint evidence and Verified: yes.

    The server reloads on save. When you are finished, press Ctrl+C in the terminal to stop it. ### Congratulations

    You turned a vague overcharging ticket into a verified fix. You replayed a fixed queue of orders to make the failure repeatable, mapped a compiled frame back to its TypeScript source, captured breakpoint-style snapshots of the accumulator, filtered out framework frames to name the culprit, and moved the accumulator inside the function so every order started clean.

    The same habit works on any shared-state bug: make the failure repeatable, gather evidence where it goes wrong and prove the fix with the same evidence.

About the author

Pluralsight’s AI authoring technology is designed to accelerate the creation of hands-on, technical learning experiences. Serving as a first-pass content generator, it produces structured lab drafts aligned to learning objectives defined by Pluralsight’s Curriculum team. Each lab is then enhanced by our Content team, who configure the environments, refine instructions, and conduct rigorous technical and quality reviews. The result is a collaboration between artificial intelligence and human expertise, where AI supports scale and efficiency, and Pluralsight experts ensure accuracy, relevance, and instructional quality, helping learners build practical skills with confidence.

Real skill practice before real-world application

Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.

Learn by doing

Engage hands-on with the tools and technologies you’re learning. You pick the skill, we provide the credentials and environment.

Follow your guide

All labs have detailed instructions and objectives, guiding you through the learning process and ensuring you understand every step.

Turn time into mastery

On average, you retain 75% more of your learning if you take time to practice. Hands-on labs set you up for success to make those skills stick.

Get started with Pluralsight