Trainerflow Docs trainerflow.com
Examples › Starting Strength

Examples

Starting Strength

A classic novice program that listens to the athlete, adding weight to a lift only after every rep of it was made.

In your first program everything was fixed: the athlete did what the program said, and nothing they did changed what came next. Real programs don’t work like that. A good novice program adds weight as long as the athlete can handle it, and holds back when they can’t. In this example we will write exactly that, using the standard library for the exercises so that we can focus on the logic.

This is what we are building, three days a week, alternating between two workouts:

Workout A

Exercise Sets × Reps
Squat 3 × 5
Bench press 3 × 5
Deadlift 1 × 5

Workout B

Exercise Sets × Reps
Squat 3 × 5
Press 3 × 5
Deadlift 1 × 5

The rule that makes it a program and not a table: a lift goes up 2.5 kg the session after all of its reps were made. Miss a single rep and the lift stays where it is until the athlete gets it.

Process

Imports

We need four exercises and a way to rest, all of which the standard library already has:

import "coach/strength/squat.cl"
import "coach/strength/push.cl"
import "coach/strength/bend.cl"
import "coach/common/rest.cl"

squat lives in the squat library, bench_press and press in the push library and deadlift in the bend library. You can find all of them in the standard library reference.

The loads

The program has to remember where each lift is. A map is the natural place for that:

let load = [squat: 60|kg|, bench: 45|kg|, press: 30|kg|, deadlift: 80|kg|]

These are the starting weights. As the program runs, it will write new values into this map, and every session will read from it.

We will also log the same two things for every set, the reps and the weight the athlete actually did, so we keep that in a variable too:

let logged = [reps: true, weight: true]

One lift

Every lift follows the same pattern: a few sets of five at the lift’s load, then a decision. So we write it once, as a group that takes the name of the lift and the number of sets:

group work(name, sets) {
    let made = true
    repeat i:sets {
        let specs = [reps: 5, weight: load.(name)]
        let options = [location: true, set: i, sets: sets]
        let reps = 0
        if (name == "squat") {
            reps = squat(specs, logged, options).reps
        } else if (name == "bench") {
            reps = bench_press(specs, logged, options).reps
        } else if (name == "press") {
            reps = press(specs, logged, [location: true, set: i, sets: sets, variation: "Strict"]).reps
        } else {
            reps = deadlift(specs, logged, options).reps
        }
        if (reps < 5) {
            made = false
        }
    }
    ...
}

There is quite a lot going on here, so let us take it one piece at a time.

  • load.(name) looks a value up in the load map by a key that is only known when the program runs. load.squat and load.("squat") are the same thing.
  • repeat i:sets repeats the sets and counts them in i, from 1 up to sets. We pass the count on as set and sets, so that the athlete sees “set 2 of 3” on their screen.
  • Calling an exercise halts the program until the athlete has done the set. What comes back is the athlete’s feedback: .reps is the number of reps they actually did, which is not necessarily the 5 they were asked for.
  • made starts out true and turns false as soon as one set comes up short.

The decision

Once all the sets are done, the decision itself is short. If every rep was made, the lift goes up 2.5 kg:

    if (made) {
        let next = load.(name) + 2.5|kg|
        if (name == "squat") {
            load.squat = next
        } else if (name == "bench") {
            load.bench = next
        } else if (name == "press") {
            load.press = next
        } else {
            load.deadlift = next
        }
    }

Notice that we add 2.5|kg| to a measurement in kilograms. Coach knows about units, so this would work just as well if the loads were in pounds. If made is false, nothing happens at all: the load stays where it was, and the athlete gets another go at it next session.

The workouts

With work in place, the two workouts read almost exactly like the tables above:

group workout_a() {
    @title = "Workout A"
    work("squat", 3)
    work("bench", 3)
    work("deadlift", 1)
}

group workout_b() {
    @title = "Workout B"
    work("squat", 3)
    work("press", 3)
    work("deadlift", 1)
}

The @title is metadata: the athlete sees it as the name of the session they are in.

The week

Finally, three sessions a week for eight weeks, alternating A and B:

group main() {
    @title = "Starting Strength"
    @description = "Squat, press and pull three days a week, alternating two workouts. Every lift goes up 2.5 kg the session after all its reps are made."

    repeat week:8 {
        repeat day:3 {
            if ((week * 3 + day) % 2 == 0) {
                workout_a()
            } else {
                workout_b()
            }
            rest(day == 3 ? 3|day| : 2|day|)
        }
    }
}

main()

The % gives the remainder of a division, so the condition simply flips between A and B from one session to the next, across weeks too: A B A in the first week, B A B in the second and so on.

The rests make the week. After the first and second sessions the athlete rests 2|day|, i.e. one day off, and after the third 3|day|, two days off. That adds up to 7 days, so a program that starts on a Monday trains on Mondays, Wednesdays and Fridays. The time and rest page explains why.

The whole program

import "coach/strength/squat.cl"
import "coach/strength/push.cl"
import "coach/strength/bend.cl"
import "coach/common/rest.cl"

let logged = [reps: true, weight: true]

let load = [squat: 60|kg|, bench: 45|kg|, press: 30|kg|, deadlift: 80|kg|]

// Sets of five at the lift's load, which goes up 2.5 kg once every rep is made
group work(name, sets) {
    let made = true
    repeat i:sets {
        let specs = [reps: 5, weight: load.(name)]
        let options = [location: true, set: i, sets: sets]
        let reps = 0
        if (name == "squat") {
            reps = squat(specs, logged, options).reps
        } else if (name == "bench") {
            reps = bench_press(specs, logged, options).reps
        } else if (name == "press") {
            reps = press(specs, logged, [location: true, set: i, sets: sets, variation: "Strict"]).reps
        } else {
            reps = deadlift(specs, logged, options).reps
        }
        if (reps < 5) {
            made = false
        }
    }
    if (made) {
        let next = load.(name) + 2.5|kg|
        if (name == "squat") {
            load.squat = next
        } else if (name == "bench") {
            load.bench = next
        } else if (name == "press") {
            load.press = next
        } else {
            load.deadlift = next
        }
    }
}

group workout_a() {
    @title = "Workout A"
    work("squat", 3)
    work("bench", 3)
    work("deadlift", 1)
}

group workout_b() {
    @title = "Workout B"
    work("squat", 3)
    work("press", 3)
    work("deadlift", 1)
}

group main() {
    @title = "Starting Strength"
    @description = "Squat, press and pull three days a week, alternating two workouts. Every lift goes up 2.5 kg the session after all its reps are made."

    repeat week:8 {
        repeat day:3 {
            if ((week * 3 + day) % 2 == 0) {
                workout_a()
            } else {
                workout_b()
            }
            rest(day == 3 ? 3|day| : 2|day|)
        }
    }
}

main()

Try it

Paste the program into a new program’s main.cl and run the simulator.

  • Go through a few sessions leaving every field as it is. Leaving a field untouched means the athlete hit the target, so all reps are made and the squat climbs: 60 kg, then 62.5 kg, then 65 kg, a step every session.
  • Now enter 4 reps on one of the squat sets. The next session’s squat stays at the same weight, while the lifts you didn’t touch keep going up.
  • Press Plan in the header to swap the simulator for the plan and see the weeks ahead at a glance. Type actual values into it to see how the program reacts without clicking through every set.