█
LastWrite
  • > Curriculum
  • > Pricing
  • > For Educators
  • > About
  • > Contact
Log InGet Started

Questions, concerns, bug reports, or suggestions? We read every message, write to us at [email protected].

More ways to reach us →
LastWrite

Structured computer science lessons for aspiring developers and security professionals.

[email protected]

(201) 785-7951

Mon–Fri, 9 AM–5 PM EST

Learn

  • Curriculum
  • Pricing

Company

  • About
  • For Educators & Schools
  • Contact Us

Legal

  • Terms of Service
  • Privacy Policy
© 2026 LastWrite. All rights reserved.
Curriculum/Software Engineering/Engineering Fundamentals/Software Engineering vs Programming
35 minBeginner

Software Engineering vs Programming

After this lesson, you will be able to: Explain the difference between programming and software engineering, name the core engineering principles, and apply them to a small code sample.

Programming is making the machine do something. Software engineering is making the machine do something that you, your team, and your future self can keep working on for years. This lesson opens the sub-track by naming the principles that separate the two.

This is a free introductory lesson. No purchase required.

Programming vs software engineering

Programming is the act of writing code that runs. Software engineering is everything around it: choosing what to build, structuring it so other engineers can read it, testing it so you know it works, deploying it so users can use it, monitoring it so you notice when it breaks, and evolving it as requirements change. The same person writes both; the engineering mindset is what separates senior from junior.

DRY: Don't Repeat Yourself

If the same logic appears in three places, an update has to happen in three places. DRY says: identify the duplication, extract it into a function, module, or component, and call it from each place. The trap: premature DRY. Two pieces of code that LOOK similar but are conceptually different will diverge later, and pulling them into one shared helper handcuffs both. Wait for the third occurrence before extracting.

SOLID, in plain English

Single Responsibility: one class / module / function does one thing. Open/Closed: open to extension (add new behavior), closed to modification (don't break existing callers). Liskov Substitution: a subclass should work anywhere its parent works. Interface Segregation: don't force callers to depend on methods they don't use. Dependency Inversion: depend on interfaces, not concrete implementations. SOLID is most relevant in OO-heavy languages (Java, C#). In modern TypeScript or Python, the same ideas show up as small modules and pure functions.

Separation of concerns

Different parts of the system should do different things. Your HTTP route handler decodes the request and writes the response. Your business logic decides what to do. Your data layer reads and writes to the database. When concerns mix (SQL in a React component, validation in a database trigger), changes get expensive because everything is tangled.

The principle of least surprise

Code should do what a reader expects from its name and shape. A function called `getUser(id)` should return a user (or null), not start a background thread that emails them. A method called `save()` should persist; not also send a notification, audit log, and Stripe charge. Surprise is bug-attractive. Lean toward boring, predictable behavior.

Refactor this snippet (an exercise)

Identify the principles violated and propose a fix.

tsx
// BAD
function processUser(rawJson: string) {
const data = JSON.parse(rawJson);
if (data.age < 18) throw new Error("too young");
const user = { id: crypto.randomUUID(), name: data.name, age: data.age };
db.query(`INSERT INTO users VALUES ('${user.id}', '${user.name}', ${user.age})`);
sendEmail(user.name, "Welcome!");
console.log("saved " + user.id);
return user;
}
// Issues to spot:
// 1. Mixes parsing, validation, persistence, side effects (notification), logging.
// 2. SQL injection vector via string interpolation.
// 3. No error handling on db.query or sendEmail.
// 4. Function name 'processUser' is vague (least surprise violation).
// 5. Validation rule (age >= 18) is buried; should be at the boundary.
// Better shape:
// parseAndValidate(rawJson) -> User
// saveUser(user) -> Promise<void>
// notifyWelcome(user) -> Promise<void>
// each function does one thing, errors handled at the boundary

Common mistakes only experienced engineers catch

Treating principles as rules to memorize rather than tools to choose between. Apply them when they reduce real pain; ignore them when the cost outweighs the benefit. Over-engineering tiny scripts to be 'production-grade'. A one-off migration script doesn't need SOLID. DRY-ing two things that LOOK similar but aren't conceptually the same. Coupling them will hurt later. Calling a function `process` or `handle` and burying every concern inside it. Names that don't promise specific behavior become surprise factories.

Quick Check

Which of the following violates 'principle of least surprise' most clearly?

Pick the most surprising behavior.

Back to Engineering Fundamentals
Reading Unfamiliar Code and Project Structure→