Project Management & Agile Module 4 – Scheduling: networks, critical path and Gantt charts
Resource levelling, crashing and fast-tracking
Find over-allocated people and level a plan, crash a schedule to a deadline at the lowest cost a day, fast-track with care, and see when more people slow work.
What you will learn
- Detect resource over-allocation in a histogram and level or smooth it
- Calculate cost slopes and crash a schedule to a deadline at the lowest added cost
- Apply fast-tracking to activities and state the risk the overlap adds
- Explain why adding people can slow a late project and how buffers protect a plan
Before you start
What you’ll learn · Before you start
What you will learn
- Detect resource over-allocation in a histogram and level or smooth it
- Calculate cost slopes and crash a schedule to a deadline at the lowest added cost
- Apply fast-tracking to activities and state the risk the overlap adds
- Explain why adding people can slow a late project and how buffers protect a plan
Before you start
On this page
A data-centre move is planned at 30 working days, and the company wants it done in 25. Five days can be bought for 25 thousand, or three of them for nothing by overlapping two activities, or none at all if the same engineer turns out to be booked twice in week one. This lesson works through each of those answers with the same move.
You will find over-allocated people in a resource histogram and level the plan, crash the schedule one cheapest day at a time, fast-track with a clear view of the risk, and explain why adding people late often makes a project later.
A histogram shows who is booked twice, and levelling removes it
The move has eight main activities and two small ones, and two engineers, Ravi and Meera; the new room is the facilities contractor’s work. GAO’s schedule guide makes assigning resources to all activities its third best practice for exactly this reason: a schedule without resources cannot show that it needs one person in two places. The program below counts, for every working day, how many activities each engineer has:
"""Finds where the data-centre move needs one engineer twice at once, then levels the plan in two ways.
Ravi and Meera are the move's two engineers; the new room (C) is the facilities contractor's. Levelling here is
serial: activities are placed in order of early start, the one with less total float first, and each waits until
its engineer is free. The second way gives the runbook to Meera, who is idle while the room is being prepared.
"""
# id: (name, days, engineers, predecessors); the move of the crashing example, with two short tasks added (I, J).
MOVE = {
"A": ("Audit servers and applications", 3, ["Ravi"], []),
"B": ("Order racks and cabling", 5, ["Meera"], ["A"]),
"C": ("Prepare the new room", 10, [], ["A"]),
"J": ("Label the cables", 1, ["Meera"], ["A"]),
"D": ("Install racks and cabling", 5, ["Meera"], ["B", "C", "J"]),
"E": ("Back up and test restores", 12, ["Ravi"], ["A"]),
"I": ("Update the runbook", 4, ["Ravi"], ["A"]),
"F": ("Move the servers", 6, ["Ravi", "Meera"], ["D", "E", "I"]),
"G": ("Test network and applications", 4, ["Meera"], ["F"]),
"H": ("Hand over and close", 2, ["Ravi"], ["G"]),
}
PEOPLE = ["Ravi", "Meera"]
def schedule(net, levelled=False):
"""Early start of every activity; when levelled, an activity also waits until its engineers are free."""
es, busy = {}, {p: set() for p in PEOPLE}
end = lambda a: es[a] + net[a][1]
# total float of the unlevelled plan decides who goes first when two activities want one engineer
order = sorted(net, key=lambda a: (start_of(net, a), float_of(net, a)))
for a in order:
_, days, who, preds = net[a]
s = max([end(p) for p in preds], default=0)
while levelled and any(d in busy[p] for p in who for d in range(s, s + days)):
s += 1
es[a] = s
for p in who:
busy[p].update(range(s, s + days))
return es
def start_of(net, a, memo={}):
key = (id(net), a)
if key not in memo:
memo[key] = max([start_of(net, p) + net[p][1] for p in net[a][3]], default=0)
return memo[key]
def float_of(net, a):
finish = max(start_of(net, b) + net[b][1] for b in net)
succ = [b for b in net if a in net[b][3]]
late_finish = min([late_start(net, b, finish) for b in succ], default=finish)
return late_finish - net[a][1] - start_of(net, a)
def late_start(net, a, finish):
succ = [b for b in net if a in net[b][3]]
return min([late_start(net, b, finish) for b in succ], default=finish) - net[a][1]
def histogram(net, es, title):
finish = max(es[a] + net[a][1] for a in net)
print(f"{title}: finish after {finish} working days")
print(" " + "".join(str((d + 1) // 10 or " ") if (d + 1) % 10 == 0 else " " for d in range(finish)))
print(" " + "".join(str((d + 1) % 10) for d in range(finish)))
for p in PEOPLE:
load = [sum(1 for a in net if p in net[a][2] and es[a] <= d < es[a] + net[a][1]) for d in range(finish)]
row = "".join("." if n == 0 else str(n) for n in load)
over = [d + 1 for d, n in enumerate(load) if n > 1]
note = "" if not over else f" over-allocated on day {over[0]}" if len(over) == 1 else f" over-allocated on days {over[0]}-{over[-1]}"
print(f" {p:<6}{row}{note}")
return finish
base = schedule(MOVE)
histogram(MOVE, base, "Early dates, engineers ignored")
print("Total float: " + ", ".join(f"{a} {float_of(MOVE, a)}" for a in MOVE if MOVE[a][2] and float_of(MOVE, a)))
print()
levelled = schedule(MOVE, levelled=True)
histogram(MOVE, levelled, "Levelled by delaying")
for a in MOVE:
if levelled[a] != base[a]:
moved = levelled[a] - base[a]
print(f" {a} ({MOVE[a][0]}) starts {moved} day{'s' if moved != 1 else ''} later; its total float was {float_of(MOVE, a)}")
print()
swap = dict(MOVE)
swap["I"] = ("Update the runbook", 4, ["Meera"], ["A"])
both = schedule(swap, levelled=True)
histogram(swap, both, "Levelled with the runbook given to Meera")
for a in swap:
if both[a] != base[a]:
moved = both[a] - base[a]
print(f" {a} ({swap[a][0]}) starts {moved} day{'s' if moved != 1 else ''} later; its total float was {float_of(MOVE, a)}") Output
Early dates, engineers ignored: finish after 30 working days
1 2 3
123456789012345678901234567890
Ravi 111222211111111...111111....11 over-allocated on days 4-7
Meera ...21111.....111111111111111.. over-allocated on day 4
Total float: B 5, J 9, E 3, I 11
Levelled by delaying: finish after 31 working days
1 2 3
1234567890123456789012345678901
Ravi 1111111111111111111111111....11
Meera ...111111....11111.1111111111..
J (Label the cables) starts 5 days later; its total float was 9
I (Update the runbook) starts 12 days later; its total float was 11
F (Move the servers) starts 1 day later; its total float was 0
G (Test network and applications) starts 1 day later; its total float was 0
H (Hand over and close) starts 1 day later; its total float was 0
Levelled with the runbook given to Meera: finish after 30 working days
1 2 3
123456789012345678901234567890
Ravi 111111111111111...111111....11
Meera ...1111111111111111111111111..
J (Label the cables) starts 5 days later; its total float was 9
I (Update the runbook) starts 6 days later; its total float was 11
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 level.py
The first histogram shows a 2 for Ravi on days 4 to 7, where backing up the servers and updating the runbook overlap, and for Meera on day 4, where ordering racks and labelling cables overlap. resource levelling: Moving or reassigning scheduled work so that no person or resource is booked for more than it can do at once; it may move the finish when a delay exceeds float. moves work until nobody is booked twice. GAO’s guide describes it as adjusting the starts of activities, or who does them, to fit the resources available, and notes that the activities moved are usually those with the most free float.
The second histogram is levelled by delaying. Labelling the cables moved 5 days, inside its 9 days of float, so that move alone changed no date beyond it: levelling that stays within float is often called resource smoothing: Levelling that delays work only within its float, so that over-allocation is removed and no other date moves.. The runbook had to wait 12 days for Ravi, one more than its 11 days of float, so moving the servers, testing and handing over all start a day later, and the finish becomes 31. The third histogram gives the runbook to Meera instead, who is free while the contractor prepares the room: the finish stays at 30, but Meera now has no free day from day 4 to day 28.
Levelling a schedule that has no resources in it
Wrong: assuming the dates are possible because every bar fits before the deadline. Right: name who does each activity, look at each person’s histogram, and level before the baseline is saved. A 2 in a histogram is a date that cannot be kept.
Crashing buys days on the critical path, cheapest day first
crashing: Shortening critical activities by adding people, equipment or overtime, at a cost for each day saved. shortens critical activities by adding people, equipment or overtime. Each one has a shortest possible duration and a cost slope: The cost of each day saved by crashing an activity: the extra cost of its shortest version divided by the days it saves., the cost of each day saved:
In words: what the shortest version costs above the normal one, divided by the days it saves. Preparing the new room normally takes 10 days; if 7 days cost 12 thousand more, its slope is 12 ÷ 3 = 4 thousand a day. The method is to shorten the project one day at a time, each time at the cheapest place on the critical path, and to recompute the paths after every step, because a cheaper path may become critical:
"""Crashes an invented data-centre move from 30 to 25 working days at the lowest added cost, then tries fast-tracking.
Each activity has a normal duration, the shortest duration more people or overtime can reach, and the cost of each
day saved, its cost slope = (crash cost - normal cost) / (normal duration - crash duration), in thousands. Every step
shortens the project by one day at the lowest cost: one activity that sits on every critical path, or one activity on
each critical path. A search of every combination of crashed days confirms that no cheaper plan exists.
"""
from itertools import product
# id: (name, normal days, shortest days, cost slope in thousands a day, predecessors)
MOVE = {
"A": ("Audit servers and applications", 3, 2, 9, []),
"B": ("Order racks and cabling", 5, 3, 2, ["A"]),
"C": ("Prepare the new room", 10, 7, 4, ["A"]),
"D": ("Install racks and cabling", 5, 4, 6, ["B", "C"]),
"E": ("Back up and test restores", 12, 9, 3, ["A"]),
"F": ("Move the servers", 6, 4, 8, ["D", "E"]),
"G": ("Test network and applications", 4, 3, 5, ["F"]),
"H": ("Hand over and close", 2, 2, 0, ["G"]),
}
INDIRECT = 7 # the old site's lease and the move team's overheads, thousands a working day
def paths(net):
"""Every path from an activity with no predecessor to one with no successor."""
succ = {a: [b for b in net if a in net[b][4]] for a in net}
out = []
def walk(a, path):
if not succ[a]:
out.append(path)
for b in succ[a]:
walk(b, path + [b])
for a in net:
if not net[a][4]:
walk(a, [a])
return out
ALL = paths(MOVE)
length = lambda dur, p: sum(dur[a] for a in p)
def crash_to(target):
dur = {a: MOVE[a][1] for a in MOVE}
steps, total = [], 0
while max(length(dur, p) for p in ALL) > target:
top = max(length(dur, p) for p in ALL)
critical = [p for p in ALL if length(dur, p) == top]
options = []
# one activity on every critical path, or one activity on each critical path
for a in MOVE:
if dur[a] > MOVE[a][2] and all(a in p for p in critical):
options.append((MOVE[a][3], (a,)))
if len(critical) > 1:
for pick in product(*[[a for a in p if dur[a] > MOVE[a][2]] for p in critical]):
if len(set(pick)) == len(pick):
options.append((sum(MOVE[a][3] for a in pick), tuple(sorted(pick))))
cost, pick = min(options)
for a in pick:
dur[a] -= 1
total += cost
steps.append((top - 1, pick, cost, total, ["".join(p) for p in critical]))
return steps, dur, total
steps, dur, total = crash_to(25)
print("Normal plan: 30 working days; paths", ", ".join(f"{''.join(p)} {length({a: MOVE[a][1] for a in MOVE}, p)}" for p in ALL))
print(f"{'Days':>4} {'Crash':<6}{'Cost':>5}{'Total':>7} Critical before the step")
for days, pick, cost, run, crit in steps:
print(f"{days:>4} {'+'.join(pick):<6}{cost:>5}{run:>7} {', '.join(crit)}")
# Every combination of crashed days, to check that the steps found the cheapest plan for each deadline.
ranges = [range(MOVE[a][1] - MOVE[a][2] + 1) for a in MOVE]
best = {}
for cut in product(*ranges):
d = {a: MOVE[a][1] - c for a, c in zip(MOVE, cut)}
days = max(length(d, p) for p in ALL)
cost = sum(c * MOVE[a][3] for a, c in zip(MOVE, cut))
best[days] = min(best.get(days, cost), cost)
combos = 1
for r in ranges:
combos *= len(r)
print(f"Checked {combos} combinations: cheapest added cost for 25 days is {best[25]} thousand")
print(f"With overheads of {INDIRECT} thousand a day, total change against 30 days:")
for days in range(30, 24, -1):
added = best[days]
print(f" {days} days: crashing {added:>2}, overheads saved {(30 - days) * INDIRECT:>2}, net {added - (30 - days) * INDIRECT:+d}")
# Fast-tracking: D may start 3 days before C finishes (a lead), instead of waiting for the whole room.
es = {}
for a, (_, days, _, _, preds) in MOVE.items():
es[a] = max([es[p] + MOVE[p][1] - (3 if (p, a) == ("C", "D") else 0) for p in preds], default=0)
finish = max(es[a] + MOVE[a][1] for a in MOVE)
print(f"Fast-tracked instead (D starts 3 days before C ends): {finish} working days, no crashing cost") Output
Normal plan: 30 working days; paths ABDFGH 25, ACDFGH 30, AEFGH 27 Days Crash Cost Total Critical before the step 29 C 4 4 ACDFGH 28 C 4 8 ACDFGH 27 C 4 12 ACDFGH 26 G 5 17 ACDFGH, AEFGH 25 F 8 25 ACDFGH, AEFGH Checked 1152 combinations: cheapest added cost for 25 days is 25 thousand With overheads of 7 thousand a day, total change against 30 days: 30 days: crashing 0, overheads saved 0, net +0 29 days: crashing 4, overheads saved 7, net -3 28 days: crashing 8, overheads saved 14, net -6 27 days: crashing 12, overheads saved 21, net -9 26 days: crashing 17, overheads saved 28, net -11 25 days: crashing 25, overheads saved 35, net -10 Fast-tracked instead (D starts 3 days before C ends): 27 working days, no crashing cost
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 crash.py
The first three days came from the room at 4 thousand each, which brought the backup chain, A, E, F, G, H, level with it at 27 days. From then on both paths were critical, so a day had to come off both: testing, which both paths share, for 5, then moving the servers for 8, rather than one activity on each path for 6 + 3 = 9. The total is 25 thousand, and a search of all 1,152 combinations found nothing cheaper.
The cheapest deadline is not always the shortest. The move’s overheads run at 7 thousand a working day, so every day crashed for less than 7 saves money overall. The program’s last table shows the total falling to its lowest at 26 days, 11 thousand below the 30-day plan; the 25th day costs 8 and saves only 7. NPTEL’s Project Management for Managers course from IIT Roorkee gives a whole week, its tenth, to crashing networks.
Checkpoint
Check: Over-allocation and levelling · Crashing and cost slopes
Fast-tracking overlaps work and pays in risk instead of money
fast-tracking: Shortening a schedule by overlapping activities that were planned one after the other, which costs little money but adds the risk of rework. turns links that run one after another into partial overlaps: in GAO’s words, finish-to-start logic is reduced to start-to-start to force parallel work. In the move, installing racks may start 3 days before the room is finished, on the part of the floor already done. The program’s last line shows the result, 27 days at no crashing cost. The price is risk instead of money: if the room’s cooling layout changes in its last 3 days, racks already bolted down are moved again. GAO’s table of recovery techniques lists these side effects for each technique, and the choice depends on the project. Try it on one of your own:
Answer the questions above: the table then marks what fits your case and says why the rest does not.
| Technique | Added cost | Risk to quality | What it needs |
|---|---|---|---|
| Crashing | Poor money for every day: 25 thousand took the move from 30 to 25 days | Mixed new or rushed people can lower quality, as GAO's guide warns | Mixed critical activities that more people really make shorter |
| Fast-tracking | Good none up front: the move fell to 27 days with D overlapping C | Poor rework if the overlapped work changes; work meant in sequence runs in parallel | Mixed work that can safely start before its predecessor ends |
| Reducing scope | Good usually saves money as well as time | Mixed the dropped work may be what the project was for | Poor the sponsor's agreement through change control |
| Overtime | Mixed costs more per hour than normal time | Poor morale and quality fall as overtime goes on | Mixed people willing, and allowed, to work longer for a short spell |
| Moving the date | Mixed overheads run on: 7 thousand a day in the move | Good the work is done as planned | Poor a deadline that is not fixed by law or by contract |
How the answers decide
- “Is there money to spend on winning back days?” No, the budget is fixed: rules out Crashing and adds a caveat to Overtime. Crashing buys days with people or equipment, so with no budget it is out; overtime still costs extra per hour.
- “Could some critical work start before the work it waits for has finished, without much rework?” No, each step needs the last one finished: rules out Fast-tracking. Fast-tracking only helps where a successor can safely start before its predecessor has finished.
- “Is the finish date fixed by a contract, a law or an event?” Yes, it cannot move: rules out Moving the date and adds a caveat to Reducing scope. A fixed date leaves the work to change: scope can shrink only if the sponsor agrees through change control.
With money and no safe overlap, crashing fits; with a safe overlap and a fixed budget, fast-tracking does; with both closed and a fixed date, only scope is left. Many plans combine them: the move could fast-track D, win 3 days for nothing and crash only the last 2.
More people can make a late project later, and buffers protect the plan instead
Crashing assumes that more people shorten an activity, which is not always true. Fred Brooks put it in one line in The Mythical Man-Month in 1975: “Adding manpower to a late software project makes it later.” New people need time to learn the work, and someone who knows it has to teach them; and the number of pairs who may need to talk grows with :
| People on the work | Pairs who may need to talk |
|---|---|
| 3 | 3 |
| 6 | 15 |
| 12 | 66 |
Doubling a team of 6 more than quadruples the pairs. An activity that can be split among more people, such as labelling cables, crashes well; one that depends on understanding, such as testing restores, often does not.
Padding every estimate is no better. Cyril Northcote Parkinson’s essay in The Economist of 1955 observed that “work expands so as to fill the time available for its completion”, and the The habit of starting a task late because its estimate holds safety time, so that the safety is used up before the work begins. adds a late start. A task with 2 days of private safety rarely finishes 2 days early; the safety is spent either way. Eliyahu Goldratt’s Critical Chain, published in 1997, moves that safety out of the tasks into shared buffer: Time kept in one place in a schedule, at the end of a chain or where chains join, to absorb delays instead of padding each task.: a project buffer at the end of the chain that sets the finish, and feeding buffers where other chains join it. The plan then reports how much of the buffer each delay has used, instead of whether each task met a padded date.
Key idea
Every way of winning back time has a price: levelling may cost days, crashing costs money, fast-tracking costs risk, and adding people late can cost more time than it saves.
Checkpoint
Check: Fast-tracking and its risk · Adding people, padding and buffers
Practise: people, money and overlap
The questions use the move’s histograms and costs, and pairs of activities from other projects that may or may not be safe to overlap. Work out the numbers on paper before you choose:
Practice that adapts to you
Practise this lesson
11 questions about 4 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Warm-up (fresher to 2–5 years): crashing and fast-tracking both shorten a schedule, so what does each one spend?
Both shorten a schedule, and both work only on the critical path, but they spend different things. Crashing adds resources, people, equipment or overtime, to critical activities so that they take fewer days: it costs money, and the cheapest day is found from each activity’s cost slope. Fast-tracking changes the logic instead, letting activities that were planned one after the other overlap: it costs little money but adds risk, because work that starts before its predecessor finishes may have to be redone. In the data-centre move, crashing bought 5 days for 25 thousand, while a 3-day overlap gave 3 days for nothing but a risk of moving racks again.
Two harder questions follow, about a sponsor who wants two weeks back and about a director who wants to double a late team. Decide what you would ask first, then open each first step:
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Mid to senior
A sponsor asks you to bring a 60-day office relocation in 10 working days earlier, and offers a budget for it. How do you work out what to do, and what do you tell them?
-
Mid to senior
A software release is 3 weeks late with 5 weeks of work left, and a director wants to double the team of 6 next Monday. What do you advise?
Flashcards · 4 cards
Recall it, then check
Say the answer to yourself before you look: recalling it is what makes it stick.
-
What does a resource histogram show, and what does levelling do?
It shows how many activities each person has on each day; a 2 is one person booked twice, a date that cannot be kept. Resource levelling delays or reassigns activities to remove the double booking. When every delay fits inside float and moves no other date, it is called resource smoothing.
-
What is a cost slope, and how does crashing use it?
Cost slope = (crash cost − normal cost) / (normal duration − crash duration): the cost per day saved by shortening an activity. Crashing shortens the critical path one day at a time at the cheapest slope, recomputing paths after every day because a cheaper path can become critical.
-
What does fast-tracking spend compared with crashing?
Risk, instead of money. Fast-tracking changes finish-to-start logic to start-to-start, overlapping work; it saves days without crashing cost, but work that starts before its predecessor finishes may have to be redone. Crashing spends money.
-
Why does adding people to a late project often make it later?
Fred Brooks (The Mythical Man-Month, 1975): new people need time to learn the work and someone who knows it has to teach them, and the count of pairs that might have to talk rises as n(n−1)/2 (3→3, 6→15, 12→66). Only work that splits well, such as labelling cables, crashes well.
Key takeaways
- A resource histogram shows where one person is booked twice; levelling delays or reassigns work to remove it, and when every delay fits inside float it moves no other date, which is called smoothing.
- Crashing shortens critical activities at the lowest cost slope first, recomputing the paths after every day, and the cheapest overall duration is where a day’s crash costs more than a day of overheads.
- Fast-tracking overlaps activities planned in sequence and saves days without crashing costs, at the price of rework risk.
- Adding people late can slow work through learning time and the pairs who must talk, and shared buffers protect a plan better than padding every task.
Terms in this lesson
- buffer
- Time kept in one place in a schedule, at the end of a chain or where chains join, to absorb delays instead of padding each task.
- cost slope
- The cost of each day saved by crashing an activity: the extra cost of its shortest version divided by the days it saves.
- crashing
- Shortening critical activities by adding people, equipment or overtime, at a cost for each day saved.
- fast-tracking
- Shortening a schedule by overlapping activities that were planned one after the other, which costs little money but adds the risk of rework.
- resource levelling
- Moving or reassigning scheduled work so that no person or resource is booked for more than it can do at once; it may move the finish when a delay exceeds float.
- resource smoothing
- Levelling that delays work only within its float, so that over-allocation is removed and no other date moves.
References
- Schedule Assessment Guide: Best Practices for Project Schedules (GAO-16-89G) (U.S. Government Accountability Office)
- The Mythical Man-Month: Essays on Software Engineering, Anniversary Edition (Frederick P. Brooks Jr.) (Addison-Wesley)
- Parkinson's Law (The Economist) (The Economist)
- Critical Chain (Eliyahu M. Goldratt) (Goldratt Marketing Group)
- NPTEL: Project Management for Managers (IIT Roorkee), course plan (NPTEL (IITs and IISc, Ministry of Education))
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress