Coding Interview Patterns Module 9 – Binary search
Search on the answer: capacity and speed
Find the slowest speed or the smallest capacity that still finishes in time by binary searching the answer itself with a yes-or-no check of each guess.
What you will learn
- Convert "the smallest speed or capacity that finishes in time" into a yes-or-no check and search its values
- Set search bounds that surely contain the answer and leave out speeds that cannot work
- Write the feasibility check in O(n) with exact integer arithmetic
- Explain when the totals of the check overflow 32 bits, and size them for 64 bits
Before you start
What you’ll learn · Before you start
What you will learn
- Convert "the smallest speed or capacity that finishes in time" into a yes-or-no check and search its values
- Set search bounds that surely contain the answer and leave out speeds that cannot work
- Write the feasibility check in O(n) with exact integer arithmetic
- Explain when the totals of the check overflow 32 bits, and size them for 64 bits
Before you start
On this page
Search on the answer means binary searching the possible answers themselves instead of positions in the input. It fits questions of the form “what is the smallest speed, or the smallest capacity, that still finishes in time?”. Pick a guess, check it with one pass over the input, and use the verdict to discard half of the remaining guesses: if speed s finishes in time, every faster speed does too, and if it does not, no slower speed can. With answers up to 10⁹ that takes about 30 checks. What follows is an original version of the task widely called Koko eating bananas, a count of what the brute force would take at its limits, and the signal that points to this search.
The problem: the slowest speed that finishes the revision
The problem
A student has n chapters left to revise before an exam, chapter i with pages[i] pages, and h hours to do it. The
student picks one reading speed, s pages an hour, a whole number, and works hour by hour: each hour goes to one
chapter and covers up to s of its remaining pages. When fewer than s pages of a chapter are left, they are finished
in that hour and the rest of the hour is a break, so no hour is shared by two chapters. Return the slowest speed that
finishes every chapter within h hours. Limits: 1 ≤ n ≤ 10⁵, n ≤ h ≤ 10⁹ and 1 ≤ pages[i] ≤ 10⁹.
pages = [12, 7, 30, 18]and h = 8 give 10: at 10 pages an hour the chapters take 2 + 1 + 3 + 2 = 8 hours, and at 9 they would take 9.- The same chapters with h = 4 give 30: one hour per chapter, so the speed must cover the longest chapter.
- One chapter of 1,000,000,000 pages with h = 3 gives 333,333,334.
Hours needed never rise as the speed grows
Text description of the diagram
The chart shows, for the chapters of 12, 7, 30 and 18 pages, how many hours each reading speed from 5 to 16 pages an hour needs, with each count written at the foot of its bar. The bars read 15, 12, 11, 10, 9, 8, 8, 7, 7, 7, 6 and 6 hours: they fall or stay level, and never rise.
A dashed horizontal line marks h = 8 hours. The bars for speeds 5 to 9 are above the line and are drawn plain; the bars from speed 10 on are at or under it and are highlighted. An arrow labelled "first speed that fits: 10" points at the bar for speed 10.
The note at the bottom says that because the hours never rise as the speed grows, the question "does it fit in h hours?" flips once, from no to yes.
The brute force: try every speed
Try s = 1, 2, 3 and so on, add up the hours each speed needs, ⌈p / s⌉ for every chapter, and stop at the first speed that fits. Each try is one pass over the chapters, but the answer can be as large as 10⁹, so the tries can number 10⁹ and the work 10¹⁴:
"""The slowest reading speed, found by trying 1, 2, 3, ... pages an hour: the brute force, with its tries counted."""
import random
import time
def hours_needed(pages, speed):
"""Each hour goes to one chapter, so a chapter of p pages takes ceil(p / speed) hours."""
return sum(-(-p // speed) for p in pages)
def slowest_by_trying(pages, h):
speed = 1
while hours_needed(pages, speed) > h:
speed += 1
return speed # the speed is also the number of tries
for pages, h in [([12, 7, 30, 18], 8), ([12, 7, 30, 18], 4), ([1_000, 2_000], 3)]:
speed = slowest_by_trying(pages, h)
print(f"{pages}, h = {h}: speed {speed:,} after {speed:,} tries")
# One try at the limits: 100,000 chapters of up to a billion pages each.
rng = random.Random(2)
chapters = [rng.randint(1, 10**9) for _ in range(100_000)]
start = time.perf_counter()
for speed in (1, 1_000, 1_000_000):
hours_needed(chapters, speed)
per_try = (time.perf_counter() - start) / 3
print(f"one try over 100,000 chapters: about {per_try * 1000:,.0f} ms")
print(f"an answer near 10^9 needs about 10^9 tries: about {per_try * 10**9 / 86_400 / 365:,.1f} years") Output
[12, 7, 30, 18], h = 8: speed 10 after 10 tries [12, 7, 30, 18], h = 4: speed 30 after 30 tries [1000, 2000], h = 3: speed 1,000 after 1,000 tries one try over 100,000 chapters: about 7 ms an answer near 10^9 needs about 10^9 tries: about 0.2 years
This output changes from run to run: the times depend on the computer and on what else it is doing
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 try_speeds.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
One try over 100,000 chapters takes milliseconds, and a billion of them would take months or years, where the time limit is a second or two.
What to notice
A faster speed never needs more hours: each chapter’s ⌈p / s⌉ can only fall or stay the same as s grows. So “finishes within h hours” is false for slow speeds and true from one speed on, a condition that flips once along the speeds 1 to 10⁹. Capacity behaves the same way: a bigger van never needs more days to carry the same parcels in the same order. The input does not need to be sorted or searched at all: the thing to search is the answer.
The slowest speed by halving, step by step Pro: opens with a Pro, Premium or Ultimate pass
In this part:
- Why a faster speed never needs more hours: the proof
- The search in five languages
- An hours total that overflows 32 bits, reproduced
- Rounding up without floating point
- The same search for capacity: shipping parcels within d days
- Edge cases, variants and follow-ups
- Edge cases and common bugs
- Variants, with answers
- Follow-up questions, answered
- Practise: the earliest minute the ovens finish the order
- The earliest minute the ovens finish the order
- Check yourself
- Interview questions
- Your Java solution for the slowest reading speed keeps the total hours in an int and passed every test. When the limit on h is raised from 10⁹ to 2 × 10⁹, a test with 100,000 chapters of 10⁹ pages gets speed 1. Explain the failure, and why the old limits hid it.
- For the slowest reading speed, a teammate searches the speeds from 0 to 10⁹. What can go wrong at each end? Give tight bounds with a reason for each, and say how many checks they save for 10⁵ chapters of 10⁹ pages each with h = 2 × 10⁵.
- Change one rule of the reading-speed problem at a time. An hour may continue with the next chapter. Each chapter may be read at its own speed, and you minimise the fastest one. Or h becomes a number of days of at most 8 hours, and a chapter may continue the next day. Which changes still need a binary search?
- Key takeaways
4 runnable examples (C++, Go, Java, JavaScript, Python) · 1 exercise with sample tests · 3 interview answers · about 23 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
References
- Built-in types: numeric types, floor division // and true division / (Python Software Foundation)
- math.ceil (Python Software Foundation)
- The Java Language Specification, Java SE 25: 15.18.2 Additive Operators for numeric types (Oracle)
- The Java Language Specification, Java SE 25: 4.2.1 Integral Types and Values (Oracle)
- ECMAScript language specification: Number.MAX_SAFE_INTEGER (Ecma International (TC39))
- The Go Programming Language Specification: numeric types (The Go Authors)
- Go 1.1 Release Notes: the size of int on 64-bit platforms (The Go Authors)
- UndefinedBehaviorSanitizer (LLVM Project)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress