> ## Documentation Index
> Fetch the complete documentation index at: https://docs.timeback.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Course progress

> Track student progress using masteredUnits or pctComplete

Course progress tells the platform how far a student is through your app's notion of a course. For example, "Student A is 70% through Math Grade 3."

## Two approaches

You can report progress in two ways. Both are passed as part of [completion metrics](/beta/build-on-timeback/sdk/activity-tracking/reference#activity-end-data) (client) or [`timeback.activity.record()`](/beta/build-on-timeback/sdk/activity-tracking/reference#timeback-activity-record-params) (server).

### `masteredUnits` (recommended)

If your app tracks mastery at the lesson level, pass `masteredUnits` and let the server auto-compute `pctComplete` for you. This is the recommended approach because the server maintains a running total across all of a student's submissions, using [`totalLessons`](/beta/build-on-timeback/reference/configuration#course-progress-config) — a mandatory field on every Timeback course — as the denominator.

<CodeGroup>
  ```typescript TypeScript (client) theme={null}
  await activity.end({
  	xpEarned: 80,
  	masteredUnits: 1,
  })
  ```

  ```typescript TypeScript (server) theme={null}
  await timeback.activity.record({
  	user: { email: 'student@example.com' },
  	activity: {
  		id: 'lesson-7',
  		name: 'Decimals',
  		course: { code: 'MATH-3' },
  	},
  	metrics: {
  		xpEarned: 80,
  		masteredUnits: 1,
  	},
  })
  ```

  ```python Python (server) theme={null}
  await timeback.activity.record({
      "user": {"email": "student@example.com"},
      "activity": {
          "id": "lesson-7",
          "name": "Decimals",
          "course": {"code": "MATH-3"},
      },
      "metrics": {
          "xp_earned": 80,
          "mastered_units": 1,
      },
  })
  ```
</CodeGroup>

### `pctComplete` directly

If your app already tracks overall course progress, you can pass `pctComplete` (0--100) directly when ending an activity. The server forwards it as-is — no computation needed.

<CodeGroup>
  ```typescript TypeScript (client) theme={null}
  await activity.end({
  	xpEarned: 80,
  	pctComplete: 70,
  })
  ```

  ```typescript TypeScript (server) theme={null}
  await timeback.activity.record({
  	user: { email: 'student@example.com' },
  	activity: {
  		id: 'lesson-7',
  		name: 'Decimals',
  		course: { code: 'MATH-3' },
  	},
  	metrics: {
  		xpEarned: 80,
  		pctComplete: 70,
  	},
  })
  ```

  ```python Python (server) theme={null}
  await timeback.activity.record({
      "user": {"email": "student@example.com"},
      "activity": {
          "id": "lesson-7",
          "name": "Decimals",
          "course": {"code": "MATH-3"},
      },
      "metrics": {
          "xp_earned": 80,
          "pct_complete": 70,
      },
  })
  ```
</CodeGroup>

## How `masteredUnits` works

`masteredUnits` is an **incremental** count; it represents how many new units (lessons) a student mastered during **this specific activity**, not a cumulative total.

<Warning>
  `masteredUnits` must be incremental. Sending cumulative counts will double-count mastery and
  inflate the student's progress percentage.
</Warning>

### What the server does

When `masteredUnits` is provided and `pctComplete` is omitted, the server:

1. Gets `totalLessons` from your course config
2. Adds up past and current `masteredUnits`
3. Calculates: `round(total / totalLessons * 100)`, clamped 0–100
4. Emits the result as `pctCompleteApp` in the [`ActivityCompletedEvent`](/beta/build-on-timeback/reference/events#activitycompletedevent)

<Note>
  If `pctComplete` is provided alongside `masteredUnits`, the server uses `pctComplete` as-is and
  does not run the auto-computation. The explicit value always wins.
</Note>

## Configuration

Every Timeback course includes `totalLessons` in its `metadata.metrics`. The total number of lessons in the course. This is the denominator used for auto-computation.

```json timeback.config.json theme={null}
{
	"courses": [
		{
			"subject": "Math",
			"grade": 3,
			"courseCode": "MATH-3",
			"metadata": {
				"metrics": {
					"totalLessons": 10
				}
			}
		}
	]
}
```

## Example walkthrough

Assume a course has `totalLessons: 10`.

A student completes three activities over time:

| Submission | `masteredUnits` | Historical sum | Total mastered | `pctCompleteApp` |
| ---------- | --------------- | -------------- | -------------- | ---------------- |
| 1st        | 3               | 0              | 3              | 30               |
| 2nd        | 2               | 3              | 5              | 50               |
| 3rd        | 2               | 5              | 7              | 70               |

After the third submission, the student is 70% through the course.

## Best practices

<AccordionGroup>
  <Accordion title="Send masteredUnits as an incremental count">
    When a student masters one lesson, send `masteredUnits: 1`. If they master two lessons in
    the same activity session, send `masteredUnits: 2`. The value always represents how many
    **new** units were mastered during **this** activity — never a running total.
  </Accordion>

  <Accordion title="Attribute mastery for a lesson exactly once">
    If a student replays a lesson they already mastered, do not send `masteredUnits` again for
    that lesson. Double-counting inflates the student's progress percentage because the server
    sums all historical values.
  </Accordion>

  <Accordion title="Omit masteredUnits when no lessons were mastered">
    If no lessons were mastered during the activity, omit `masteredUnits` entirely (or send
    `0`). The server only runs the auto-computation when `masteredUnits` is a number greater
    than zero.
  </Accordion>
</AccordionGroup>

## Next steps

<CardGroup cols={2}>
  <Card title="Custom Activities reference" icon="brackets-curly" href="/beta/build-on-timeback/sdk/activity-tracking/reference">
    Full API for `activity.end()` and `timeback.activity.record()`
  </Card>

  <Card title="Events" icon="bolt" href="/beta/build-on-timeback/reference/events">
    Caliper event schemas including `ActivityCompletedEvent`
  </Card>

  <Card title="Configuration" icon="gear" href="/beta/build-on-timeback/reference/configuration">
    `timeback.config.json` reference including `totalLessons`
  </Card>

  <Card title="Stateful activities" icon="arrows-rotate" href="/beta/build-on-timeback/sdk/activity-tracking/stateful">
    Multi-session activities where completion is recorded server-side
  </Card>
</CardGroup>
