GO - Goroutines and Channels

Chapter: Goroutines and Channels

Chapter Overview

We will learn this chapter differently: very little theory, very small Go programs, and lots of MCQs.

Assume you know absolutely nothing about goroutines, channels, or even the symbols used in the code. We will first learn what each tiny piece means, and then use questions to make the idea stick.

By the end, you should be able to look at code such as go work(), ch <- 10, x := <-ch, and select and understand what is happening.

1. First: What Problem Are We Trying to Solve?

Imagine Two Jobs

Forget programming for a moment.

Imagine you have two jobs:

  • Job A: download a file.
  • Job B: calculate something.

If you do A first and completely wait for it, and only then do B, you are making B wait unnecessarily.

In programming, we sometimes want different pieces of work to make progress independently.

Go gives us a simple way to start such independent work. It is called a goroutine.

For this chapter, you can initially think:

goroutine = "let this work run separately"

Quick MCQ 1

Question
Suppose a program has two independent jobs. What is one reason we might want them to run independently?

A. So one job can make progress while the other is waiting
B. So the program contains more code
C. So variables disappear
D. So Go stops executing functions

Answer: A

Explanation: Independent work does not always need to wait for other work to finish.

Quick MCQ 2

Question
In this chapter, the easiest beginner mental model for a goroutine is:

A. A database
B. A separate piece of work that Go can run concurrently
C. A variable
D. A programming language

Answer: B

Explanation: A goroutine lets a function or piece of work execute concurrently with other work.

2. What Exactly Is a Goroutine?

Suppose we have this function:

func sayHello() {
    fmt.Println("Hello")
}

Do not worry about every word yet. The important part is that sayHello() is a function call.

If we write:

sayHello()

we are saying:

"Run this function now."

But if we write:

go sayHello()

we are saying:

"Start this function as a goroutine."

The important difference

Code Simple meaning
sayHello() Run the function normally.
go sayHello() Start the function as a goroutine.

Understand the code piece by piece

go sayHello()
  • go = Go keyword telling the program to start a goroutine.
  • sayHello = name of the function.
  • () = call the function.

So you can mentally read:

go + function call = start that function as a goroutine.

MCQ 3

Question
What does go sayHello() mean?

A. Delete sayHello
B. Start sayHello as a goroutine
C. Create a variable called go
D. Stop the program

Answer: B

Explanation: The keyword go tells Go to start the function call as a goroutine.

MCQ 4

Question
Which part of go sayHello() tells Go to use a goroutine?

A. go
B. sayHello
C. ()
D. Nothing

Answer: A

Explanation: go is the special keyword. sayHello is simply the function being called.

MCQ 5

Question
What is the main difference between these two?

work()
go work()

A. Both always mean exactly the same thing
B. The second starts work as a goroutine
C. The first creates a goroutine and the second deletes it
D. Neither calls work

Answer: B

3. A Very Important Surprise: main Can Finish First

Look at this:

func main() {
    go sayHello()

    fmt.Println("Main finished")
}

You might think:

"Surely sayHello() will print first."

No.

Starting a goroutine does not mean the current code waits for it.

The program can reach the end of main() before the goroutine gets a chance to print.

Think of it like this

You tell an employee:

"Please do this task."

Then you immediately close the entire office.

The employee was asked to work, but the office did not remain open long enough.

Similarly, when main() finishes, the Go program finishes.

MCQ 6

Question
What happens when the main function returns?

A. The program continues forever because goroutines exist
B. The program ends
C. All goroutines automatically become independent programs
D. Every channel closes automatically

Answer: B

MCQ 7

Question
Is this code guaranteed to print Hello?

func main() {
    go fmt.Println("Hello")
}

A. Yes, always
B. No, because main may finish before the goroutine runs
C. Yes, because go waits
D. No, because fmt.Println cannot run in a goroutine

Answer: B

Important lesson

Starting a goroutine and waiting for a goroutine are two different things.

4. Before Channels: How Can Two Goroutines Share Information?

Suppose one goroutine produces a number:

42

and another goroutine needs that number.

We need some way to pass the number from one piece of work to another.

Go gives us channels.

For a beginner, think of a channel as:

A tube through which goroutines can pass values to each other.

For example:

Worker A ----> channel ----> Worker B

Worker A can put a value into the channel.

Worker B can take that value out.

MCQ 8

Question
What is the easiest mental model for a Go channel?

A. A communication tube for values between goroutines
B. A function that prints text
C. A database table
D. A CPU core

Answer: A

MCQ 9

Question
Why might one goroutine need a channel?

A. To communicate a value to another goroutine
B. To change the Go language
C. To create a computer
D. To rename a function

Answer: A

5. Creating Your First Channel

Here is our first channel:

ch := make(chan int)

We will break every part apart.

Part Meaning
ch The name we chose for the channel.
:= Create a new variable and give it a value.
make Creates certain Go data structures, including channels.
chan Says we are creating a channel.
int This channel will carry integer numbers.

So:

ch := make(chan int)

means:

"Create a channel named ch that carries integer numbers."

Why specify int?

Because channels are typed.

This:

make(chan int)

is for integers.

This:

make(chan string)

is for strings.

MCQ 10

Question
What type of values can ch carry?

ch := make(chan int)

A. Only strings
B. Only integers
C. Only functions
D. Any value without restrictions

Answer: B

MCQ 11

Question
Which creates a channel for strings?

A. make(string)
B. make(chan string)
C. chan(string)
D. new(string)

Answer: B

6. Putting a Value INTO a Channel

Suppose we have:

ch := make(chan int)

How do we put 10 into it?

ch <- 10

This strange-looking symbol is extremely important.

Read it as:

"Send 10 into ch."

Notice the direction:

ch <- 10

The value 10 is going into the channel.

MCQ 12

Question
What does this mean?

ch <- 50

A. Receive 50 from ch
B. Send 50 into ch
C. Delete 50
D. Close ch

Answer: B

MCQ 13

Question
In ch <- 25, which is the value being sent?

A. ch
B. 25
C. <-
D. Nothing

Answer: B

7. Taking a Value OUT of a Channel

Now suppose another goroutine wants to receive the value.

We write:

x := <-ch

Read it as:

"Take a value from ch and put it into x."

Code Meaning
ch <- 10 Send 10 into ch.
x := <-ch Receive a value from ch into x.

The complete picture

Sender
   |
   | ch <- 42
   ↓
[ CHANNEL ]
   |
   | x := <-ch
   ↓
Receiver

MCQ 14

Question
What does x := <-ch do?

A. Sends x into ch
B. Receives a value from ch and stores it in x
C. Deletes ch
D. Creates a goroutine

Answer: B

MCQ 15

Question
Which one sends a value?

A. x := <-ch
B. ch <- 10
C. close(ch)
D. make(chan int)

Answer: B

MCQ 16

Question
Which one receives a value?

A. ch <- 10
B. x := <-ch
C. close(ch)
D. make(chan int)

Answer: B

8. Your First Complete Goroutine + Channel Program

Now combine everything.

package main

import "fmt"

func main() {
    ch := make(chan int)

    go func() {
        ch <- 42
    }()

    value := <-ch

    fmt.Println(value)
}

Do not memorize it. Read it like a story.

  1. ch := make(chan int) creates a channel for integers.
  2. go func() { ... }() starts a goroutine.
  3. Inside the goroutine, ch <- 42 sends 42 into the channel.
  4. value := <-ch receives that 42.
  5. fmt.Println(value) prints it.

Output:

42

What does func() { ... }() mean?

This is an anonymous function: a function without a name.

You can think of:

func() {
    ch <- 42
}

as:

"Here is a small piece of work."

And:

go func() {
    ch <- 42
}()

means:

"Run this small piece of work as a goroutine."

MCQ 17

Question
What value does the goroutine send?

go func() {
    ch <- 42
}()

A. 0
B. 1
C. 42
D. ch

Answer: C

MCQ 18

Question
What does value := <-ch receive in the program above?

A. 42
B. The channel itself
C. Nothing
D. The function

Answer: A

MCQ 19

Question
What is the final output?

ch := make(chan int)

go func() {
    ch <- 100
}()

x := <-ch

fmt.Println(x)

A. 0
B. 1
C. 100
D. It must print a random number

Answer: C

Explanation: The goroutine sends 100, and the main goroutine receives that value.

9. The Most Important Idea: Blocking

Now we need to understand one behavior that explains many channel questions.

With an ordinary function call, you generally know the function executes and returns.

Channels can wait.

For example:

x := <-ch

If there is currently no value available to receive, the goroutine waits.

Similarly, with an unbuffered channel:

ch <- 10

the sender waits until another goroutine can receive the value.

Think about a real door

Imagine you want to hand a package to another person.

  • If the person is there, you hand it over.
  • If nobody is there, you wait.

An unbuffered channel behaves somewhat like this direct handoff.

MCQ 20

Question
Suppose ch is an empty channel and no goroutine will ever send anything into it. What happens here?

x := <-ch

A. x becomes 10
B. The receive waits forever
C. The channel automatically creates a value
D. The program prints the channel

Answer: B

MCQ 21

Question
For an unbuffered channel, what happens if a goroutine sends a value but there is no receiver ready?

A. The value is stored forever
B. The send waits
C. The value becomes zero
D. The channel closes

Answer: B

10. Unbuffered Channel: Understand It With a Picture

This:

ch := make(chan int)

creates an unbuffered channel.

Think:

Sender ---- hand the value directly ----> Receiver

There is no waiting room inside the channel.

If the sender arrives first:

Sender
  |
  | "I have 10"
  ↓
WAITING
  |
  | Receiver arrives
  ↓
Transfer happens

If the receiver arrives first:

Receiver
   |
   | "I need a value"
   ↓
WAITING
   |
   | Sender arrives
   ↓
Transfer happens

MCQ 22

Question
An unbuffered channel is best imagined as:

A. A huge storage box
B. A direct handoff between sender and receiver
C. An unlimited queue
D. A database

Answer: B

11. Buffered Channels: A Small Waiting Room

Now look at:

ch := make(chan int, 3)

The 3 means:

The channel can hold up to three values in its buffer.

Think of it as a small waiting room.

Sender
   ↓
[ 10 ][ 20 ][ 30 ]
       CHANNEL
             ↓
          Receiver

The sender can put values into the available spaces without requiring a receiver immediately.

Example

ch := make(chan int, 2)

ch <- 10
ch <- 20

fmt.Println(<-ch)
fmt.Println(<-ch)

Output:

10
20

Why?

The channel has two spaces.

First:

[10][ ]

Second:

[10][20]

Then receiving removes 10:

[20][ ]

Then receiving removes 20.

MCQ 23

Question
What does the 2 mean here?

make(chan int, 2)

A. The channel can hold two buffered values
B. The channel can hold only the number 2
C. Two goroutines are automatically created
D. Two channels are created

Answer: A

MCQ 24

Question
What happens here?

ch := make(chan int, 2)

ch <- 10
ch <- 20

A. Both values can enter the buffer
B. The first send always deadlocks
C. The channel closes
D. The second value replaces the first

Answer: A

MCQ 25

Question
What happens when a buffered channel is full and another value is sent?

A. The old value is automatically deleted
B. The sender waits until space becomes available
C. The channel doubles its size automatically
D. The value becomes zero

Answer: B

12. Unbuffered vs Buffered: The Question You Must Master
Question Unbuffered Buffered
Example make(chan int) make(chan int, 3)
Has storage space? No Yes, limited
Can sender proceed without receiver? No Yes, if buffer has space
What happens when buffer is full? Not applicable Sender waits

MCQ 26

Question
Which channel can hold values temporarily?

A. Only an unbuffered channel
B. A buffered channel
C. A nil channel
D. A closed channel only

Answer: B

MCQ 27

Question
Which statement is correct?

A. Every channel is automatically buffered
B. make(chan int) has capacity 1
C. make(chan int, 5) has buffer capacity 5
D. Buffered channels have unlimited capacity

Answer: C

13. Channel Values Behave Like a Queue

Suppose:

ch := make(chan int, 3)

ch <- 10
ch <- 20
ch <- 30

Now receive three times:

fmt.Println(<-ch)
fmt.Println(<-ch)
fmt.Println(<-ch)

Output:

10
20
30

The values come out in the same order in which they were sent.

Think of a line of people:

10 → 20 → 30

first person out = 10
second person out = 20
third person out = 30

MCQ 28

Question
What is the output?

ch := make(chan int, 3)

ch <- 5
ch <- 8
ch <- 12

fmt.Println(<-ch)

A. 12
B. 8
C. 5
D. Random number

Answer: C

MCQ 29

Question
If the values are sent in this order:

1
2
3
4

what does the first receive normally get?

A. 4
B. 3
C. 1
D. Random value

Answer: C

14. Closing a Channel: "I Am Finished Sending"

Imagine a worker is sending numbers:

10
20
30

How can it tell the receiver:

"I am finished. I will not send anything else."

It can close the channel:

close(ch)

Important:

Closing a channel does not mean deleting it.

It means:

"No more values will be sent."

Example

ch := make(chan int, 2)

ch <- 10
ch <- 20

close(ch)

fmt.Println(<-ch)
fmt.Println(<-ch)

The values 10 and 20 are still available.

MCQ 30

Question
What does close(ch) primarily tell receivers?

A. Delete ch
B. No more values will be sent
C. Empty all values
D. Start a goroutine

Answer: B

MCQ 31

Question
Suppose:

ch := make(chan int, 2)
ch <- 10
close(ch)

Can 10 still be received?

A. Yes
B. No
C. Only if the channel is reopened
D. Only after another value is sent

Answer: A

MCQ 32

Question
What happens if you send to a channel after closing it?

A. The value is ignored safely
B. The channel reopens
C. The program panics
D. The value waits forever

Answer: C

15. How Can We Know Whether a Channel Is Closed?

Go gives us this useful form:

value, ok := <-ch

There are two pieces of information:

  • value = the received value.
  • ok = whether a value was successfully received from an open channel.

If the channel is closed and there are no values left:

ok == false

Example

ch := make(chan int, 1)

ch <- 99
close(ch)

value, ok := <-ch

fmt.Println(value)
fmt.Println(ok)

Output:

99
true

Why is ok true?

Because 99 was successfully received.

After that:

value, ok := <-ch

produces:

0 false

because the channel is closed and empty.

MCQ 33

Question
What does ok == false tell us in the usual channel receive pattern?

A. A value was successfully received
B. The channel is closed and has no more values to receive
C. The channel is buffered
D. A goroutine was created

Answer: B

MCQ 34

Question
After a channel is closed and all buffered values have been consumed, what does receiving an int produce?

A. An arbitrary integer
B. The integer zero, with ok == false
C. A panic every time
D. The last sent value forever

Answer: B

16. range: "Keep Taking Values"

Suppose a worker produces several numbers.

Instead of repeatedly writing:

x := <-ch
x := <-ch
x := <-ch

we can write:

for value := range ch {
    fmt.Println(value)
}

This means:

"Keep receiving values from ch and run this code for each value."

When the channel is closed and all remaining values have been consumed, the loop ends.

Example

ch := make(chan int, 3)

ch <- 10
ch <- 20
ch <- 30

close(ch)

for value := range ch {
    fmt.Println(value)
}

Output:

10
20
30

MCQ 35

Question
What does this generally mean?

for value := range ch {
    fmt.Println(value)
}

A. Keep receiving values from ch
B. Send values into ch
C. Close ch repeatedly
D. Create a new channel each time

Answer: A

MCQ 36

Question
What normally allows a channel range loop to finish after all values are processed?

A. The channel being closed
B. The channel being renamed
C. The program creating another variable
D. A random number

Answer: A

17. Who Should Usually Close the Channel?

This is a very useful rule.

Imagine:

Producer → Channel → Consumer

The producer knows when it has finished sending.

Therefore, the producer is normally the natural place to close the channel.

func producer(ch chan<- int) {
    ch <- 10
    ch <- 20
    close(ch)
}

The consumer can then do:

for value := range ch {
    fmt.Println(value)
}

Why shouldn't the consumer casually close it?

The producer may still try to send something.

Sending to a closed channel causes a panic.

MCQ 37

Question
Which goroutine usually knows best when an output channel should be closed?

A. The producer that knows it has finished sending
B. A random consumer
C. The operating system
D. The CPU

Answer: A

18. Send-Only and Receive-Only Channels

Suppose we have a producer.

The producer only needs to send.

We can tell Go that explicitly:

func producer(ch chan<- int) {
    ch <- 10
}

The symbol:

chan<- int

means:

send-only channel of integers.

For receiving only:

func consumer(ch <-chan int) {
    value := <-ch
    fmt.Println(value)
}

<-chan int means:

receive-only channel of integers.

Do not confuse these

Syntax Meaning
chan int Can send and receive.
chan<- int Send only.
<-chan int Receive only.

MCQ 38

Question
What does chan<- int mean?

A. Receive-only integer channel
B. Send-only integer channel
C. Two integer channels
D. Closed integer channel

Answer: B

MCQ 39

Question
What does <-chan string mean?

A. Send-only string channel
B. Receive-only string channel
C. A string variable
D. A closed channel

Answer: B

19. select: Waiting for Different Possibilities

Imagine you are waiting for one of two things:

  • Message A arrives.
  • Message B arrives.

You do not want to check only A and ignore B.

Go gives us select.

select {
case x := <-ch1:
    fmt.Println(x)

case y := <-ch2:
    fmt.Println(y)
}

For now, understand it as:

"Wait until one of these channel operations can happen."

Simple picture

             ┌── ch1 ready? → do case 1
select ──────┤
             └── ch2 ready? → do case 2

Why is this useful?

It is useful when a program is waiting for different possible events.

For example:

  • A server response.
  • A timeout.
  • A cancellation signal.

MCQ 40

Question
What is the simplest purpose of select?

A. Wait for one of multiple channel operations
B. Create a channel
C. Close every channel
D. Create a function

Answer: A

MCQ 41

Question
Suppose both channel cases in a select are ready. Which one is guaranteed to run first?

A. The first case in the source code
B. The second case in the source code
C. Go selects among the ready cases; you should not depend on source order
D. Both always execute

Answer: C

20. select + default

Consider:

select {
case value := <-ch:
    fmt.Println(value)

default:
    fmt.Println("Nothing available")
}

If the receive is ready, the receive case can run.

If the receive is not ready, default runs immediately.

So the beginner mental model is:

"Try the channel operation. If it cannot happen right now, do something else."

MCQ 42

Question
If no channel case is ready and a default case exists, what happens?

A. The program waits forever
B. The default case runs
C. The channel closes
D. A goroutine is automatically created

Answer: B

21. Timeouts: "Don't Wait Forever"

Imagine your program asks another server for information.

You send:

"Please give me the result."

But the other server never responds.

You do not want your program to wait forever.

Go can combine a channel with select and a timer.

select {
case result := <-resultCh:
    fmt.Println(result)

case <-time.After(2 * time.Second):
    fmt.Println("Timed out")
}

Read it as:

  • If the result arrives first → use it.
  • If two seconds pass first → handle the timeout.

Understand 2 * time.Second

  • 2 = two.
  • time.Second = one second duration.
  • 2 * time.Second = two seconds.

MCQ 43

Question
Why might a timeout be used when communicating with an external service?

A. To allow the program to wait forever
B. To prevent waiting indefinitely
C. To close the computer
D. To create more CPUs

Answer: B

MCQ 44

Question
In this code, what happens if resultCh does not produce a value within two seconds?

select {
case result := <-resultCh:
    fmt.Println(result)

case <-time.After(2 * time.Second):
    fmt.Println("Timed out")
}

A. The timeout case can run
B. The result becomes 2
C. The program automatically retries forever
D. The channel becomes buffered

Answer: A

22. nil Channel: A Channel That Was Never Created

Look at:

var ch chan int

We declared a channel variable, but we did not create an actual channel.

Its value is nil.

To create one, we need:

ch := make(chan int)

This difference is important.

Code Meaning
var ch chan int ch is nil.
ch := make(chan int) ch refers to an actual channel.

Communication with a nil channel blocks forever.

MCQ 45

Question
What is the problem here?

var ch chan int
ch <- 10

A. ch is nil, so the send blocks forever
B. ch automatically stores 10
C. ch is buffered with capacity 1
D. The send closes the channel

Answer: A

MCQ 46

Question
Which line actually creates the channel?

A. var ch chan int
B. ch := make(chan int)
C. ch = nil
D. chan int

Answer: B

23. Deadlock: When Everyone Is Waiting

This is one of the most important concepts for understanding channel MCQs.

Look at:

func main() {
    ch := make(chan int)

    ch <- 10

    fmt.Println(<-ch)
}

A beginner might think:

"First put 10 into the channel. Then take 10 out."

But remember: this is an unbuffered channel.

The send needs a receiver.

But the receiver is written after the send.

Therefore:

ch <- 10
   ↓
"I need a receiver!"
   ↓
No receiver is running
   ↓
WAIT

The program never reaches:

<-ch

This situation is a deadlock.

MCQ 47

Question
Why can this code deadlock?

ch := make(chan int)

ch <- 10

fmt.Println(<-ch)

A. Because 10 is an invalid integer
B. Because an unbuffered send needs a receiver, but no receiver is ready
C. Because channels cannot contain integers
D. Because fmt cannot print integers

Answer: B

MCQ 48

Question
Which change can allow this basic communication to work?

A. Start the receiver in another goroutine before sending
B. Delete the channel
C. Close the channel before sending
D. Replace 10 with a string

Answer: A

24. The Same Code, But Correctly Coordinated

Now:

func main() {
    ch := make(chan int)

    go func() {
        ch <- 10
    }()

    fmt.Println(<-ch)
}

Now the sender is running in a separate goroutine.

The main goroutine is receiving.

So communication can happen.

Think:

Goroutine 1                  Main
    |                          |
    | ---- send 10 ---------->|
    |                          |
    |                       receive

MCQ 49

Question
Why does this version work conceptually?

go func() {
    ch <- 10
}()

fmt.Println(<-ch)

A. The channel became buffered automatically
B. One goroutine can send while the main goroutine receives
C. The channel contains infinite values
D. close was called

Answer: B

25. WaitGroup: "Tell Me When Everyone Is Finished"

Sometimes we do not need to send a result between goroutines.

We simply want to say:

"Start these workers, and don't continue until all of them are finished."

Go provides sync.WaitGroup for this kind of coordination.

Example:

var wg sync.WaitGroup

wg.Add(2)

go func() {
    defer wg.Done()
    fmt.Println("Worker 1")
}()

go func() {
    defer wg.Done()
    fmt.Println("Worker 2")
}()

wg.Wait()

fmt.Println("Finished")

Understand the three important commands

Code Simple meaning
wg.Add(2) We are waiting for two pieces of work.
wg.Done() This worker has finished.
wg.Wait() Wait until all registered work finishes.

Channel vs WaitGroup

Remember this distinction:

Channel → "Give me information."

WaitGroup → "Tell me when everyone is done."

MCQ 50

Question
What does wg.Wait() primarily do?

A. Send a value through a channel
B. Wait for registered goroutines to finish
C. Create a goroutine
D. Close a channel

Answer: B

26. Worker Pool: Many Jobs, Limited Workers

Imagine 10,000 jobs.

You could potentially create many goroutines, but sometimes you want to control how many pieces of work happen concurrently.

A worker pool is a common pattern.

Imagine:

1000 jobs
   ↓
[ Job Queue ]
   ↓
Worker 1
Worker 2
Worker 3
Worker 4
Worker 5

The workers repeatedly take jobs from the queue.

Tiny worker example

func worker(jobs <-chan int, results chan<- int) {
    for job := range jobs {
        results <- job * 2
    }
}

Understand the code

  • jobs = channel from which workers receive jobs.
  • <-chan int = jobs can only be received here.
  • job = one job received from the channel.
  • job * 2 = pretend processing.
  • results <- ... = send the result to another channel.

MCQ 51

Question
What is a worker pool mainly useful for?

A. Controlling how many workers process jobs concurrently
B. Removing all concurrency
C. Converting integers to strings
D. Closing every channel

Answer: A

27. Fan-Out and Fan-In Without the Scary Names

These names sound complicated, but the ideas are simple.

Fan-out

One stream of jobs is distributed to multiple workers.

             → Worker 1
Jobs → Queue → Worker 2
             → Worker 3

That is fan-out.

Fan-in

Multiple sources send results into one place.

Worker 1 ──\
Worker 2 ───→ Results
Worker 3 ──/

That is fan-in.

MCQ 52

Question
One jobs channel feeds three workers. What is this pattern called?

A. Fan-in
B. Fan-out
C. Deadlock
D. Buffer overflow

Answer: B

MCQ 53

Question
Three workers send their results into one results channel. What pattern does this describe?

A. Fan-in
B. Fan-out
C. Nil channel
D. Goroutine leak

Answer: A

28. Pipeline: Work in Stages

Imagine a factory.

Raw material
     ↓
Cutting
     ↓
Painting
     ↓
Packaging

Each stage does one job.

Programming can use a similar idea:

Input
  ↓
Stage 1
  ↓
Channel
  ↓
Stage 2
  ↓
Channel
  ↓
Stage 3

Each stage can be connected using channels.

Small example

func square(in <-chan int, out chan<- int) {
    for n := range in {
        out <- n * n
    }
    close(out)
}

Read it as:

"Take numbers from the input channel, square them, put the answers into the output channel, and close the output when finished."

MCQ 54

Question
What is the basic idea of a pipeline?

A. Different stages process data and pass it to the next stage
B. Every function must be written in one line
C. All channels must be closed immediately
D. No goroutines can be used

Answer: A

29. Goroutine Leak: "The Worker That Never Goes Home"

Imagine you hire a worker and tell them:

"Wait here until someone gives you a job."

But nobody ever gives them a job.

They stay there forever.

A goroutine can have the same problem.

go func() {
    value := <-ch
    fmt.Println(value)
}()

If nobody ever sends to ch, this goroutine can remain blocked.

This is commonly called a goroutine leak.

Important question whenever you write go

"How will this goroutine eventually finish?"

MCQ 55

Question
What can happen if a goroutine waits forever for a channel value that will never arrive?

A. Goroutine leak
B. Automatic garbage collection of the goroutine immediately
C. The channel becomes buffered
D. The goroutine becomes a function

Answer: A

30. Context: Telling Work to Stop

Suppose a web request starts some work.

Then the user closes the browser.

It may no longer make sense for every piece of work related to that request to continue.

Go commonly uses context.Context for cancellation and deadlines.

You will often see:

select {
case <-ctx.Done():
    return

case value := <-work:
    fmt.Println(value)
}

For now, the important idea is:

ctx.Done() can tell a goroutine that the operation should stop.

MCQ 56

Question
What is one major use of context.Context in concurrent Go programs?

A. Cancellation and deadlines
B. Creating integer variables
C. Printing text
D. Replacing all functions

Answer: A

Chapter-Wide Quick Reference

The 10 Things You Absolutely Must Understand

  1. go work() starts work as a goroutine.
  2. A goroutine is a piece of work that can run concurrently.
  3. make(chan int) creates an unbuffered channel carrying integers.
  4. make(chan int, 3) creates a buffered channel with capacity 3.
  5. ch <- 10 means "send 10 into ch."
  6. x := <-ch means "receive a value from ch into x."
  7. close(ch) means "no more values will be sent."
  8. for x := range ch repeatedly receives values until the channel is closed and drained.
  9. select waits for one of several channel operations.
  10. Whenever you start a goroutine, understand how it will communicate and how it will eventually stop.

Syntax Table

Code Read it as
go f() Run f as a goroutine.
make(chan int) Create an unbuffered integer channel.
make(chan int, 3) Create an integer channel with capacity 3.
ch <- 5 Send 5 into ch.
x := <-ch Receive from ch into x.
close(ch) No more sending.
x, ok := <-ch Receive x and check whether the receive succeeded.
for x := range ch Keep receiving from ch.
chan<- int Send-only integer channel.
<-chan int Receive-only integer channel.
select Wait for one of several channel operations.

Channel Behavior

Situation What happens?
Receive from empty unbuffered channel Waits.
Send to unbuffered channel with no receiver Waits.
Send to buffered channel with free space Value enters buffer.
Send to full buffered channel Waits.
Receive from closed channel with remaining values Remaining values can still be received.
Receive from closed and empty channel Returns zero value and ok == false.
Send to closed channel Panic.
Close already closed channel Panic.
Send to nil channel Blocks forever.
Receive from nil channel Blocks forever.

Common Confusions

Concept A Concept B Difference
ch <- 10 <-ch First sends; second receives.
make(chan int) make(chan int, 3) First is unbuffered; second has capacity 3.
go work() work() First starts a goroutine; second calls the function normally.
Close channel Delete channel Closing means no more sends; it does not mean immediate deletion.
Closed channel Nil channel A closed channel can be received from; a nil channel blocks on communication.
Channel WaitGroup Channel communicates values; WaitGroup waits for goroutines to finish.
Fan-out Fan-in Fan-out distributes work; fan-in combines streams.
Deadlock Goroutine leak Deadlock means required operations cannot make progress; a leak means an unwanted goroutine remains alive or blocked.

Final Mastery Test

Challenge 1 — Predict the Output
func main() {
    ch := make(chan int, 2)

    ch <- 10
    ch <- 20

    fmt.Println(<-ch)
    fmt.Println(<-ch)
}

Answer:

10
20

Why? The channel has capacity 2, so both values fit. They are received in the same order they were sent.

Challenge 2 — Find the Problem
func main() {
    ch := make(chan int)

    ch <- 50

    fmt.Println(<-ch)
}

Answer: Deadlock.

Why? The channel is unbuffered. The send needs another goroutine to receive the value, but the receive occurs later in the same blocked goroutine.

Challenge 3 — Fix the Problem
func main() {
    ch := make(chan int)

    go func() {
        ch <- 50
    }()

    fmt.Println(<-ch)
}

Answer: This can successfully print 50.

Why? The sender and receiver can now run as separate goroutines of execution.

Challenge 4 — Buffered or Unbuffered?

Question: Which of these allows the following two sends to proceed without an immediate receiver?

ch := make(chan int, 2)

ch <- 10
ch <- 20

A. Yes
B. No

Answer: A

Why? The buffer has two available spaces.

Challenge 5 — Closing and Receiving
ch := make(chan int, 1)

ch <- 99
close(ch)

x, ok := <-ch

fmt.Println(x, ok)

Answer:

99 true

Why? Closing does not throw away the buffered 99. It can still be received successfully.

Challenge 6 — After the Channel Is Empty

Now execute another receive:

x, ok := <-ch
fmt.Println(x, ok)

Answer:

0 false

Why? The channel is closed and contains no remaining values. The zero value for int is 0, and ok is false.

Challenge 7 — range
ch := make(chan int, 3)

ch <- 2
ch <- 4
ch <- 6

close(ch)

for x := range ch {
    fmt.Println(x)
}

Answer:

2
4
6

Why? The loop receives each buffered value and finishes after the closed channel has been drained.

Challenge 8 — Direction

What does this function parameter mean?

func send(ch chan<- int) {
    ch <- 10
}

Answer: ch is a send-only integer channel inside the function.

Why? The arrow points toward the channel type: chan<- int.

Challenge 9 — select
select {
case x := <-ch:
    fmt.Println(x)
default:
    fmt.Println("No value")
}

Question: If no value is currently available and no sender is ready, what happens?

Answer: No value is printed.

Why? The default case is selected when no other case is ready.

Challenge 10 — The Big Picture

Question: Which sequence correctly describes the following program?

ch := make(chan int)

go func() {
    ch <- 123
}()

x := <-ch

fmt.Println(x)

A. Create channel → start goroutine → send 123 → receive 123 → print 123

B. Create channel → close channel → print 0

C. Create channel → receive first → send later → always deadlock

D. Create buffered channel → send two values → print both

Answer: A

Explanation: This is the fundamental goroutine-and-channel pattern: one piece of work produces a value, a channel carries it, and another piece of work receives it.

One-Minute Chapter Revision

Start here:

go work()

means:

"Run work as a goroutine."

Then:

ch := make(chan int)

means:

"Create an integer communication channel."

Send:

ch <- 10

means:

"Put 10 into the channel."

Receive:

x := <-ch

means:

"Take a value out of the channel and put it into x."

Buffered:

make(chan int, 3)

means:

"Create an integer channel with room for three waiting values."

Close:

close(ch)

means:

"I am finished sending."

Range:

for x := range ch

means:

"Keep taking values until the channel is closed and finished."

Multiple choices:

select {
case x := <-ch1:
case y := <-ch2:
}

means:

"Wait for whichever communication is ready."

And the most important mental model:

GOROUTINE
    |
    | sends value
    ↓
 CHANNEL
    |
    | receives value
    ↓
ANOTHER GOROUTINE

If you understand that picture, you understand the heart of Goroutines and Channels.

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.