Your concept feed

48 ideas

A few small steps. No rush.

Follow the connections in this topic

A familiar idea

Start with one small responsibility.

2 seats
book 1
1 left

A room has two seats left. Booking one should leave one. Booking three should fail without changing anything.

Swipe when you’re ready
One new wrinkle

Keep the rule beside the data.

seats
book(count)
never negative

If anyone can subtract seats anywhere, one caller may forget the check. Give booking to the object that owns availability.

Same idea. One more small step.
Try a tiny example

A class describes that little owner.

class: room plan
object: this room

An object is one actual room created from the class. A method is an operation on that room; self refers to this particular room.

Same idea. One more small step.
Notice what matters

Different rooms keep separate state.

room A: 1
room B: 2

Booking in room A should not reduce room B’s seats. Each object carries its own availability.

Want to try a tiny question?
No pressure. Just try it.

What should a failed booking do to the seat count?

You can also keep scrolling
1 / 5
Low-level design

One small step at a time.

This idea builds on these smaller ones. Pick any to revisit it here.

Name a tiny job.

3
add one
4

Our job is “add one.” Give it 3, and it gives back 4.

1 / 3
A familiar idea

An order and its receipt have different jobs.

order: total
renderer: text

The order knows its items and total. The receipt renderer knows how to turn that information into text.

Swipe when you’re ready
One new wrinkle

Let one ask the other for help.

checkout
order
renderer

A checkout object holds an order and a renderer. It delegates each small job rather than doing everything itself.

Same idea. One more small step.
Try a tiny example

That relationship is composition.

has a helper
not is a helper

Checkout has a renderer. It is not a special kind of renderer. Changing the receipt layout need not change price calculations.

Same idea. One more small step.
Notice what matters

Separate changes, not every line.

pricing changes
layout changes

Split responsibilities when they have genuinely different reasons to change. A tiny stable function does not need three extra classes.

Want to try a tiny question?
No pressure. Just try it.

Is “checkout is a kind of receipt renderer” the useful relationship?

You can also keep scrolling
1 / 5
Low-level design

One small step at a time.

This idea builds on these smaller ones. Pick any to revisit it here.

Start with one small responsibility.

2 seats
book 1
1 left

A room has two seats left. Booking one should leave one. Booking three should fail without changing anything.

1 / 4
A familiar idea

A reminder only needs a sender.

reminder
send(message)

It says “send this message.” It does not need to know whether the sender uses email, a screen, or a test notebook.

Swipe when you’re ready
One new wrinkle

Write down the small promise.

send a string
return normally or raise

An interface describes the operations a caller relies on. Python’s Protocol can express that expected shape for a type checker.

Same idea. One more small step.
Try a tiny example

Pass the helper in.

caller chooses sender
reminder uses sender

Give the reminder its sender when creating it. That is dependency injection: supplying a collaborator instead of building it secretly inside.

Same idea. One more small step.
Notice what matters

Tests can use a recording sender.

real delivery
record in test

The test sender records messages without contacting anyone. It keeps the same promise, so the reminder workflow stays unchanged.

Same idea. One more small step.
Make it reusable

A matching name is not the whole contract.

method shape
behavioral promise

Type hints do not enforce runtime behavior. A replacement must also respect agreed inputs, side effects, and failure behavior.

Want to try a tiny question?
No pressure. Just try it.

Should a unit test need to send a real email to test reminder wording?

You can also keep scrolling
1 / 6
Low-level design

One small step at a time.

This idea builds on these smaller ones. Pick any to revisit it here.

An order and its receipt have different jobs.

order: total
renderer: text

The order knows its items and total. The receipt renderer knows how to turn that information into text.

1 / 4
Let’s picture it

The helper speaks a different language.

send(message)
deliver(text, priority)

Our app calls send(message), but an old client exposes deliver(text, priority). Neither side needs a rewrite.

Swipe when you’re ready
One little change

Translate at the boundary.

app
adapter
legacy client

An adapter accepts the app’s small interface and calls the legacy method with the required arguments.

Same idea. One more small step.
Now it has a name

Adapt the meaning as well as the names.

same promise
translated call

Choose a documented priority and preserve failures. Renaming a method is not enough if success means something different.

Want to try a tiny question?
No pressure. Just try it.

Does an adapter need to replace the legacy implementation?

You can also keep scrolling
1 / 4
Low-level design

One small step at a time.

This idea builds on these smaller ones. Pick any to revisit it here.

A reminder only needs a sender.

reminder
send(message)

It says “send this message.” It does not need to know whether the sender uses email, a screen, or a test notebook.

1 / 5
Let’s picture it

A caller wants one useful operation.

lesson
note
display

Preparing a lesson session may require loading a lesson, finding a saved note, and assembling a display.

Swipe when you’re ready
One little change

Offer an entry point for that workflow.

caller
facade
helpers

A facade exposes open_lesson(id) and delegates to the relevant collaborators. The caller need not sequence them itself.

Same idea. One more small step.
Now it has a name

Do not let convenience swallow every responsibility.

coordinate here
implement details there

The facade coordinates. Loading, storage, and formatting still belong to focused helpers with their own contracts.

Want to try a tiny question?
No pressure. Just try it.

Should the facade become the implementation of every subsystem?

You can also keep scrolling
1 / 4
Low-level design

One small step at a time.

This idea builds on these smaller ones. Pick any to revisit it here.

The helper speaks a different language.

send(message)
deliver(text, priority)

Our app calls send(message), but an old client exposes deliver(text, priority). Neither side needs a rewrite.

1 / 3
A familiar idea

Five names. One aim: contain change.

make report
save report

Suppose report content changes separately from where it is saved. Keep those responsibilities separate. That is single responsibility.

Swipe when you’re ready
One new wrinkle

Add a new option without rewriting callers.

same caller
new storage

A new storage implementation can keep the same save promise. That is the useful part of open–closed design.

Same idea. One more small step.
Try a tiny example

A replacement must keep its promise.

promises save
must support save

A read-only store cannot pretend to be a writable store and fail every save. This is the substitution rule.

Same idea. One more small step.
Notice what matters

Ask only for abilities you use.

save ✓
delete? not needed

A report saver needs save, not delete and search too. A small interface avoids forcing unrelated capabilities.

Same idea. One more small step.
Make it reusable

Depend on the promise, not the concrete helper.

service
save contract
chosen store

Pass a store into the report service. The service describes what it needs; storage details stay outside. This is dependency inversion.

Same idea. One more small step.
Name the idea

SOLID is a review lens, not a class quota.

clear responsibility
honest contract
small dependency

Use these checks when a real change or broken contract exposes a problem. More abstractions are not automatically a better design.

Want to try a tiny question?
No pressure. Just try it.

Does adding an interface for every tiny function guarantee good design?

You can also keep scrolling
1 / 7
Low-level design

One small step at a time.

This idea builds on these smaller ones. Pick any to revisit it here.

A reminder only needs a sender.

reminder
send(message)

It says “send this message.” It does not need to know whether the sender uses email, a screen, or a test notebook.

1 / 5
A familiar idea

Delivery pricing has two possible rules.

flat: 50
per item: 10

One shop charges a flat 50 units. Another charges 10 per item. Checkout still needs one fee.

Swipe when you’re ready
One new wrinkle

Keep the changing rule in one place.

3 items
chosen rule
fee

Pass a fee function to checkout. For three items, one function returns 50 and the other returns 30.

Same idea. One more small step.
Try a tiny example

That replaceable rule is a strategy.

same workflow
different policy

The owner keeps its workflow. The strategy supplies the part that varies. In Python, a function is often enough.

Same idea. One more small step.
Notice what matters

The policy still needs a contract.

input count
output fee
same units

Agree on input meaning, currency units, valid counts, and failures. Swapping functions is safe only if those promises match.

Want to try a tiny question?
No pressure. Just try it.

What changes when a new delivery rule is added?

You can also keep scrolling
1 / 5
Low-level design

One small step at a time.

This idea builds on these smaller ones. Pick any to revisit it here.

Five names. One aim: contain change.

make report
save report

Suppose report content changes separately from where it is saved. Keep those responsibilities separate. That is single responsibility.

1 / 6
Let’s picture it

Keep the operation. Add one behavior.

sender
successful-send count

A sender already works. We want to count successful sends without editing the sender or every caller.

Swipe when you’re ready
One little change

Wrap a sender with another sender.

caller
counting wrapper
sender

The wrapper calls its inner sender, then increments its count. Callers still use send(message).

Same idea. One more small step.
Now it has a name

Order changes the meaning.

attempts before
successes after

Counting after delegation records successes. Counting before it records attempts. Decide which metric you promised.

Want to try a tiny question?
No pressure. Just try it.

If the inner sender fails, should a success counter increase?

You can also keep scrolling
1 / 4
Low-level design

One small step at a time.

This idea builds on these smaller ones. Pick any to revisit it here.

Delivery pricing has two possible rules.

flat: 50
per item: 10

One shop charges a flat 50 units. Another charges 10 per item. Checkout still needs one fee.

1 / 4
A familiar idea

Someone still has to choose the strategy.

configuration
working policy

Configuration says flat or per-item. We need to turn that small setting into a usable pricing object.

Swipe when you’re ready
One new wrinkle

Put construction choices in one place.

flat setting
FlatFee object

A factory reads the setting and builds the appropriate object. Callers receive the common fee interface.

Same idea. One more small step.
Try a tiny example

Factory and strategy answer different questions.

create it
use its rule

Factory: which object do we create? Strategy: which rule does the workflow use? They can work together.

Same idea. One more small step.
Notice what matters

Reject a setting we do not understand.

unknown setting
clear error

Silently selecting a default could charge the wrong fee. Invalid configuration should fail clearly before checkout proceeds.

Want to try a tiny question?
No pressure. Just try it.

Is returning any object enough for a factory?

You can also keep scrolling
1 / 5
Low-level design

One small step at a time.

This idea builds on these smaller ones. Pick any to revisit it here.

Delivery pricing has two possible rules.

flat: 50
per item: 10

One shop charges a flat 50 units. Another charges 10 per item. Checkout still needs one fee.

1 / 4
Let’s picture it

A request has several optional choices.

method
path
timeout

Method, path, headers, and timeout are easier to inspect as named choices than a long positional constructor.

Swipe when you’re ready
One little change

Assemble first. Validate before use.

choose options
validate
build

A builder gathers choices. build checks that required fields exist and returns a separate usable value.

Same idea. One more small step.
Now it has a name

The built value must not keep changing.

builder changes
old request stays

Copy mutable settings into the result. Reusing the builder should not edit an earlier request by accident.

Want to try a tiny question?
No pressure. Just try it.

Should changing the builder later mutate an already built request?

You can also keep scrolling
1 / 4
Low-level design

One small step at a time.

This idea builds on these smaller ones. Pick any to revisit it here.

Someone still has to choose the strategy.

configuration
working policy

Configuration says flat or per-item. We need to turn that small setting into a usable pricing object.

1 / 4
Let’s picture it

Copy a configured starting point.

template
new report

A report template has a title and a list of columns. A new report should inherit those choices but be editable independently.

Swipe when you’re ready
One little change

An outer copy can still share inner data.

two dictionaries
one list

A shallow dictionary copy keeps its nested column list shared. Editing that list then changes both reports.

Same idea. One more small step.
Now it has a name

Choose what becomes independently owned.

copied data
recreated resources

Deep copying works for this data-only example. Connections, locks, and external resources need an explicit reconstruction policy.

Want to try a tiny question?
No pressure. Just try it.

Does an outer dictionary copy always copy nested lists?

You can also keep scrolling
1 / 4
Low-level design

One small step at a time.

This idea builds on these smaller ones. Pick any to revisit it here.

A request has several optional choices.

method
path
timeout

Method, path, headers, and timeout are easier to inspect as named choices than a long positional constructor.

1 / 3
Let’s picture it

Some products need to belong to one family.

text family
symbol family

A text interface wants text buttons and checkboxes. A symbol-based interface wants matching symbolic versions.

Swipe when you’re ready
One little change

Choose the family once.

factory
button
checkbox

The chosen factory supplies both products. Callers do not independently mix concrete products from incompatible families.

Same idea. One more small step.
Now it has a name

Compatibility earns the extra abstraction.

related products
shared family promise

Abstract Factory supplies related products together. If there is no family constraint, separate constructors may be enough.

Want to try a tiny question?
No pressure. Just try it.

What does Abstract Factory add beyond creating one object?

You can also keep scrolling
1 / 4
Low-level design

One small step at a time.

This idea builds on these smaller ones. Pick any to revisit it here.

Someone still has to choose the strategy.

configuration
working policy

Configuration says flat or per-item. We need to turn that small setting into a usable pricing object.

1 / 4
Let’s picture it

Some workflows need a fixed order.

read
validate
transform

Read values, validate them, transform them, then return a result. Different importers may vary only the transform step.

Swipe when you’re ready
One little change

Keep the skeleton in one method.

fixed workflow
replace one step

The base run method fixes the sequence. A subclass supplies the named variation point.

Same idea. One more small step.
Now it has a name

The base promise still applies.

same contract
different transform

A subclass must honor validation and output expectations. Inheritance does not excuse breaking the caller’s contract.

Want to try a tiny question?
No pressure. Just try it.

How does Template Method differ from a supplied Strategy object?

You can also keep scrolling
1 / 4
Low-level design

One small step at a time.

This idea builds on these smaller ones. Pick any to revisit it here.

Five names. One aim: contain change.

make report
save report

Suppose report content changes separately from where it is saved. Keep those responsibilities separate. That is single responsibility.

1 / 6
Let’s picture it

A creator owns a stable workflow.

create
format
return

An exporter creates a formatter, formats records, and returns text. Its subclass chooses which formatter gets created.

Swipe when you’re ready
One little change

Make construction the overridable step.

creator workflow
creation hook

The base workflow calls make_formatter. A subclass supplies the product without rewriting the surrounding sequence.

Same idea. One more small step.
Now it has a name

Similar names hide different boundaries.

simple factory
factory method
abstract factory

Simple factory selects by configuration. Factory Method uses a subclass hook. Abstract Factory provides a compatible set of products.

Want to try a tiny question?
No pressure. Just try it.

What is the variation point in this creator?

You can also keep scrolling
1 / 4
Low-level design

One small step at a time.

This idea builds on these smaller ones. Pick any to revisit it here.

Some workflows need a fixed order.

read
validate
transform

Read values, validate them, transform them, then return a result. Different importers may vary only the transform step.

1 / 3
Let’s picture it

You do not need every item at once.

page 1
page 2
page 3

A page has two notes. Ask for the next page only when those notes have been consumed.

Swipe when you’re ready
One little change

Expose one item at a time.

next item
paused progress

A Python generator keeps its paused position between yields. The caller can use a normal for loop without knowing the paging details.

Same idea. One more small step.
Now it has a name

Define what happens as data changes.

iteration order
snapshot? separate promise

A cursor can move through a stable order, but pagination alone does not promise a snapshot while concurrent writes occur.

Want to try a tiny question?
No pressure. Just try it.

Does returning an iterator automatically make a remote dataset a consistent snapshot?

You can also keep scrolling
1 / 4
Low-level design

One small step at a time.

This idea builds on these smaller ones. Pick any to revisit it here.

Now visit each stop.

Home
Park
Café

You don’t need three different instructions. “Visit the next place” works each time.

1 / 3
A familiar idea

One event can have several listeners.

card saved
counter
notebook

Saving a learning card might update a counter and record a notebook event. The saver need not own both reactions.

Swipe when you’re ready
One new wrinkle

Let interested helpers subscribe.

publisher
listener A
listener B

The publisher keeps listeners. When an event happens, it tells each one through the same callback shape.

Same idea. One more small step.
Try a tiny example

Decide what failure means.

A fails
B still runs

If one listener fails, should the others run? Our small example keeps notifying and returns the failures to the caller.

Same idea. One more small step.
Notice what matters

Observer is a local notification pattern.

same process
explicit failure rules

This version is synchronous and in memory. It does not promise delivery after a process crash or make slow listeners disappear.

Want to try a tiny question?
No pressure. Just try it.

Does a local observer automatically provide durable event delivery?

You can also keep scrolling
1 / 5
Low-level design

One small step at a time.

This idea builds on these smaller ones. Pick any to revisit it here.

A reminder only needs a sender.

reminder
send(message)

It says “send this message.” It does not need to know whether the sender uses email, a screen, or a test notebook.

1 / 5
Let’s picture it

An action can become a small object.

document
append hello

“Append hello” can remember both the target document and the requested text instead of running immediately.

Swipe when you’re ready
One little change

Execute it when the caller chooses.

button
command
document

A queue or button can hold this command. It does not need to understand how the document changes.

Same idea. One more small step.
Now it has a name

Undo needs its own promise.

before
after
restore?

Remember the previous text. Undo is safe only if no later unrelated edit has changed the document since this command ran.

Want to try a tiny question?
No pressure. Just try it.

Can every external action be undone merely because it is a command?

You can also keep scrolling
1 / 4
Low-level design

One small step at a time.

This idea builds on these smaller ones. Pick any to revisit it here.

One event can have several listeners.

card saved
counter
notebook

Saving a learning card might update a counter and record a notebook event. The saver need not own both reactions.

1 / 4
Let’s picture it

Keep a restorable earlier draft.

before
snapshot
after

Save a document snapshot before editing. A history manager can hold it without implementing how restoration works.

Swipe when you’re ready
One little change

Let the state owner restore it.

owner
matching snapshot

The document creates and accepts its own snapshots. A snapshot from a different document should not silently overwrite this one.

Same idea. One more small step.
Now it has a name

Local restoration has a boundary.

local state restored
external effects remain

A restored document does not undo an email already sent. Snapshot history also consumes storage that needs a lifetime policy.

Want to try a tiny question?
No pressure. Just try it.

Does restoring local state reverse every external side effect?

You can also keep scrolling
1 / 4
Low-level design

One small step at a time.

This idea builds on these smaller ones. Pick any to revisit it here.

An action can become a small object.

document
append hello

“Append hello” can remember both the target document and the requested text instead of running immediately.

1 / 3
Let’s picture it

Peers do not need to know every other peer.

room
Ana
Bo

A chat participant sends through a room. The room decides which other participants receive the message.

Swipe when you’re ready
One little change

Put collaboration rules in one place.

membership
routing
coordinator

The room owns membership and routing. Participants only submit or receive messages through a small boundary.

Same idea. One more small step.
Now it has a name

Keep that coordinator focused.

coordination
not every responsibility

A mediator reduces tangled peer references. It should not absorb every unrelated feature just because messages pass through it.

Want to try a tiny question?
No pressure. Just try it.

Must each participant hold references to every other participant?

You can also keep scrolling
1 / 4
Low-level design

One small step at a time.

This idea builds on these smaller ones. Pick any to revisit it here.

A caller wants one useful operation.

lesson
note
display

Preparing a lesson session may require loading a lesson, finding a saved note, and assembling a display.

1 / 3
Let’s picture it

A request passes through several decisions.

validate
authorize
handle

First check whether its shape is valid. Then check permission. Finally call the business operation.

Swipe when you’re ready
One little change

A handler may stop the chain.

reject
stop here

Invalid input should not reach the business operation. A failed permission check should not merely add a warning and continue.

Same idea. One more small step.
Now it has a name

Order is part of the contract.

ordered handlers
explicit continuation

Each handler decides whether to reject or forward. Keep that decision visible instead of scattering checks across callers.

Want to try a tiny question?
No pressure. Just try it.

Should a denied request continue to the final operation?

You can also keep scrolling
1 / 4
Low-level design

One small step at a time.

This idea builds on these smaller ones. Pick any to revisit it here.

A reminder only needs a sender.

reminder
send(message)

It says “send this message.” It does not need to know whether the sender uses email, a screen, or a test notebook.

1 / 5
A familiar idea

The same action can mean different things.

locked
paid → unlocked
push → locked

Push a locked turnstile: stay locked. Insert payment: unlock. Push now: let one person through, then lock again.

Swipe when you’re ready
One new wrinkle

Name the states before adding flags.

state
action
next state

Locked and unlocked describe which actions are currently legal. A transition says how an action changes the state.

Same idea. One more small step.
Try a tiny example

Keep transitions in one checked doorway.

checked action
consistent next state

If callers change unrelated paid and open flags directly, impossible combinations can appear. One operation should own each transition.

Same idea. One more small step.
Notice what matters

Start with a small state machine.

simple enum now
State objects if needed

An enum and clear conditions are enough here. Separate State objects become useful when each state gains substantial behavior.

Want to try a tiny question?
No pressure. Just try it.

Must every two-state object use a separate class for each state?

You can also keep scrolling
1 / 5
Low-level design

One small step at a time.

This idea builds on these smaller ones. Pick any to revisit it here.

Start with one small responsibility.

2 seats
book 1
1 left

A room has two seats left. Booking one should leave one. Booking three should fail without changing anything.

1 / 4
Let’s picture it

A group can contain items or smaller groups.

folder
file
folder

A file has a size. A folder’s size is the sum of its children, which may themselves be folders.

Swipe when you’re ready
One little change

Give both the same small operation.

child sizes
sum
group size

Call size on a leaf or a group. The group delegates to its children and combines their answers.

Same idea. One more small step.
Now it has a name

This is tree recursion expressed as object design.

leaf: direct answer
group: combine

Composite makes leaves and groups usable through one operation. The input must obey the promised tree structure.

Want to try a tiny question?
No pressure. Just try it.

What happens if a folder contains itself in this simple recursive design?

You can also keep scrolling
1 / 4
Low-level design

One small step at a time.

This idea builds on these smaller ones. Pick any to revisit it here.

Count a tiny family of dots.

Parent
Child: 1
Child: 1

One parent has two children. Each child says, “There’s one of me.”

1 / 3
Let’s picture it

Two dimensions can vary independently.

remote features
device implementations

Basic and advanced remotes can control speakers or screens. Subclassing every pair makes combinations multiply.

Swipe when you’re ready
One little change

Connect them through a small interface.

remote
device contract
implementation

A remote holds a device that supports power and volume. New remotes and new devices can then vary independently.

Same idea. One more small step.
Now it has a name

A bridge anticipates variation.

bridge: separate axes
adapter: translate mismatch

An adapter translates an incompatible existing interface. A bridge separates two changing dimensions in the design itself.

Want to try a tiny question?
No pressure. Just try it.

Do five remotes and four devices require twenty combined subclasses?

You can also keep scrolling
1 / 4
Low-level design

One small step at a time.

This idea builds on these smaller ones. Pick any to revisit it here.

An order and its receipt have different jobs.

order: total
renderer: text

The order knows its items and total. The receipt renderer knows how to turn that information into text.

1 / 4
Let’s picture it

Element types stay stable while operations grow.

number
addition
operations

A tree contains numbers and additions. Evaluation and formatting are two different operations over those same elements.

Swipe when you’re ready
One little change

Let the operation visit each element kind.

element
visitor method

An element accepts a visitor and calls the matching method. The visitor owns the operation’s behavior across the tree.

Same idea. One more small step.
Now it has a name

The tradeoff has a direction.

new operation: local
new type: many updates

Adding an operation is convenient. Adding a new element type requires updating visitors that promise to handle every type.

Want to try a tiny question?
No pressure. Just try it.

Which change does Visitor make easier?

You can also keep scrolling
1 / 4
Low-level design

One small step at a time.

This idea builds on these smaller ones. Pick any to revisit it here.

A group can contain items or smaller groups.

folder
file
folder

A file has a size. A folder’s size is the sum of its children, which may themselves be folders.

1 / 3
Let’s picture it

A tiny rule language can stay tiny.

member
AND
active

A rule is a named flag or two rules joined with AND. That is enough to express member AND active.

Swipe when you’re ready
One little change

Make the grammar a tree.

AND
member
active

Flag reads one context value. And evaluates its children and combines their truth values.

Same idea. One more small step.
Now it has a name

Evaluate known operations, not arbitrary code.

allowed grammar
bounded evaluation

A larger language needs parsing, validation, and work limits. User text should not become unrestricted Python execution.

Want to try a tiny question?
No pressure. Just try it.

Should user rule text go directly into Python eval?

You can also keep scrolling
1 / 4
Low-level design

One small step at a time.

This idea builds on these smaller ones. Pick any to revisit it here.

A group can contain items or smaller groups.

folder
file
folder

A file has a size. A folder’s size is the sum of its children, which may themselves be folders.

1 / 3
Let’s picture it

Many labels share one style.

shared style
many labels

Text and position differ, but font family and size repeat. Share the repeated style instead of copying it into every label.

Swipe when you’re ready
One little change

Keep per-use state outside the shared value.

shared: style
separate: position

A style is immutable. Each label keeps its own text and position, so moving one label does not move the others.

Same idea. One more small step.
Now it has a name

Reuse helps only when values repeat.

same key: reuse
new key: new value

A factory reuses a style for each key. If every style is unique, the factory can still grow without bound.

Want to try a tiny question?
No pressure. Just try it.

Where should a label’s position live?

You can also keep scrolling
1 / 4
Low-level design

One small step at a time.

This idea builds on these smaller ones. Pick any to revisit it here.

An order and its receipt have different jobs.

order: total
renderer: text

The order knows its items and total. The receipt renderer knows how to turn that information into text.

1 / 4
Let’s picture it

The domain wants a saved note, not a SQL string.

service
repository
storage

Let a notebook service ask a repository for notes. The repository owns how those notes are stored and queried.

Swipe when you’re ready
One little change

A write may change several related things.

several changes
one commit

A unit of work groups those changes into one commit or rollback boundary. Repository methods alone do not guarantee atomicity.

Same idea. One more small step.
Now it has a name

Tests can supply local storage.

memory test
database adapter

A memory repository checks domain behavior without a live database. It is useful only if its relevant semantics match the real adapter.

Want to try a tiny question?
No pressure. Just try it.

Does having a repository automatically make multiple writes transactional?

You can also keep scrolling
1 / 4
Low-level design

One small step at a time.

This idea builds on these smaller ones. Pick any to revisit it here.

Five names. One aim: contain change.

make report
save report

Suppose report content changes separately from where it is saved. Keep those responsibilities separate. That is single responsibility.

1 / 6
A familiar idea

Keep only two useful answers nearby.

A older
B newer

Put A, then B into a capacity-two cache. A is the least recently used entry.

Swipe when you’re ready
One new wrinkle

Reading A changes the order.

B oldest
A newest
C arrives

A was just used, so B becomes the oldest. Adding C must evict B, not A.

Same idea. One more small step.
Try a tiny example

One structure is not enough.

dictionary: find
linked list: reorder

A dictionary finds an entry quickly. A doubly linked list can detach that entry and move it to the recent end quickly.

Same idea. One more small step.
Notice what matters

Let the cache own both structures.

one key
one node
one owner

Every stored key must have exactly one list node. All get and put operations preserve that shared invariant.

Same idea. One more small step.
Make it reusable

Use sentinels to simplify the ends.

head
real entries
tail

Dummy head and tail nodes make removal and insertion follow the same pointer rules, even for the first or last real entry.

Want to try a tiny question?
No pressure. Just try it.

After put(A), put(B), get(A), put(C), which key leaves a capacity-two cache?

You can also keep scrolling
1 / 6
Low-level design

One small step at a time.

This idea builds on these smaller ones. Pick any to revisit it here.

Let’s remember a little more.

Park
5 minutes

The park is five minutes away. Write that beside its name.

1 / 3
Let’s picture it

Sometimes we should avoid calling the real helper.

cache hit
skip source

A slow lookup returns the same value repeatedly. A cache proxy can answer a hit without reaching the underlying source.

Swipe when you’re ready
One little change

Keep the same lookup interface.

caller
proxy
source

On a miss, ask the real source and remember the result. The caller still uses get(key).

Same idea. One more small step.
Now it has a name

Control comes with a freshness rule.

immutable value ✓
changing value: new rules

Our example caches immutable values only. Mutable data would need expiry, invalidation, or a version-aware key.

Want to try a tiny question?
No pressure. Just try it.

Can this forever-cache safely wrap arbitrary changing data?

You can also keep scrolling
1 / 4
Low-level design

One small step at a time.

This idea builds on these smaller ones. Pick any to revisit it here.

Keep only two useful answers nearby.

A older
B newer

Put A, then B into a capacity-two cache. A is the least recently used entry.

1 / 5
A familiar idea

Ask what this parking lot must do.

park
ticket
leave

For this first design: park a vehicle, issue a ticket, then release the spot. No reservations or payment yet.

Swipe when you’re ready
One new wrinkle

Write two rules we must not break.

spot: one vehicle
vehicle: one ticket

One vehicle per spot. One active ticket per vehicle. These rules matter more than how many classes we draw.

Same idea. One more small step.
Try a tiny example

Give the pieces small responsibilities.

spot
ticket
lot coordinator

A spot knows whether a vehicle fits. A ticket identifies a stay. The lot coordinates claiming and releasing.

Same idea. One more small step.
Notice what matters

Choose a fitting free spot, then commit.

check fit
choose free spot
commit together

Our policy picks the smallest fitting spot. If none exists, return unavailable without creating a ticket.

Same idea. One more small step.
Make it reusable

Closing the ticket reverses the allocation.

ticket identity
matching stay
release

A valid ticket frees exactly its spot. Repeating an old exit must not free a different vehicle’s later stay.

Want to try a tiny question?
No pressure. Just try it.

Two gates see the same free spot. Is checking before assigning enough?

You can also keep scrolling
1 / 6
Low-level design

One small step at a time.

This idea builds on these smaller ones. Pick any to revisit it here.

An order and its receipt have different jobs.

order: total
renderer: text

The order knows its items and total. The receipt renderer knows how to turn that information into text.

1 / 4
A familiar idea

A purchase changes two kinds of state.

credit: 50
price: 30
change: 20

A snack costs 30 units. You insert 50. Successful vending removes one snack and returns 20 change.

Swipe when you’re ready
One new wrinkle

A failed attempt must not lose money.

check stock
check credit
then commit

If the snack is sold out or credit is too low, keep the credit and stock unchanged.

Same idea. One more small step.
Try a tiny example

Make cancellation a real operation.

credit: 50
cancel
refund: 50

Before a successful purchase, cancel returns all inserted credit and resets it to zero.

Same idea. One more small step.
Notice what matters

Put validation before both mutations.

all checks pass
stock −1
credit → 0

Only after every check passes do we decrease stock and consume credit. That protects the purchase boundary in this single-threaded simulator.

Same idea. One more small step.
Make it reusable

Real hardware adds another uncertain step.

paid
dispensing?
confirmed or failed

Dispensing can jam after payment. A real machine needs pending and failed states plus recovery. Our simulation assumes dispensing always succeeds.

Want to try a tiny question?
No pressure. Just try it.

The chosen snack is unavailable. What happens to inserted credit?

You can also keep scrolling
1 / 6
Low-level design

One small step at a time.

This idea builds on these smaller ones. Pick any to revisit it here.

The same action can mean different things.

locked
paid → unlocked
push → locked

Push a locked turnstile: stay locked. Insert payment: unlock. Push now: let one person through, then lock again.

1 / 4
A familiar idea

Design one small elevator simulation first.

floor 0
request 2
request 5

Start at floor 0 with requests for floors 2 and 5. We will serve a fixed batch with a simple nearest-stop policy.

Swipe when you’re ready
One new wrinkle

Picking a stop is a policy.

choose next stop
move to it

Nearest-stop chooses 2 before 5. A directional sweep or FIFO queue could make a different choice without changing movement rules.

Same idea. One more small step.
Try a tiny example

Doors and movement constrain each other.

close
move
arrive → open

Our controller closes doors before moving, changes one floor per tick, and opens only after reaching a requested floor.

Same idea. One more small step.
Notice what matters

Remember pending requests without duplicates.

pending: {2,5}
serve 2
pending: {5}

A set is enough for this fixed batch. Serving a floor removes it once, even if its button was pressed twice.

Same idea. One more small step.
Make it reusable

Keep the assumptions visible.

simulation
not hardware control

Real elevators need safety interlocks and richer dispatch. Nearest-stop can starve far requests under continuous arrivals; our fixed-batch example makes no fairness claim.

Want to try a tiny question?
No pressure. Just try it.

Is nearest-stop guaranteed fair when nearby requests keep arriving forever?

You can also keep scrolling
1 / 6
Low-level design

One small step at a time.

This idea builds on these smaller ones. Pick any to revisit it here.

Delivery pricing has two possible rules.

flat: 50
per item: 10

One shop charges a flat 50 units. Another charges 10 per item. Checkout still needs one fee.

1 / 4
A familiar idea

Split 100 small currency units among three friends.

Ana: 34
Bo: 33
Cy: 33

Equal division leaves a remainder. Use shares 34, 33, 33 under an explicit, deterministic remainder rule.

Swipe when you’re ready
One new wrinkle

Suppose Ana paid the full 100.

Ana: +66
Bo: −33
Cy: −33

Ana’s net credit is 100 − 34 = 66. Bo and Cy each owe 33. The balances still add to zero.

Same idea. One more small step.
Try a tiny example

Keep money in integer units.

integer units
one currency

Store cents or paise, not binary floating-point approximations. Record which currency the ledger represents.

Same idea. One more small step.
Notice what matters

Validate the whole split first.

shares sum correctly?
commit balances + record

Shares must be nonnegative and total the expense exactly. Only then update balances and retain the expense record.

Same idea. One more small step.
Make it reusable

Net balance and original debt are different views.

net balances
audit records

Balances help settle totals. The expense log explains how they arose. Netting does not preserve each original person-to-person obligation.

Want to try a tiny question?
No pressure. Just try it.

What should all net balances sum to after every valid expense?

You can also keep scrolling
1 / 6
Low-level design

One small step at a time.

This idea builds on these smaller ones. Pick any to revisit it here.

Find 8 in a sorted row.

2
4
6
8
10

Start with 2, 4, 6, 8, 10. Look at the middle: 6.

1 / 3
A familiar idea

The happy path is only one sequence.

thread A: free?
thread B: free?

One spot is free. Two threads both check it before either claims it. Both could think they succeeded.

Swipe when you’re ready
One new wrinkle

Protect check and claim together.

lock
check + claim
unlock

A lock makes those steps one critical section inside this process. The second caller then sees the first caller’s committed result.

Same idea. One more small step.
Try a tiny example

Now the same request arrives again.

request r7
retry r7
same result

A timeout caused a retry. Give requests stable IDs and remember completed results, so the same claim is not executed twice.

Same idea. One more small step.
Notice what matters

Detect conflicting reuse too.

same ID
different payload
reject

If r7 later requests a different spot, reject it. An idempotency key identifies one operation, not permission to change its payload.

Same idea. One more small step.
Make it reusable

Test promises across operation sequences.

invariants
retries
failure boundaries

Check duplicate retries, competing claims, and invalid requests. A local lock is not enough for multiple servers or recovery after a crash.

Want to try a tiny question?
No pressure. Just try it.

Does a Python lock stop a second server process from claiming the same database row?

You can also keep scrolling
1 / 6
Low-level design

One small step at a time.

This idea builds on these smaller ones. Pick any to revisit it here.

Ask what this parking lot must do.

park
ticket
leave

For this first design: park a vehicle, issue a ticket, then release the spot. No reservations or payment yet.

1 / 5
Let’s picture it

Some helpers should live for the whole application.

one app lifetime
many callers

A configuration value or connection pool may be shared. That is a lifetime decision, not proof every caller should fetch a global object.

Swipe when you’re ready
One little change

Let the application own that lifetime.

application
service A
service B

Create the shared helper at startup and pass it to services. Tests can create a separate application with a different helper.

Same idea. One more small step.
Now it has a name

Know what “one” means.

one process
not one distributed system

A language-level singleton is usually one per process, not one across every server. Hidden globals also make tests and replacement harder.

Want to try a tiny question?
No pressure. Just try it.

Does one singleton in each of three server processes create one shared distributed instance?

You can also keep scrolling
1 / 4
Low-level design

One small step at a time.

This idea builds on these smaller ones. Pick any to revisit it here.

The happy path is only one sequence.

thread A: free?
thread B: free?

One spot is free. Two threads both check it before either claims it. Both could think they succeeded.

1 / 5
You've reached the end of these ideas.Try them without the labels