AI Computer Institute
Expert-curated CS & AI curriculum aligned to CBSE standards. A bharath.ai initiative. About Us

Parallel Computing: Making Programs Faster

📚 Computer Science Fundamentals⏱️ 20 min read🎓 Grade 9
✍️ AI Computer Institute Editorial Team Updated: August 2026 CBSE-aligned · Peer-reviewed · 20 min read
Content curated by subject matter experts with IIT/NIT backgrounds. All chapters are fact-checked against official CBSE/NCERT syllabi.

Checking 800 Answer Sheets

Imagine it is the last day of half-yearly exams at your school, and the Computer Science teacher has 800 answer sheets to check before report cards go out tomorrow morning. Checking one sheet — reading the answers, matching them against the key, writing the marks — takes about 36 seconds on average. If one teacher sits down and checks all 800 sheets alone, one after another, the whole job takes 800 × 36 seconds, which is 28,800 seconds, or exactly 8 hours. That is the entire school day gone, spent doing one thing, one sheet at a time.

Now suppose four teachers split the stack into four equal piles of 200 sheets each, and all four start checking at the same time, in four different staff rooms. Each teacher still takes 36 seconds per sheet — nobody got faster at checking — but now each of them only has 200 sheets, so each pile takes 200 × 36 seconds = 7,200 seconds = 2 hours. Since all four teachers are working simultaneously, the entire batch of 800 sheets is done in 2 hours instead of 8.

Nothing about the checking itself changed. The same total amount of work happened — 800 sheets, 36 seconds each, 8 teacher-hours of labour either way. What changed is how that work was arranged in time. One worker doing everything in sequence took 8 hours of wall-clock time. Four workers doing quarter-sized pieces at the same time took 2 hours of wall-clock time. This — taking one big job, splitting it into independent pieces, and having multiple workers handle the pieces at the same instant — is the entire idea behind parallel computing. A computer's "workers" are not teachers; they are processor cores. But the logic is identical.

From Teachers to Cores: What Parallel Computing Actually Means

A CPU (Central Processing Unit) executes instructions — the tiny steps of a program, like "add these two numbers" or "compare this value to that one." For decades, a CPU chip contained exactly one core: one independent unit capable of fetching an instruction, decoding it, and executing it. A single core can only be doing one instruction at a given instant. If a program has a million steps, a single core marches through them one at a time — this is called sequential or serial execution, exactly like the lone teacher checking 800 sheets alone.

Modern chips — the one inside the laptop you are reading this on, and almost certainly the one inside your phone — contain multiple cores on a single piece of silicon: dual-core (2), quad-core (4), octa-core (8), and beyond. Many smartphones sold in India today use octa-core processors, meaning eight independent execution units sit on one chip, each capable of running its own stream of instructions at the same time as the others. When a program is written so that different cores work on different pieces of a problem simultaneously, that is parallel computing: real, physical simultaneity, not just the appearance of it.

This distinction — one core doing everything in turn versus several cores genuinely working at once — is the single most important idea in this chapter. Every other concept below is really just asking one question in different disguises: how do you split a job across cores correctly, and how much faster does that actually make things?

Seeing It on a Timeline

The diagram below draws the answer-sheet example as a timeline. The top bar shows one teacher working through all four piles back-to-back, taking the full 8 hours. The four bars below show four teachers each handling one pile, all starting together and all finishing together in 2 hours. The total area of coloured bar — the actual work done — is identical in both pictures. Only the arrangement in time differs.

1 Teacher vs 4 Teachers Checking 800 Answer Sheets A — all 4 piles P1 P2 P3 P4 Total = 8 hours 0 2h 4h 6h 8h B – Pile 1 P1 C – Pile 2 P2 D – Pile 3 P3 E – Pile 4 P4 All finish together! Total = 2 hours 0 2h Speedup = 8 hours / 2 hours = 4x

Data Parallelism: Same Operation, Different Data

When multiple cores run the same piece of code on different chunks of data, that is called data parallelism. Checking answer sheets was data parallelism: every teacher ran the identical "check a sheet" procedure, just on a different pile of sheets. This is by far the most common form of parallelism in real programs, because arrays, lists, and tables are everywhere — images are arrays of pixels, spreadsheets are arrays of rows, and sensor logs are arrays of readings.

Take something simpler than answer sheets: adding up a list of eight numbers. Written sequentially in Python, it looks like this:

def sequential_sum(numbers):
    total = 0
    for n in numbers:
        total += n
    return total

data = [3, 1, 4, 1, 5, 9, 2, 6]
print(sequential_sum(data))

Trace it by hand: total starts at 0, then becomes 3, 4, 8, 9, 14, 23, 25, and finally 31 after the last number is added. One core does all eight additions, one after another, in order.

Now split the same list into two chunks of four and hand one chunk to each of two cores:

from multiprocessing import Pool

def sum_chunk(chunk):
    total = 0
    for n in chunk:
        total += n
    return total

data = [3, 1, 4, 1, 5, 9, 2, 6]
chunks = [data[0:4], data[4:8]]   # [3,1,4,1] and [5,9,2,6]

with Pool(processes=2) as pool:
    partial_sums = pool.map(sum_chunk, chunks)

final_total = sum(partial_sums)
print(final_total)

Trace this version too. Core 1 receives [3, 1, 4, 1] and computes 3+1+4+1 = 9. Core 2 receives [5, 9, 2, 6] and computes 5+9+2+6 = 22. Both of these additions happen at the same time, on different cores — neither core waits for the other. Once both cores report back, partial_sums holds [9, 22], and one final, unavoidable step adds those two numbers together: 9 + 22 = 31. This last step — combining partial results into a single answer — is called a reduction, and notice that it has to happen sequentially, after the parallel part finishes. You'll see in a moment why that small sequential leftover matters more than it looks like it should.

Task Parallelism: Different Operations, at the Same Time

Data parallelism runs the same code on different data. Task parallelism is the opposite: different cores run genuinely different code, at the same time, because the jobs don't depend on each other. Think about typing this very sentence into a word-processing app. While you type, one core is likely handling your keystrokes and updating the screen; a second core may be running a spell-checker over the paragraph you just finished; a third might be silently auto-saving the document to disk. These are three unrelated tasks — not three pieces of the same array — running concurrently on separate cores. If your computer only had one core, it would have to rapidly hop between these three jobs, doing a little of each in turn; with three real cores, all three genuinely happen at once. Data parallelism asks "how do I split this data?"; task parallelism asks "which of these different jobs can happen independently?" Both are real parallelism, because both involve simultaneous execution on multiple cores — they just split the work along a different axis.

The Danger of Sharing: Race Conditions

A common misconception is that once you split a job across cores, the answer is automatically correct — that parallelism is "free" as long as you have enough cores. It is not, and the danger appears the moment two cores need to read and update the same shared piece of memory.

Imagine a simplified online ticket-booking system with a single shared counter tracking how many seats remain, currently 5. Two cores, P1 and P2, each process one booking at the exact same moment by running this logic:

temp = seats_remaining   # read the shared value
temp = temp - 1          # decrease it
seats_remaining = temp   # write it back

If P1 finishes all three lines before P2 starts, the result is correct: P1 takes seats_remaining from 5 to 4, then P2 takes it from 4 to 3 — two bookings correctly recorded. But cores don't wait politely for each other unless told to. Suppose the timing interleaves like this instead:

  • P1 reads seats_remaining into its own temp: temp₁ = 5.
  • Before P1 writes anything back, P2 also reads seats_remaining: temp₂ = 5 (P2 has no idea P1 already read it).
  • P1 computes temp₁ − 1 = 4 and writes seats_remaining = 4.
  • P2 computes temp₂ − 1 = 4 and writes seats_remaining = 4.

Two bookings were processed, but seats_remaining ends at 4, not 3 — it should have dropped by 2, but it only dropped by 1. One booking's update was silently overwritten and lost. This is a race condition: the final result depends on the unpredictable order in which cores happen to interleave their reads and writes to shared data, and that order can produce a wrong answer even though every individual instruction executed correctly. The fix is a synchronization tool called a lock, which forces one core to complete its entire read–modify–write sequence before any other core is allowed to touch the same variable — restoring the correct, one-at-a-time access to that specific piece of shared data, while everything else still runs in parallel.

Concurrency Is Not the Same as Parallelism

A second common confusion is treating "concurrent" and "parallel" as the same word. They aren't. Parallelism means multiple cores are executing instructions at the literal same physical instant — true simultaneity, like four teachers checking sheets in four different rooms at once. Concurrency means multiple tasks are making progress over the same span of time, but not necessarily at the same instant — a single core can switch rapidly between task A and task B, running a slice of A, then a slice of B, then back to A, fast enough that both appear to progress together even though, at any exact millisecond, only one is actually running. A single teacher who reads two lines of Sheet 1, then two lines of Sheet 2, then back to Sheet 1, and so on, is being concurrent, not parallel — the sheets appear to be progressing together, but the teacher is still doing exactly one thing at any given instant. Every parallel system is running things concurrently in this looser sense, but not every concurrent system is genuinely parallel — that depends on whether there is more than one core actually executing at once.

How Much Faster, Exactly? Speedup and Amdahl's Law

To measure how much a program benefits from extra cores, define speedup as:

Speedup(N) = (time taken by 1 core) / (time taken by N cores)

In the answer-sheet example, one teacher took 8 hours and four teachers took 2 hours, so Speedup(4) = 8 / 2 = 4. Four teachers gave exactly 4× speedup — this is called ideal or linear speedup, and it happens only when the job splits perfectly evenly with no leftover work that must stay sequential.

Real programs almost always have some portion that cannot be split — like the final reduction step (9 + 22 = 31) in the summing example, or, in the ticket-booking case, the locking that forces one core to wait for another. This unavoidable sequential portion caps how much parallel cores can actually help, a relationship called Amdahl's Law. If S is the fraction of the total work that must run sequentially, and the remaining (1 − S) fraction can be perfectly divided across N cores, the time taken with N cores is:

T_parallel(N) = S x T + (1 - S) x T / N

where T is the original single-core time. Work through a concrete example. Suppose a video-rendering job takes T = 100 seconds on one core, and 20% of that time (S = 0.2, or 20 seconds) is spent on setup and final file-writing that only one core can do — the remaining 80 seconds is the actual frame rendering, which splits perfectly across cores.

With N = 4 cores: T_parallel = 20 + 80/4 = 20 + 20 = 40 seconds. Speedup = 100/40 = 2.5×, not 4×, even though there are 4 cores.

With N = 8 cores: T_parallel = 20 + 80/8 = 20 + 10 = 30 seconds. Speedup = 100/30 ≈ 3.33×.

Now push N very high — say 1,000 cores: T_parallel = 20 + 80/1000 = 20.08 seconds, Speedup ≈ 4.98×. No matter how many cores you throw at it, the parallel time can never drop below the 20-second sequential portion, so the speedup can never exceed 100/20 = 5×, which is exactly 1/S. This is the sharp, often-surprising conclusion of Amdahl's Law: a task with even a small unavoidable sequential fraction hits a hard ceiling on speedup, and buying more cores past a certain point stops helping almost entirely. This is precisely why the earlier misconception — "N cores always means N times faster" — breaks down: it is only true when S = 0, meaning the job is perfectly, entirely parallelizable, which real programs with shared state, setup, or output steps rarely are.

Parallel Computing in Practice

India's own supercomputing effort, the PARAM series built by C-DAC (Centre for Development of Advanced Computing) since the early 1990s, is fundamentally an exercise in large-scale parallel computing: thousands of cores are wired together so that scientific simulations — weather forecasting, monsoon modelling, molecular research — can be split into independent chunks and computed simultaneously, the same reduction from 8 hours to 2 hours you saw with the answer sheets, just repeated across far more workers and far larger problems. The octa-core chip inside a typical Indian smartphone applies the same principle at a tiny scale: while one core handles your touch input, another can be decoding a video you're watching and another running a background app, each executing its own instruction stream at the same physical instant. From a phone in your pocket to a national research supercomputer, the underlying question is identical to the one this chapter opened with: how do you split a job into independent pieces, hand each piece to its own worker, and combine the results correctly?

Check Your Understanding

1. A data-cleaning script takes 45 seconds on one core and has no sequential portion at all — every row can be processed independently. What is the time on 9 cores, and what is the speedup? (Answer: 45/9 = 5 seconds; speedup = 45/5 = 9×, ideal linear speedup since S = 0.)

2. An image-processing pipeline takes T = 120 seconds on one core. 25% of that time (S = 0.25) is unavoidable sequential file I/O; the rest splits evenly across cores. Using Amdahl's Law, find the speedup with N = 6 cores. (Answer: T_parallel = 0.25×120 + 0.75×120/6 = 30 + 15 = 45 seconds; speedup = 120/45 ≈ 2.67×.)

3. For the same pipeline in Question 2, what is the maximum possible speedup, no matter how many cores are added? (Answer: maximum speedup = 1/S = 1/0.25 = 4×, since the sequential 30 seconds can never be reduced by adding cores.)

4. A weather app splits a map of India into 12 regions and assigns each region to a separate core to compute tomorrow's temperature independently. Is this data parallelism or task parallelism? (Answer: data parallelism — the same forecasting calculation runs on different chunks of geographic data.)

5. A music app simultaneously runs equalizer processing on one core, downloads the next song on another core, and updates the lyrics display on a third. Is this data parallelism or task parallelism? (Answer: task parallelism — three different operations run at once, not the same operation on split data.)

6. Two cores both run temp = balance; temp = temp + 100; balance = temp on a shared bank balance that starts at ₹500, and their reads happen to interleave exactly like the ticket-counter example in this chapter. What is the final, incorrect value of balance, and what should it have been? (Answer: both read ₹500, both compute ₹600, and both write ₹600 — the final value is ₹600 instead of the correct ₹700, because one core's update overwrote the other's — a race condition caused by unsynchronized access to shared data.)

Summary

A CPU core is an independent unit that executes instructions; single-core chips run everything sequentially, one instruction at a time, while multi-core chips can run genuinely simultaneous streams of instructions — this simultaneity is parallel computing. Splitting the same operation across different pieces of data is data parallelism (summing array chunks, processing map regions); running different operations at the same time is task parallelism (typing, spell-check, and autosave running together). Sharing mutable data between cores without protection creates race conditions, where the final result depends on unpredictable interleaving and can silently come out wrong — locks fix this by forcing one-at-a-time access to the shared value. Concurrency (tasks interleaving, possibly on one core) is a broader idea than parallelism (tasks truly overlapping in time on multiple cores) — every parallel system is concurrent, but not every concurrent system is parallel. Speedup is defined as single-core time divided by N-core time, and it only reaches the ideal value of N when a task has no sequential portion at all; Amdahl's Law shows that any unavoidable sequential fraction S caps the maximum achievable speedup at 1/S, regardless of how many cores are added — a limit worth checking before assuming more cores is always the answer.

← Building a Chatbot with PythonKubernetes: Orchestrating AI at Scale →

Found this useful? Share it!

📱 WhatsApp 🐦 Twitter 💼 LinkedIn