Crux Read small code snippets — a counter object, a module's exports, a stale-state breach, and a stack-overflow leak — and pick what the abstraction actually exposes, hides, or fails to hide.
Your altitude — climbing toward senior
ZeroJuniorMiddleSenior
You are at middle altitude — in the sky
◷ 14 min
Abstraction is judged in the code, not the definition. Read each snippet, decide where the interface ends and the hidden implementation begins, and spot the moment the boundary holds — or breaks.
Goal
Practise the read a working programmer runs constantly: find the interface a caller depends on, find the implementation behind it, and tell whether the hiding actually holds under the code in front of you.
Snippet 1 — the counter bundle
const counter = { count: 0, // data field — the state increment() { // method field — an operation this.count = this.count + 1; }, value(): number { // method field — another operation return this.count; },};counter.increment();counter.increment();let n = counter.value(); // n becomes 2
Quiz
Completed
Which line is the only one that names a data field, and what does that tell you about the bundle's interface?
Heads-up counter.value() calls a method — it never writes count. The method reads count internally via this; the caller only sees the method, which is exactly the point of the bundle.
Heads-up The object is about count, but only the method bodies touch it through this. The three caller lines name only the methods — that separation is what encapsulation provides.
Heads-up The method bodies do name it, as this.count. Data fields are nameable inside the object's own methods; what encapsulation hides is naming them from outside.
// another module, importing the one aboveimport { format } from "./text-format";format(" hi "); // okpad("x"); // ??? — pad was never exported
Quiz
Completed
The importing module calls pad('x') directly. What happens, and what is the module's public interface?
Heads-up Living in the same file as an export does not export a name. Only names marked export cross the boundary; pad has no export, so the importing module cannot see it.
Heads-up Calling a private helper internally does not export it. format using pad is an implementation detail; pad stays private and unreachable from outside.
Heads-up The module loads fine; only the pad('x') call is the error. The boundary blocks that one reference — it does not break the module that legally exported format.
Snippet 3 — the breach
// module: banklet balance = 100; // private — no exportexport function deposit(n: number) { balance = balance + n; }export function getBalance() { return balance; }
// caller — what it can and cannot doimport { deposit, getBalance } from "./bank";deposit(50); // ok — uses the interfacebalance = -999; // ??? — reaching past the interface
Quiz
Completed
The caller assigns balance = -999 to force the balance negative, bypassing deposit. Why does the module's design stop this from being a real risk?
Heads-up The caller cannot reach the module's balance: it was never exported. The assignment touches a separate name in the caller, not the module's private field, so the module's state is safe.
Heads-up Exporting deposit exports the function, not the variable balance. The private balance stays private regardless of which functions are exported alongside it.
Heads-up That is exactly what the boundary prevents. A private name cannot be reached or overwritten from outside; that enforced hiding is what lets the module guarantee its invariant.
Snippet 4 — the leak
function depth(n: number): number { return depth(n + 1); // never stops — no base case}depth(0); // RangeError: Maximum call stack size exceeded
Quiz
Completed
The function-call abstraction promises 'call it, it runs, it returns'. This code crashes naming the call stack. What did the unit call this, and what is the lesson?
Heads-up The abstraction is fine — the bug is the missing base case. The point is that even a good abstraction leaks the lower layer (the stack) when that layer hits a real limit, not that recursion is forbidden.
Heads-up The syntax is valid; it runs until the stack overflows. The error is a runtime limit of the hidden layer surfacing — the definition of a leak — not a parse problem.
Heads-up More memory only delays the overflow; unbounded recursion still exhausts any finite stack. No amount of memory makes an abstraction over a real machine leak-proof.
Recap
Every snippet is the same read at a different scale. In the counter, the interface is the methods and count is hidden behind this. In the module, only the exported format crosses the boundary while pad, trim, and TABLE stay private. In the bank, a private balance cannot be reached from outside, so the module can guarantee its invariant — the boundary turns a hope into a rule. And in the runaway recursion, the finite call stack leaks through the call abstraction the moment the lower layer hits its limit. Find the interface, find the hidden implementation, and check whether the hiding holds.