Most people learn programming in a way that makes them feel productive without necessarily making them good at programming.
They solve multiple-choice questions (MCQs), fill in the blanks, memorize syntax, watch tutorials, and collect notes. They might even score well on programming quizzes.
Then someone gives them a problem and says, “Build this.”
Suddenly, they are stuck.
They know what a loop is, but they cannot decide where to use one. They understand functions, but they struggle to break a problem into smaller functions. They recognize the correct syntax when they see it, but cannot write it from scratch.
The problem is that recognizing code and producing code are two completely different skills.
And if your goal is to become a programmer, you need to train the second skill.
1. Why MCQs and Fill-in-the-Blanks Often Suck for Learning Programming
Let's be clear: MCQs and fill-in-the-blanks are not completely useless. They are simply overused for a skill that requires much more than remembering information.
Problem 1: They test recognition, not creation
Imagine this question:
What is the output of the following code?
x = 5
print(x * 2)
Options:
- A. 5
- B. 10
- C. 25
- D. Error
You select B. Correct!
But what have you actually demonstrated? You recognized a simple operation and identified its output.
Now try this:
Write a program that accepts a number and determines whether it is a perfect square.
Suddenly, you must figure out the approach, handle input, choose the right operations, structure the code, and test edge cases.
These tasks demand substantially different levels of thinking.
Recognition is useful, but programming requires production.
Problem 2: The options do your thinking for you
MCQs provide artificial hints. Even if you do not know the answer, you can eliminate options, make an educated guess, or recognize a familiar pattern.
Real programming problems rarely give you four possible algorithms and ask you to pick one.
You must generate possible solutions yourself.
You need to decide:
- What is the actual problem?
- What information do I have?
- What approach should I use?
- What data structures are appropriate?
- What happens in edge cases?
- How do I know the solution is correct?
MCQs can test some of these decisions, but they rarely force you to perform the entire process.
Problem 3: Fill-in-the-blanks encourage shallow memorization
Consider:
for i in range(____):
print(i)
You fill in 5. Done.
But can you write a loop that finds the first five numbers divisible by both 3 and 7? Can you decide whether a loop, a comprehension, or a mathematical shortcut is appropriate?
Knowing that a syntax pattern exists is not the same as knowing when and why to use it.
Fill-in-the-blanks can help you remember syntax. They are much less effective at developing independent problem-solving ability when used alone.
Problem 4: You can get good at quizzes without getting good at coding
This is the dangerous part.
You repeatedly answer questions about lists, dictionaries, loops, functions, and recursion. Your scores improve. You feel confident.
But when you open a blank editor, you struggle to connect these concepts into a working program.
Your practice has optimized the skill you repeatedly exercised: answering questions.
The ability you practice is the ability you improve.
If you mostly practice recognizing correct answers, do not be surprised when writing a complete solution feels difficult.
2. Why Coding Directly From Problems Is Better
Suppose you want to learn Python dictionaries.
You could answer 50 MCQs about dictionary syntax.
Or you could build a word-frequency counter that reads a sentence and counts how often each word appears.
The second approach forces you to use dictionaries for a purpose.
You must understand the input, decide what to store, update counts, handle repeated words, and inspect the output. If the solution fails, you must investigate why.
This is where programming knowledge becomes usable.
Coding develops several skills simultaneously
Problem decomposition: Turning one large task into smaller, manageable steps.
Algorithmic thinking: Figuring out the sequence of operations needed to solve the problem.
Syntax recall: Writing code without depending on a list of options.
Debugging: Finding the difference between what you expected and what actually happened.
Decision-making: Choosing between alternative approaches and understanding their trade-offs.
Transfer: Applying a concept to a problem that does not look exactly like the example you studied.
An MCQ might tell you whether you recognize a concept. A well-designed coding problem makes you use it.
That is why problem-first learning is such a powerful default.
3. But Solving a Ton of Problems Is Not the Answer Either
Here is where many aspiring programmers make another mistake.
They discover coding platforms, open a problem list, and start solving as many questions as possible.
One problem. Then another. Then another hundred.
They collect submissions, maintain streaks, and chase problem counts.
Yet they repeatedly struggle with the same concepts.
Why?
Because the number of problems solved is a poor substitute for the quality of learning extracted from them.
If you solve 100 problems by copying solutions, memorizing patterns, or immediately checking hints, you may learn less than someone who deeply studies 15 carefully selected problems.
Volume matters, but volume without reflection can become repetition without progress.
The problem with random practice
Suppose you solve a binary search problem today, forget the reasoning next week, and then solve a nearly identical problem by remembering the previous code.
You might successfully submit both solutions.
But did you learn to recognize when binary search applies? Can you explain its invariant? Can you implement it with different boundary conditions? Can you avoid infinite loops?
A successful submission does not automatically mean the underlying concept is understood.
The goal is not merely to finish problems.
The goal is to become capable of solving problems independently.
4. Why Repeating Good Problems Is Better Than Constantly Finding New Ones
This is an underrated strategy.
Solving a carefully selected problem multiple times, with increasing independence, can be more valuable than solving many unrelated problems once.
The first attempt teaches you where you are confused.
The second attempt helps you reconstruct the approach.
The third attempt tests whether you understand the reasoning rather than merely remembering the code.
Later attempts reveal whether the technique has become something you can recognize and apply elsewhere.
Consider binary search.
Instead of solving 20 unrelated problems and moving on, select a few representative problems:
- Find a target in a sorted array.
- Find the first occurrence of a target.
- Find the first position where a condition becomes true.
- Find the minimum feasible answer in a search space.
Study how the problems are related and how their differences change the implementation.
You begin to see a reusable idea rather than a collection of isolated answers.
Repeat the problem, but change the conditions
Blindly rewriting the same solution is not enough. Make each repetition serve a different purpose.
Attempt 1: Understand. Solve the problem with documentation or hints when necessary. Focus on the reasoning.
Attempt 2: Reconstruct. Close the solution and solve it from a blank editor.
Attempt 3: Explain. Describe why the algorithm works, its time complexity, and its edge cases.
Attempt 4: Modify. Change the input constraints or expected output. Adapt your solution.
Attempt 5: Transfer. Solve a different problem that uses the same underlying technique.
This is deliberate practice: working on specific weaknesses, getting feedback, and gradually removing support.
Repetition works best when it strengthens understanding instead of reinforcing memorized keystrokes.
5. A Better Way to Practice Programming
Use this cycle for each important problem.
Step 1: Attempt before consuming the solution
Read the problem carefully. Identify the inputs, outputs, constraints, and edge cases.
Try to solve it independently for 20–40 minutes, adjusting the time to your level and the problem's difficulty.
Getting stuck is not automatically failure. It can reveal precisely what you need to learn.
Step 2: Think before writing code
Write down the approach in plain English or pseudocode.
For example, for a word-frequency counter:
- Split the sentence into words.
- Create an empty dictionary.
- Visit each word.
- Increase its count or initialize its count.
- Return the dictionary.
This reduces the mental burden of solving the algorithm and remembering syntax simultaneously.
Step 3: Code the solution yourself
Now translate your approach into code.
Do not immediately copy a finished implementation. If you need a hint, use the smallest hint that helps you move forward.
If you look at a solution, close it afterward and reconstruct the code independently.
Step 4: Test beyond the happy path
A solution that works for one example is not necessarily correct.
Test empty input, duplicate values, boundary cases, unexpected input where relevant, and the largest permitted inputs.
Learn to ask, “What could break this?”
Step 5: Analyze the solution
After solving, ask:
- Why does this algorithm work?
- What is its time and space complexity?
- Is there a simpler solution?
- What assumptions does it depend on?
- Which part was difficult, and why?
Do not optimize everything prematurely. First get correctness, then improve clarity and efficiency where it matters.
Step 6: Revisit the problem later
Try it again after a day, several days, and a week or two.
Do not look at your old solution first.
If you can reconstruct the approach and explain it, that is stronger evidence of learning than recognizing your previous code.
6. The Best Learning Strategy Combines Concepts, Problems, and Repetition
Does this mean you should never use MCQs, quizzes, or fill-in-the-blanks?
No. That would be another mistake.
Different exercises serve different purposes.
| Method | Best use |
|---|---|
| MCQs | Checking conceptual understanding and spotting misconceptions |
| Fill-in-the-blanks | Practicing syntax and recalling specific language features |
| Tracing code by hand | Understanding execution flow and debugging |
| Writing small programs | Building fluency with syntax and basic constructs |
| Solving problems independently | Developing problem-solving and implementation skills |
| Repeating selected problems | Strengthening recall and recognizing reusable patterns |
| Modifying existing solutions | Learning adaptability and understanding trade-offs |
| Building projects | Integrating multiple concepts into a working system |
The strongest approach is not to eliminate every learning method except coding. It is to stop confusing supporting exercises with the main skill you want to develop.
Use MCQs to check what you know. Use coding to find out whether you can actually use it.
7. The Fastest, Easiest, and Hardest Routes to Getting Better
There are different ways to approach programming practice, and each has trade-offs.
The easiest route: Follow tutorials, copy examples, and answer quizzes. This creates quick familiarity but can leave you dependent on guidance.
The fastest practical route for many learners: Attempt a suitable problem, study a hint if necessary, implement the solution, analyze your mistakes, and revisit the problem later. This gives you a tight feedback loop without wasting hours being stuck on something you cannot yet understand.
The hardest route: Solve unfamiliar problems under strict constraints, with no hints or references, and explain correctness and complexity afterward. This develops independence, but it can be unnecessarily frustrating if used before you have the required foundations.
A sensible progression is to start with guided practice, gradually reduce assistance, and eventually test yourself on unfamiliar problems.
Difficulty should increase as your competence increases.
8. Stop Measuring Progress Only by Problem Count
Instead of asking, “How many problems did I solve today?” ask better questions:
- How many problems did I solve without hints?
- Which mistakes did I stop repeating?
- Can I explain my algorithm without reading the code?
- Can I implement the solution again after a few days?
- Can I recognize the same pattern in a different problem?
- Can I explain why my approach is correct?
- Can I identify when my approach is inefficient?
These questions measure capability more directly.
Keep a small mistake log. Record the problem, the mistake, its cause, the correct reasoning, and when you will retry it.
Over time, this log becomes a personalized map of your weaknesses.
Conclusion: Train the Skill You Actually Want
Programming is not a trivia contest.
You do not become a good programmer merely by recognizing correct syntax, memorizing definitions, or collecting solved problems.
You become better by repeatedly turning unclear requirements into working solutions, discovering where your reasoning fails, correcting your mistakes, and applying what you learn to new situations.
MCQs and fill-in-the-blanks have their place. Solving many problems has its place, too. But neither guarantees independent coding ability.
Choose good problems. Attempt them yourself. Study the reasoning. Rebuild the solutions. Revisit them after time has passed. Modify them. Apply the same ideas to unfamiliar situations.
Do not chase the satisfaction of finishing another question when you could be building a skill that lasts.
The real goal is not to remember how you solved a problem yesterday. It is to become the kind of programmer who can solve a problem tomorrow, even when nobody has shown you the answer.
Top comments (0)