Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

System Design (High-Level Design) Module 2 – Back-of-the-envelope estimation

Units, powers of two and time conversions

Convert decimal and binary units such as GB and GiB, turn daily counts into rates per second and round estimates on purpose, with scripts that check the maths.

  • Beginner
  • 15 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Node.js 24.21.0 and quickjs 0.32.0
  • By MySmartCoPilot

What you will learn

  • Convert between decimal units (kB, MB, GB) and binary units (KiB, MiB, GiB) without mixing them
  • Convert link rates in bits per second into bytes per second and work out a transfer's best-case time
  • Convert per-day and per-month counts into average rates per second
  • Estimate with one or two significant figures and say which way each rounding pushes the result

Before you start

On this page

Most estimates that go wrong in a design round go wrong in the units, not in the idea: a rate per minute read as a rate per second, gigabits taken for gigabytes, or a drive’s decimal terabytes compared with memory’s binary gibibytes. This lesson settles the units first, so that every later estimate in this module stands on firm ground: the two families of size prefixes, bits and bytes, the powers of two that set real limits, the step from a daily count to a rate per second, and how to round without fooling yourself.

Two families of size prefixes

Kilo, mega, giga, tera and peta are SI prefixes, and SI prefixes are powers of ten: a kilobyte (kB) is 1,000 bytes and a gigabyte (GB) is 10910^{9} bytes (NIST, SI prefixes). Computer memory is built in powers of two, because its addresses are binary numbers, so a second family of prefixes names exact binary multiples: kibi (Ki, 2102^{10}), mebi (Mi, 2202^{20}), gibi (Gi, 2302^{30}), tebi (Ti) and pebi (Pi). The IEC defined these binary prefixes, and they are not part of the SI (NIST, binary prefixes). Watch the letter case: the decimal kilo is a lowercase k (kB), the binary kibi a capital K (KiB).

How far apart the two families drift Python · prefixes.py
# Decimal (SI) prefixes are powers of 10; binary (IEC) prefixes are powers of 2.
# The two families start close together and drift apart with every step.
PREFIXES = [("kilo", "kB", "KiB"), ("mega", "MB", "MiB"), ("giga", "GB", "GiB"), ("tera", "TB", "TiB"), ("peta", "PB", "PiB"), ("exa", "EB", "EiB")]

print(f"{'Prefix':<8}{'Decimal':<13}{'Binary':<13}{'Gap':>6}")
for step, (name, dec, binary) in enumerate(PREFIXES, start=1):
    gap = (2 ** (10 * step) / 10 ** (3 * step) - 1) * 100
    print(f"{name:<8}{dec:<3} = 10^{3 * step:<4}{binary:<3} = 2^{10 * step:<5}{gap:5.1f}%")
print("Gap: how much bigger the binary unit is than the decimal one")

print()
print(f"1 GB  = {10**9:>13,} bytes")
print(f"1 GiB = {2**30:>13,} bytes")
disk = 10**12  # a drive sold as "1 TB"
print(f'A "1 TB" drive holds {disk / 2**30:.1f} GiB, which is {disk / 2**40:.3f} TiB')

Output

Prefix  Decimal      Binary          Gap
kilo    kB  = 10^3   KiB = 2^10     2.4%
mega    MB  = 10^6   MiB = 2^20     4.9%
giga    GB  = 10^9   GiB = 2^30     7.4%
tera    TB  = 10^12  TiB = 2^40    10.0%
peta    PB  = 10^15  PiB = 2^50    12.6%
exa     EB  = 10^18  EiB = 2^60    15.3%
Gap: how much bigger the binary unit is than the decimal one

1 GB  = 1,000,000,000 bytes
1 GiB = 1,073,741,824 bytes
A "1 TB" drive holds 931.3 GiB, which is 0.909 TiB

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 prefixes.py

The two start 2.4% apart and drift further with every step: a tebibyte is 10% bigger than a terabyte, a pebibyte 12.6% bigger than a petabyte. The last line explains a familiar puzzle. A program that divides a byte count by 2302^{30} but prints the label “GB” shows a 1 TB drive as about 931 GB. No space is missing; the unit changed and the label did not. NIST’s page records the same split in practice: memory makers have used the binary megabyte, and storage makers usually the decimal one.

In an estimate, choose one family and say which. Decimal units are easier to multiply in your head, and for most estimates the gap is smaller than the uncertainty in the assumptions. Switch to binary units when the thing you are sizing is counted in binary: memory, page and block sizes, and any limit that a system documents in KiB or MiB.

A program computes the same thing whichever family you meant, so a mix-up gives no error, only a wrong number:

Decimal bytes divided by a binary unit Python · mixup.py
disk_bytes = 500 * 10**9  # a drive sold as "500 GB" (decimal units)
print(round(disk_bytes / 2**30, 1), "GiB")

Output

465.7 GiB

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 mixup.py

Bits and bytes

A byte is 8 bits (NIST). Files, disks and memory are counted in bytes, written with a capital B, but links are rated in bits per second, written with a lowercase b: the IEEE 802.3 working group names Ethernet speeds such as 400 Gb/s. Confusing the two puts an estimate out by exactly a factor of eight.

What a 1 Gbit/s link can move Python · link_speed.py
# Links are rated in bits per second; files and disks are measured in bytes.
BITS_PER_BYTE = 8

link_bits_per_s = 1 * 10**9  # a "1 Gbit/s" link
bytes_per_s = link_bits_per_s / BITS_PER_BYTE
print(f"1 Gbit/s carries at most {bytes_per_s / 10**6:.0f} MB/s ({bytes_per_s / 2**20:.1f} MiB/s)")

file_bytes = 1 * 10**12  # copying "1 TB"
seconds = file_bytes / bytes_per_s
print(f"Copying 1 TB over it takes at least {seconds:,.0f} s, about {seconds / 3600:.1f} hours")
print(f"Forgetting the 8 would promise {file_bytes / link_bits_per_s:,.0f} s instead")

Output

1 Gbit/s carries at most 125 MB/s (119.2 MiB/s)
Copying 1 TB over it takes at least 8,000 s, about 2.2 hours
Forgetting the 8 would promise 1,000 s instead

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 link_speed.py

Treat a link’s rating as a ceiling, not a promise: the same link also carries protocol headers, other traffic and retransmissions, so a real copy takes longer than the time computed here. The figure is still useful, because it is a lower bound. If even the best case is too slow, no tuning will rescue the design.

Powers of two worth knowing

Ten doublings make about a thousand, because 210=10242^{10} = 1024. Twenty doublings make about a million and thirty about a billion. Two more powers turn up in designs because they are the sizes of common integer types: a 32-bit unsigned integer has 2322^{32} possible values and a 64-bit one 2642^{64}.

Powers of two, and how long an ID space lasts Python · powers.py
# Powers of two worth knowing, and how long an ID space lasts.
for exponent in [10, 16, 20, 30, 32, 40, 64]:
    value = 2**exponent
    print(f"2^{exponent:<3} = {value:>26,}  (about {value:.1e})")

print()
SECONDS_PER_DAY = 86_400
SECONDS_PER_YEAR = 365 * SECONDS_PER_DAY
ids_32 = 2**32
print(f"A 32-bit counter, 1,000 new IDs a second: full after {ids_32 / 1_000 / SECONDS_PER_DAY:.1f} days")
ids_64 = 2**64
print(f"A 64-bit counter, 1 million new IDs a second: full after {ids_64 / 10**6 / SECONDS_PER_YEAR:,.0f} years")
codes = 62**7
print(f"7-character codes from 62 letters and digits: {codes:,} (about {codes:.1e})")

Output

2^10  =                      1,024  (about 1.0e+03)
2^16  =                     65,536  (about 6.6e+04)
2^20  =                  1,048,576  (about 1.0e+06)
2^30  =              1,073,741,824  (about 1.1e+09)
2^32  =              4,294,967,296  (about 4.3e+09)
2^40  =          1,099,511,627,776  (about 1.1e+12)
2^64  = 18,446,744,073,709,551,616  (about 1.8e+19)

A 32-bit counter, 1,000 new IDs a second: full after 49.7 days
A 64-bit counter, 1 million new IDs a second: full after 584,942 years
7-character codes from 62 letters and digits: 3,521,614,606,208 (about 3.5e+12)

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 powers.py

These limits decide real designs. A 32-bit counter that hands out 1,000 IDs a second runs out in under two months, while a 64-bit counter at a million a second outlasts any system you will build. Short codes work the same way: seven characters drawn from 62 letters and digits give about 3.5 trillion codes, which is why a link shortener’s links can stay short for a very long time.

From a daily count to a rate per second

Requirements usually arrive as counts per day or per month (“20 million messages a day”), while servers, databases and queues are sized in operations per second. A day has 24×60×60=8640024 \times 60 \times 60 = 86400 seconds, and the rest follows from it:

Seconds per period, and daily counts as rates Python · rates.py
# Seconds in the periods that estimates use (a month is taken as 30 days, a year as 365).
MINUTE = 60
HOUR = 60 * MINUTE
DAY = 24 * HOUR
MONTH = 30 * DAY
YEAR = 365 * DAY

for name, seconds in [("minute", MINUTE), ("hour", HOUR), ("day", DAY), ("30-day month", MONTH), ("365-day year", YEAR)]:
    print(f"1 {name:<13} = {seconds:>10,} s")

print()
print(f"{'Events a day':>16}  {'average per second':>18}")
for exponent in range(6, 11):
    per_day = 10**exponent
    print(f"{per_day:>16,}  {per_day / DAY:>18,.1f}")

print()
monthly = 3 * 10**9  # an example: 3 billion events in a 30-day month
print(f"3 billion a month = {monthly / MONTH:,.0f} per second on average")

Output

1 minute        =         60 s
1 hour          =      3,600 s
1 day           =     86,400 s
1 30-day month  =  2,592,000 s
1 365-day year  = 31,536,000 s

    Events a day  average per second
       1,000,000                11.6
      10,000,000               115.7
     100,000,000             1,157.4
   1,000,000,000            11,574.1
  10,000,000,000           115,740.7

3 billion a month = 1,157 per second on average

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 rates.py

Two shortcuts are worth remembering: a million events a day is about 12 a second, and a billion a day about 11,600 a second. For a 30-day month divide by about 2.6 million, and for a year by about 31.5 million. Every one of these is an average over the whole period. Real traffic is never flat; the lesson on estimating traffic turns an average into a peak.

Rounding on purpose

State an estimate with one or two significant figures: “about 17,000 requests a second” is honest, while 17,361 claims a precision that the assumptions behind it do not have. Every rounding pushes the result one way, though, so round on purpose and say which way it went.

The same estimate, rounded three ways Python · rounding.py
import math

DAY = 86_400


def sig(x, digits=2):
    """x rounded to `digits` significant figures."""
    if x == 0:
        return 0
    places = digits - 1 - math.floor(math.log10(abs(x)))
    value = round(x, places)
    return int(value) if places <= 0 else value


per_day = 1.5 * 10**9  # an estimate: 1.5 billion requests a day

exact = per_day / DAY
rounded_day = per_day / 10**5  # treating a day as 100,000 seconds

print(f"Exact:                  {exact:,.0f} per second")
print(f"Day taken as 10^5 s:    {rounded_day:,.0f} per second ({(rounded_day / exact - 1) * 100:+.1f}%)")
print(f"Two significant digits: {sig(exact):,} per second")
print()
# Every rounding pushes an estimate one way or the other: say which.
for label, value in [("Day rounded up to 10^5 s", rounded_day), ("Day rounded down to 8 x 10^4 s", per_day / (8 * 10**4))]:
    direction = "under" if value < exact else "over"
    print(f"{label:<31} {value:>7,.0f}/s  {direction}estimates the load")

Output

Exact:                  17,361 per second
Day taken as 10^5 s:    15,000 per second (-13.6%)
Two significant digits: 17,000 per second

Day rounded up to 10^5 s         15,000/s  underestimates the load
Day rounded down to 8 x 10^4 s   18,750/s  overestimates the load

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 rounding.py

Dividing by 10510^{5} instead of 86,400 is tempting because it is easy, but every rate comes out about 14% too low (the true rate is about 16% higher than the shortcut’s). When you size capacity, that is the unsafe direction: you would plan for less load than will arrive. When the question is how much load a system must carry, round so that the load comes out higher; when it is how long a resource will last, round so that the time comes out shorter. If you do take a shortcut, say so: “about 15,000 a second, a little low because I used 10510^{5} seconds a day”.

Lakh and crore

Indian figures are often written in lakh (1,00,000, which is 10510^{5}) and crore (1,00,00,000, which is 10710^{7}), with commas that keep the last three digits together and split the digits before them into pairs (NCERT, class 7 mathematics). The Reserve Bank of India’s Payment System Indicators give monthly payment volumes, including UPI’s, in lakh, so converting lakh is the first step of any check against those tables. Python accepts an underscore between any two digits of a number (Python reference), so a figure can be typed in the grouping it was printed in:

A volume in lakh as a rate per second Python · lakh_crore.py
LAKH = 10**5
CRORE = 10**7
DAY = 86_400

# Python accepts _ between digits, so Indian digit grouping can be written as it is printed.
monthly = 2_45_089.58 * LAKH  # a monthly volume given in lakh
print(f"2,45,089.58 lakh = {monthly:,.0f} = {monthly:.2e}")
print(f"Spread over a 31-day month: {monthly / (31 * DAY):,.0f} per second")
print(f"1 crore a day = {CRORE / DAY:,.1f} per second")

Output

2,45,089.58 lakh = 24,508,958,000 = 2.45e+10
Spread over a 31-day month: 9,151 per second
1 crore a day = 115.7 per second

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 lakh_crore.py

Lakh Crore Converter (to Million, Billion) Convert lakh and crore to millions and billions, and back.

Mistakes to check before you say a number

  • Bits for bytes. A “Gb” is a gigabit. Divide a link rate by 8 before comparing it with a file size.
  • Binary for decimal. A GiB is 7.4% more than a GB, and a TiB 10% more than a TB. Say which one you mean.
  • The wrong period. Per minute and per second differ by 60 times. Even month lengths matter a little: a 31-day month has 3.3% more seconds than a 30-day one.
  • An average taken for a peak. A daily count divided by 86,400 is the average rate. The busiest hour always brings at least that much, and usually far more.
Data Storage Converter Check a decimal or binary size conversion to the byte. Data Rate Converter (Mbps to MB/s) Turn link rates in bits per second into bytes per second, and back. Time Unit Converter Convert days, months and years into seconds when a requirement uses an unusual period.

Key takeaways

  • SI prefixes (kB, MB, GB) are powers of ten; IEC prefixes (KiB, MiB, GiB) are powers of two. They differ by 2.4% at kilo and 10% at tera, so pick one family and say which.
  • Links are rated in bits per second and data is counted in bytes: divide by 8.
  • 210≈1032^{10} \approx 10^{3}, and the sizes of 32-bit and 64-bit integers decide how long an ID space lasts.
  • Divide a daily count by 86,400 to get the average rate per second; a million a day is about 12 a second.
  • Keep one or two significant figures, and round in the direction that keeps the answer safe.

Exercise

Exercise · Easy · Python

Convert sizes to bytes and counts to rates

Write two small converters that an estimate sheet can lean on, in conversions.py.

to_bytes(amount, unit) returns how many bytes amount of unit is.

  • Decimal units are powers of ten: "B", "kB", "MB", "GB", "TB" and "PB", so to_bytes(2, "kB") is 2000.
  • Binary units are powers of two: "KiB", "MiB", "GiB", "TiB" and "PiB", so to_bytes(2, "KiB") is 2048.
  • Any other unit raises ValueError. That includes "Gb", which means gigabits: a converter that guessed would be off by a factor of eight.

per_second(count, period) returns the average number of events per second when count events happen in one period: "minute", "hour", "day", "month" (taken as 30 days) or "year" (taken as 365 days). For example, per_second(86_400, "day") is 1.0. Any other period raises ValueError.

The sample tests import both functions from conversions.py, check whole-number results exactly and check rates to nine significant figures.

Starter code · conversions.py

def to_bytes(amount, unit):
    """Return the number of bytes in `amount` of `unit` ("kB", "MiB" ...); raise ValueError for other units."""
    # Replace this line with your code.
    return 0


def per_second(count, period):
    """Return the average events per second for `count` events per `period` ("minute" ... "year")."""
    # Replace this line with your code.
    return 0
The sample tests · test_conversions.py
import math

from conversions import per_second, to_bytes


def raises_value_error(function, *args):
    """True when function(*args) raises ValueError."""
    try:
        function(*args)
    except ValueError:
        return True
    return False


def test_decimal_units():
    """converts decimal units with powers of ten"""
    assert to_bytes(5, "B") == 5
    assert to_bytes(1, "kB") == 1_000
    assert to_bytes(3, "GB") == 3_000_000_000
    assert to_bytes(2, "TB") == 2 * 10**12
    assert to_bytes(1.5, "MB") == 1_500_000


def test_binary_units():
    """converts binary units with powers of two"""
    assert to_bytes(1, "KiB") == 1_024
    assert to_bytes(1, "GiB") == 1_073_741_824
    assert to_bytes(4, "TiB") == 4 * 2**40


def test_unknown_unit():
    """refuses units it does not know, such as gigabits"""
    assert raises_value_error(to_bytes, 1, "Gb")
    assert raises_value_error(to_bytes, 1, "gigabyte")


def test_per_day():
    """turns a daily count into an average per second"""
    assert per_second(86_400, "day") == 1
    assert math.isclose(per_second(10**9, "day"), 11_574.074074, rel_tol=1e-9)


def test_other_periods():
    """knows minutes, hours, 30-day months and 365-day years"""
    assert per_second(60, "minute") == 1
    assert per_second(7_200, "hour") == 2
    assert per_second(2_592_000, "month") == 1
    assert math.isclose(per_second(3 * 10**9, "month"), 1_157.407407, rel_tol=1e-9)
    assert per_second(31_536_000, "year") == 1


def test_unknown_period():
    """refuses periods it does not know"""
    assert raises_value_error(per_second, 10, "fortnight")
A hint

Keep one dictionary from unit name to the number of bytes in one unit ("kB": 10**3, "KiB": 2**10 …) and one from period name to its length in seconds. Then each function is a lookup and one multiplication or division, and if unit not in factors: raise ValueError(...) handles the units you do not know.

The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Check yourself

6 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 6 A drive is sold as 2 TB, in decimal units. How many GiB does it hold?

    Type a number.

    Show the answer to question 1

    Answer: 1862.6 GiB (anything from 1825.6 to 1899.6 counts)

    2 TB is 2 × 10^12 bytes and 1 GiB is 2^30 = 1,073,741,824 bytes, so the drive holds 2 × 10^12 / 2^30 ≈ 1,862.6 GiB. Nothing is missing: the same bytes are counted in a bigger unit.

  2. Question 3 of 6 A service receives 300 million requests a day. About how many is that per second, on average?

    Type a number.

    Show the answer to question 3

    Answer: 3472.2 requests per second (anything from 3402.2 to 3542.2 counts)

    300,000,000 / 86,400 ≈ 3,472 requests a second. That is the average over the whole day; the busiest hour brings more.

  3. Question 4 of 6 To save time you divide a daily count by 100,000 instead of 86,400. What happens to the rate?

    Choose one answer.

    Show the answer to question 4

    Answer: It comes out about 14% too low, so a plan based on it underestimates the load

    86,400 / 100,000 = 0.864, so every rate is multiplied by 0.864, about 14% too low. For capacity planning that is the unsafe direction, so either use 86,400 or say that your figure is a little low.

  4. Question 5 of 6 What does this program print?

    What does this program print? Choose one answer.

    disk_bytes = 500 * 10**9  # a drive sold as "500 GB" (decimal units)
    print(round(disk_bytes / 2**30, 1), "GiB")
    Show the answer to question 5

    Answer: it prints

    465.7 GiB

    500 × 10^9 bytes divided by 2^30 bytes per GiB is about 465.66, rounded to 465.7. A drive sold in decimal gigabytes always shows fewer binary gibibytes.

  5. Question 6 of 6 Which of these units are powers of two?

    Choose every answer that is right.

    Show the answer to question 6

    Answer:

    • KiB
    • TiB
    • GiB

    The IEC prefixes with an "i" (kibi, gibi, tebi) are powers of two. kB is 1,000 bytes, and Gbit/s is a decimal rate of 10^9 bits a second.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.