Skip to content
MINH VO A working notebook
by an engineer in Vietnam
Foundation9 min read

Balancing full-stack breadth with depth

Balance full-stack breadth with depth by investigating an inventory update bug and defining observable learning goals.

Watercolor notebooks, an open sketchbook and a wooden pencil on a cream surface

I want breadth to help me ask better questions across a feature, while keeping enough depth to make and verify a decision in at least one part of it. For a full-stack developer, that usually means choosing a main learning problem and treating the surrounding tools as supporting knowledge. Trying to study every layer equally can leave the most important question unfinished.

My path includes frontend, backend, DevOps, mobile, desktop, cloud and AI. That range makes prioritization necessary; studying seven new frameworks at once would leave too little time to examine any one of them closely. A more useful plan names what you want to be able to explain or deliver at the end.

Choose a problem with a visible center

Imagine a developer who can build a React interface but struggles to explain why concurrent stock deductions produce the wrong inventory count. Their main problem is write correctness. A new CSS library or another deployment provider may be interesting, but neither resolves the immediate gap.

For a hypothetical learning month, make a small inventory service the center of the work. Start with ten units in stock. Two requests read ten, then subtract two and three units respectively. If each writes its independently calculated value, the last write leaves seven or eight units instead of five. The arithmetic is simple; the interleaving is the lesson. HTTP requests, a database transaction and a basic interface are supporting topics because they let you reproduce and explain that behavior.

Depth here means being able to describe an interleaving, predict the result under the chosen rule and inspect the resulting state. It does not mean having read every chapter about databases. Breadth means recognizing when the interface, API contract or deployment changes the conditions of the experiment.

Write exit conditions before a tool list

A learning plan becomes easier to finish when it contains observable exits. For this inventory exercise, the following evidence makes progress inspectable:

Learning questionEvidence of a useful first answer
Where can an update be lost?A written sequence of two reads and two writes that exposes the conflict
What rule does the application use?A request contract and stored-state example that agree
What does the user see after a conflict?A manual interface trace showing the recovery path
What remains uncertain?A short list of untested cases, such as multi-region writes or offline merging

These goals deliberately leave advanced cases outside the first project. A bounded exercise is more useful when its limits are visible than when it quietly claims to cover “distributed systems.”

A central write-correctness learning goal connects to supporting HTTP, interface and deployment knowledge, with explicit exit evidence belowView full-size image ↗

The center of the diagram should receive most of the investigation effort. The surrounding topics need enough attention to make the central experiment trustworthy. If deployment consumes the entire month, reduce that part to a simpler environment and record it as a separate future learning problem.

Use practice that reveals what you remember

The learning review by Dunlosky and colleagues discusses techniques including practice testing and distributed practice. It is a review of learning research, not a promise that one study schedule works for every engineering task. A small technical artifact lets you check recall and application after some time.

After working through the inventory discrepancy, close the notes and draw the two request sequences again. Predict what the application will store before running the exercise. When the prediction is wrong, write down which assumption failed. Later, change one condition, such as a delayed response, and see whether the explanation still holds.

This asks more of you than recognizing familiar terminology in a tutorial. It also gives the next session a concrete starting point. You are returning to an unresolved model rather than reopening a long list of tabs.

Protect depth from unnecessary expansion

The Google review guidance asks reviewers to examine design, functionality, complexity and tests. I find those categories useful for a learning project too. Can I explain the design? Does the feature behave as intended? Did I introduce complexity that the task does not require? Do the checks cover the behavior I claim?

Suppose the project suddenly seems to need a message broker, a separate authentication service and a new observability stack. Ask which unresolved question requires each addition. If none does, put the idea in a deferred list. There is nothing wrong with learning those systems later. Adding them now may make it harder to tell whether the original write rule is correct.

There is also a cost to narrowing too far. A database exercise that never crosses an API boundary may miss how a client retries a request. Include enough surrounding behavior to expose the central risk, then stop when the evidence is sufficient for the chosen question.

At the end of the month, keep the application, the predicted traces and the failed assumptions. Those artifacts tell you whether the work produced a usable explanation. The next topic can come from the remaining uncertainty, rather than from the longest technology list on someone else’s profile.

Turn the inventory discrepancy into a depth check

Return to the ten-unit inventory example and write both requests as separate histories. Request A reads ten and calculates eight after subtracting two. Request B reads ten and calculates seven after subtracting three. If A writes first and B writes last, the stored value becomes seven. Reversing the writes leaves eight. Neither result represents both accepted deductions, which would leave five.

Now ask what knowledge is missing. The arithmetic is not the problem. The issue is that each request computes a replacement from a value that may be stale by the time it writes. A database feature, application lock or different update contract may address that interaction, but choosing one requires understanding its actual guarantee.

Use this as a self-explanation check. Can you draw the interleaving without looking at notes? Can you explain why putting the same code in another frontend framework does not change the stored result? Can you name the point at which the application needs coordination? If those answers are unclear, more database and concurrency work has a direct purpose.

The PostgreSQL transaction tutorial is a starting point for understanding grouped database work. A transaction boundary alone is not a complete explanation of concurrent behavior; the operations and isolation rules still matter. That distinction is a useful next research question, while unrelated infrastructure changes can remain deferred.

An inventory learning plan connects the wrong stored count to an interleaving explanation, a chosen coordination rule and an independently checked recovery behaviorView full-size image ↗

The additional figure describes evidence of understanding rather than elapsed study time. A person can spend several sessions on a topic and still need help explaining its central failure. Another may already know the principle and need only to learn how a particular database expresses it.

Compare the stale replacement with an atomic deduction

For a concrete alternative, let the database calculate the deduction from the row it updates. In PostgreSQL, a parameterized statement for a single inventory row can express both the quantity change and a no-negative-stock condition:

UPDATE inventory
SET stock = stock - $2
WHERE item_id = $1 AND stock >= $2
RETURNING stock;

Here $1 is the item identifier and $2 is a validated positive integer quantity. The caller must inspect whether a row was returned; zero rows cannot be reported as an accepted deduction. The surrounding application still needs authorization and an appropriate response for a missing item or insufficient stock.

Under PostgreSQL’s Read Committed behavior, an updater can wait for another updater of the same row, then apply its operation to the updated row and recheck the condition. The transaction-isolation documentation describes that behavior. Starting at ten, accepted deductions of two and three therefore leave five in either order. Starting at ten with attempted deductions of eight and three, the first accepted deduction leaves too little for the other; both must not be reported as accepted.

This is a narrower result than “transactions solve concurrency.” It relies on the stated database behavior, a single-row operation and the application’s response to an unmatched condition. It does not make a repeated request safe after a lost response, and it does not establish an invariant spanning several stock rows. Those are separate learning questions. The depth exercise is to explain exactly which stale-replacement problem this statement removes and which problems remain.

Allocate supporting work by the question it answers

Suppose you have a fixed block of time for the practice project. Do not divide it equally among every technology named in the architecture. Reserve the largest share for reproducing and explaining the inventory interaction. Give the interface enough attention to show which request was accepted, and give deployment enough attention to exercise the same contract outside the development process if that environment is part of the goal.

Supporting activityReason to include it nowReason to defer it
A visible request identifierConnects the screen to the stored operationNone if you otherwise cannot interpret the trace
An alternative styling libraryThe current interface prevents a required interactionThe change only makes the exercise look different
A deployment environmentThe failure depends on multiple running instancesThe current question is still reproducible in a small local model
A load generatorYou need to control concurrent attemptsA few explicit interleavings already expose the missing rule
A new database engineThe chosen engine cannot express the required contractYou have not yet inspected the engine you already use

This is a decision table for the example, not a universal allocation formula. Its value is that each added tool must answer a current question. A learning plan can still include curiosity-driven exploration, but it should be clear when that exploration serves a different goal.

Check transfer with a changed condition

After explaining the first race, change one condition. Let A subtract two while B adds a returned item. Predict the correct mathematical total and the possible stale-replacement results. Or keep the quantities fixed and lose A’s response after its update is accepted. Now ask how the caller identifies whether a retry would repeat the deduction.

These variations test different boundaries. The first checks whether you understand the interaction beyond memorizing the original numbers. The second introduces operation identity and uncertain outcomes. It may deserve a later exercise rather than an immediate expansion of the current implementation.

Write the transfer question and its expected result before running it. If the model does not explain the new case, revise the explanation. If the case introduces a separate mechanism, mark that boundary instead of claiming the first solution already covers it. That is how breadth can grow from a deep investigation without turning the project into an uncontrolled list of integrations.

Sources & further reading

  1. Dunlosky et al.: improving students learning with effective learning techniques
  2. Google Engineering Practices: what to look for in a review
  3. PostgreSQL: transactions
  4. PostgreSQL: transaction isolation
← Back to the journal
All notes

Illustration

100%