Forward and Backward Pass, Worked by Hand
Every CPM tool runs the same two calculations: a forward pass to find the earliest each activity can start and finish, and a backward pass to find the latest each can start and finish without delaying the project. Do it by hand once on a small network and you'll never be confused by an early date, a late date, or a float value again. This post works a nine-activity network from mobilization to dry-in, shows every step, and points out the places where people usually slip up.
The convention
There are two common ways to count days in CPM, and mixing them is the most common source of off-by-one errors.
This post uses the 0-based convention:
- The first activity starts at time 0.
- Early finish = early start + duration.
- A finish-to-start successor's early start = the predecessor's early finish.
- On the backward pass, late start = late finish − duration, and a predecessor's late finish = the successor's late start.
Read "ES 0" as "the start of day 1" and "EF 5" as "the end of day 5." The other convention (1-based) starts the first activity on day 1 and uses EF = ES + duration − 1, then adds 1 to get the successor's start. Either works if you're consistent.
The network
A small single-story building, from mobilization through dry-in. All relationships are finish-to-start with zero lag. Durations are working days.
| ID | Activity | Duration | Predecessors |
|---|---|---|---|
| A | Mobilize | 2 | none |
| B | Mass excavation | 4 | A |
| C | Footings | 5 | B |
| D | Underground utilities | 3 | B |
| E | Foundation walls | 4 | C |
| F | Slab on grade | 3 | D, E |
| G | Steel erection | 8 | E |
| H | Roof deck and roofing | 4 | G |
| I | Dry-in (milestone) | 0 | F, H |
I is a zero-duration milestone. It's finished when both the slab and the roof are done.
Forward pass
Work from the start of the network toward the end. The rule: an activity's early start is the largest early finish among its predecessors. You can't start until everything you depend on is done, so the slowest predecessor wins.
A Mobilize ES = 0 EF = 0 + 2 = 2
B Mass excavation ES = EF(A) = 2 EF = 2 + 4 = 6
C Footings ES = EF(B) = 6 EF = 6 + 5 = 11
D Underground util. ES = EF(B) = 6 EF = 6 + 3 = 9
E Foundation walls ES = EF(C) = 11 EF = 11 + 4 = 15
F Slab on grade ES = max(EF(D), EF(E))
= max(9, 15) = 15 EF = 15 + 3 = 18
G Steel erection ES = EF(E) = 15 EF = 15 + 8 = 23
H Roof deck/roofing ES = EF(G) = 23 EF = 23 + 4 = 27
I Dry-in ES = max(EF(F), EF(H))
= max(18, 27) = 27 EF = 27 + 0 = 27
The project reaches dry-in at the end of day 27. That's the shortest the project can be with this logic and these durations.
Watch the merge points. At F, the slab waits for foundation walls (day 15), not utilities (day 9). At I, dry-in waits for the roof (27), not the slab (18).
Backward pass
Now work from the end back toward the start. Set the late finish of the last activity equal to its early finish (27), unless there's a contract date you're measuring against. The rule: an activity's late finish is the smallest late start among its successors. You have to be done in time for whichever successor needs you first.
I Dry-in LF = 27 LS = 27 - 0 = 27
H Roof deck/roofing LF = LS(I) = 27 LS = 27 - 4 = 23
F Slab on grade LF = LS(I) = 27 LS = 27 - 3 = 24
G Steel erection LF = LS(H) = 23 LS = 23 - 8 = 15
E Foundation walls LF = min(LS(F), LS(G))
= min(24, 15) = 15 LS = 15 - 4 = 11
D Underground util. LF = LS(F) = 24 LS = 24 - 3 = 21
C Footings LF = LS(E) = 15 LS = 15 - 5 = 10
Hold on. That last line is wrong, and it's exactly the kind of slip that happens by hand. C's late finish is 11, not 15: LS(E) is 11. Redo it:
C Footings LF = LS(E) = 11 LS = 11 - 5 = 6
B Mass excavation LF = min(LS(C), LS(D))
= min(6, 21) = 6 LS = 6 - 4 = 2
A Mobilize LF = LS(B) = 2 LS = 2 - 2 = 0
The mistake was grabbing E's late finish instead of its late start. On the backward pass you always pass back the successor's late start. If your hand calculation gives activity A a nonzero late start on a network with no constraints, you've made an error somewhere downstream. The backward pass should land back at 0.
The divergence points, B and E, are the backward-pass mirror of the merge points. B feeds both C and D; it has to finish in time for C, which needs it on day 6. E feeds both F and G; G needs it by day 15.
Float
Total float is how much an activity can slip without delaying the project finish:
TF = LS − ES = LF − EF
Free float is how much an activity can slip without delaying the early start of any successor. For finish-to-start relationships with zero lag:
FF = min(ES of successors) − EF
| ID | Activity | ES | EF | LS | LF | TF | FF |
|---|---|---|---|---|---|---|---|
| A | Mobilize | 0 | 2 | 0 | 2 | 0 | 0 |
| B | Mass excavation | 2 | 6 | 2 | 6 | 0 | 0 |
| C | Footings | 6 | 11 | 6 | 11 | 0 | 0 |
| D | Underground utilities | 6 | 9 | 21 | 24 | 15 | 6 |
| E | Foundation walls | 11 | 15 | 11 | 15 | 0 | 0 |
| F | Slab on grade | 15 | 18 | 24 | 27 | 9 | 9 |
| G | Steel erection | 15 | 23 | 15 | 23 | 0 | 0 |
| H | Roof deck and roofing | 23 | 27 | 23 | 27 | 0 | 0 |
| I | Dry-in | 27 | 27 | 27 | 27 | 0 | 0 |
Check D's free float: its only successor is F, with ES 15. D's EF is 9. FF = 15 − 9 = 6. Underground utilities can run six days late before the slab notices.
But D's total float is 15. Why the difference? Because the slab itself has 9 days of total float. D can use its own 6 days of free float, then borrow F's 9 days, for 15 total. If D actually slips 15 days, the slab has zero float left. That's the core difference between the two kinds of float, and it's covered in more depth in Total Float vs. Free Float.
The critical path is A, B, C, E, G, H, I: every activity with zero total float. In words: mobilize, dig, footings, walls, steel, roof, dry-in. The slab and underground are off the critical path because the building can't be dried in until the roof is on, and the roof is waiting on steel.
Sanity checks for hand calculations
When you check a schedule calculation, by hand or by squinting at software output, these catch most mistakes:
- Total float is never less than free float. If you get FF > TF for any activity, something is wrong.
- The first activity's LS equals its ES when there are no constraints and the backward pass starts from the early finish of the last activity.
- Activities in a straight chain share the same total float when nothing merges into or branches off the chain between them (on one calendar, with no constraints). If C and E show different total float in a simple chain, look for a missed predecessor or successor.
- At a merge, the forward pass takes the max. At a divergence, the backward pass takes the min. Swapping these is the second most common error.
- Pass back late starts, not late finishes. As shown above.
Where software differs from the hand method
A few things the software handles that your hand calculation doesn't:
- Calendars. Durations are in working days on each activity's calendar. A 5-day concrete activity on a 4-day calendar takes more calendar time than a 5-day steel activity on a 6-day calendar. Float is reported in working days of the activity's own calendar, which is why two linked activities on different calendars can show different float values even on the same path. See Calendars, Holidays, and Weather Days.
- Lags and non-FS relationships. SS and FF relationships change the formulas. An SS relationship with a 3-day lag means successor ES ≥ predecessor ES + 3. The forward pass still takes the max across all incoming relationships. See Lags and Leads.
- Constraints and progress. A "finish on or before" date can pull a late finish earlier, creating negative float. See Schedule Constraints. Once work has actual dates, the forward pass starts from the data date, not time 0.
None of these changes the basic rule. Forward pass: max of predecessors. Backward pass: min of successors. Float: late minus early.
Key takeaways
- Pick a convention (0-based or 1-based) and use it consistently. This post's convention: ES of the first activity = 0, EF = ES + duration.
- Forward pass takes the largest predecessor early finish. Backward pass takes the smallest successor late start.
- Total float = LS − ES. Free float = earliest successor ES − EF (for FS, zero lag).
- Total float is shared along a chain. Free float belongs to one activity.
- If the backward pass doesn't come back to 0 on the first activity, recheck your work.