Intermediate Guide: Key Concepts, Best Practices, and Next Steps
This tutorial is written for people who already know the basics of working in a terminal, using Git, writing code, and deploying something small. The goal is to connect intermediate-level concepts into a practical workflow you can apply to most software projects: how to structure work, keep systems reliable, and choose what to learn next.
What “Intermediate” Really Means
At an intermediate level, you’re usually past “how do I do X?” and into:
- How do I do X consistently across environments?
- How do I prevent X from breaking later?
- How do I make X observable, testable, and maintainable?
- How do I collaborate without stepping on others?
Intermediate work is less about clever code and more about repeatable systems.
1) Key Concepts You Need to Internalize
1.1 Determinism: Make Builds and Runs Repeatable
A deterministic workflow means:
- Same inputs → same outputs
- Minimal “works on my machine”
- Dependencies pinned
- Environments describable as code
Common sources of non-determinism:
- Floating dependency versions (e.g.,
^1.2.0without lockfiles) - Implicit system dependencies (e.g., relying on
pythonbeing present) - Environment variables not documented
- Time- or network-dependent tests
Concrete practices:
- Use lockfiles (
package-lock.json,poetry.lock,pnpm-lock.yaml) - Use containers or reproducible dev environments
- Pin tool versions (Node, Python, Go, Terraform, etc.)
Example (Node.js):
node --version
npm --version
npm ci
npm test
npm ci is designed for reproducible installs using the lockfile.
Example (Python with Poetry):
poetry --version
poetry install --sync
poetry run pytest -q
--sync aligns the environment exactly to the lockfile.
1.2 Boundaries: Define Interfaces Between Components
Intermediate systems break down when everything can call everything else.
Boundaries include:
- Modules and packages
- Services and APIs
- Infrastructure layers (app vs DB vs cache)
- Ownership boundaries (team A owns service X)
Good boundaries have:
- Clear contracts (types, schemas, API specs)
- Versioning strategy
- Controlled coupling
Practical example: validate API contracts
- Use OpenAPI for REST, or protobuf for gRPC.
- Validate requests and responses at boundaries.
Example (generate OpenAPI docs with a typical toolchain):
# Example only; exact command depends on framework/tool
npm run openapi:generate
Even if you don’t generate code, writing a schema forces clarity.
1.3 Observability: Measure What Matters
Observability is not “logging a lot.” It’s being able to answer:
- What is broken?
- Where is it broken?
- Why is it broken?
- Who is affected?
Three pillars:
- Logs (events)
- Metrics (numbers over time)
- Traces (request journeys across services)
At intermediate level, focus on:
- Structured logs (JSON)
- Correlation IDs
- Basic RED/USE metrics
RED method (services):
- Rate (requests/sec)
- Errors (error rate)
- Duration (latency)
USE method (resources):
- Utilization
- Saturation
- Errors
Example: inspect logs with jq
cat app.log | jq -r '.level + " " + .msg'
Example: follow container logs
docker logs -f myapp
1.4 Risk Management: Change Is the Biggest Source of Outages
Intermediate engineers treat changes as risky by default and mitigate with:
- Small pull requests
- Feature flags
- Rollback plans
- Progressive delivery (canary, blue/green)
- Backward-compatible migrations
Key idea: reliability is a product of process, not heroics.
2) Best Practices That Scale
2.1 Git Workflows: Clean History, Safe Collaboration
You should be comfortable with:
- Branching
- Rebasing vs merging
- Resolving conflicts
- Writing useful commit messages
- Tagging releases
Recommended baseline branch flow:
mainis always deployable- feature branches for work
- PR reviews + CI checks required to merge
Useful commands:
git status
git switch -c feature/add-search
git add -A
git commit -m "Add search endpoint with pagination"
git fetch origin
git rebase origin/main
git push -u origin feature/add-search
Interactive rebase to clean up commits:
git rebase -i HEAD~5
Use this to squash “fix typo” commits before merging.
Tagging a release:
git tag -a v1.4.0 -m "Release v1.4.0"
git push origin v1.4.0
2.2 Project Structure: Make the “Happy Path” Obvious
A maintainable repository:
- Has a clear entrypoint
- Has consistent naming
- Separates domain logic from infrastructure glue
- Makes testing easy
A practical, language-agnostic structure:
.
├── cmd/ or src/ # entrypoints
├── internal/ or app/ # core application logic
├── pkg/ # reusable libraries (if applicable)
├── migrations/ # DB migrations
├── scripts/ # automation scripts
├── docs/ # documentation
├── .github/workflows/ # CI pipelines
└── Makefile # common commands
Makefile example (real commands):
.PHONY: test lint run
test:
pytest -q
lint:
ruff check .
run:
python -m app
Then you can do:
make test
make lint
make run
2.3 Dependency Management: Pin, Audit, Update
Rules of thumb:
- Pin direct dependencies.
- Keep lockfiles committed.
- Update on a schedule, not randomly.
- Audit for known vulnerabilities.
Node audit:
npm audit
npm audit fix
Python (pip-audit):
python -m pip install pip-audit
pip-audit
Update strategy:
- Weekly: patch updates
- Monthly: minor updates
- Quarterly: major updates (plan time)
2.4 Testing Strategy: Build a Pyramid That Reflects Reality
A common testing pyramid:
- Many unit tests (fast, isolated)
- Some integration tests (real DB, real filesystem)
- Few end-to-end tests (full stack)
Unit tests should validate business logic without heavy I/O.
Integration tests should validate boundaries:
- DB queries
- HTTP client behavior
- Message queue interactions
E2E tests should validate critical user journeys only.
Example commands (Python):
pytest -q
pytest -q -m integration
pytest -q --maxfail=1
Example commands (JavaScript):
npm test
npm run test:integration
npm run test:e2e
Coverage (Python):
python -m pip install coverage
coverage run -m pytest
coverage report -m
Coverage is a tool, not a goal. Optimize for meaningful assertions.
2.5 Code Reviews: Reduce Risk, Share Context
Good reviews focus on:
- Correctness and edge cases
- Security implications
- Maintainability and readability
- Tests and observability
- Backward compatibility
Reviewer checklist:
- Can I understand the change without extra meetings?
- Are there tests for expected behavior and failure modes?
- Are error messages actionable?
- Are secrets avoided?
- Is logging appropriate and non-sensitive?
Author checklist:
- Small PRs
- Clear description + screenshots/logs if relevant
- “How to test” section
- Notes on rollout/rollback
3) Security Best Practices You Should Apply By Default
3.1 Secrets Management: Never Commit Secrets
Avoid storing secrets in:
- Git history
- Docker images
- CI logs
.envfiles committed to repo
Scan for accidental secrets (example with gitleaks):
gitleaks detect --source . --no-git
Rotate exposed secrets immediately. Deleting from Git is not enough.
Environment variables in local dev:
export DATABASE_URL="postgres://user:pass@localhost:5432/app"
export API_KEY="..."
Prefer a secrets manager in production (AWS Secrets Manager, GCP Secret Manager, Vault).
3.2 Principle of Least Privilege
Give services only what they need:
- DB user with limited permissions
- IAM roles scoped to specific resources
- Network access restricted
Example (Postgres):
-- Create a role with restricted permissions
CREATE ROLE app_user LOGIN PASSWORD '...';
GRANT CONNECT ON DATABASE app TO app_user;
GRANT USAGE ON SCHEMA public TO app_user;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO app_user;
Then ensure migrations run with a separate, more privileged role.
3.3 Input Validation and Output Encoding
Assume all external input is hostile:
- HTTP requests
- Webhooks
- Files
- CLI args
- Environment variables
Validate at boundaries, fail fast, return safe errors.
Example (shell: validate required env var):
: "${DATABASE_URL:?DATABASE_URL is required}"
4) Performance and Reliability: Practical Techniques
4.1 Latency Budgets and Timeouts
Intermediate systems often fail due to missing timeouts.
Add:
- Request timeouts
- DB query timeouts
- Circuit breakers (where appropriate)
- Retries with backoff (only for idempotent operations)
Example (curl with timeout):
curl --max-time 3 https://example.com/health
Example (Postgres statement timeout):
SET statement_timeout = '2s';
4.2 Caching: Use It Deliberately
Caching improves performance but adds complexity:
- Invalidation is hard
- Stale data risks
- Cache stampedes
Start with:
- HTTP caching headers for static assets
- In-memory caching for small, safe items
- Redis for shared caching when needed
Example (inspect cache headers):
curl -I https://example.com/static/app.css
4.3 Backpressure and Rate Limiting
If your service can be overwhelmed, it will be.
Add:
- Rate limits per user/IP/token
- Queue limits
- Concurrency limits
- Graceful degradation
Example (Nginx rate limiting snippet conceptually): You’d configure this in Nginx config, then reload:
sudo nginx -t
sudo systemctl reload nginx
5) Databases and Migrations: Avoid the Common Traps
5.1 Schema Changes Must Be Backward Compatible
A safe migration strategy:
- Add new column (nullable)
- Deploy code that writes both old and new
- Backfill
- Switch reads to new
- Remove old
Example migration workflow (generic):
# Run migrations
./scripts/migrate up
# Verify schema
./scripts/migrate status
Postgres: check locks and long queries
SELECT pid, state, query, now() - query_start AS duration
FROM pg_stat_activity
WHERE state <> 'idle'
ORDER BY duration DESC;
5.2 Indexing: Measure Before and After
Indexes speed reads but slow writes and use disk.
Explain a query (Postgres):
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM orders WHERE user_id = 123 ORDER BY created_at DESC LIMIT 20;
Then add an index if justified:
CREATE INDEX CONCURRENTLY idx_orders_user_created_at
ON orders (user_id, created_at DESC);
CONCURRENTLY reduces lock impact but has tradeoffs.
6) Containers: Build, Run, Debug Like a Pro
6.1 Understand Images vs Containers
- Image: immutable template
- Container: running instance of an image
Core commands:
docker build -t myapp:dev .
docker run --rm -p 8080:8080 myapp:dev
docker ps
docker exec -it <container_id> sh
6.2 Use Multi-Stage Builds (Smaller, Safer Images)
A typical pattern:
- Builder stage with compilers/dev deps
- Runtime stage with minimal artifacts
Example commands to inspect size:
docker images | head
docker history myapp:dev
6.3 Debugging Containers
View logs:
docker logs -f <container_id>
Check environment:
docker exec -it <container_id> env | sort
Inspect ports:
docker port <container_id>
7) CI/CD: Automate the Boring and Risky Parts
7.1 What CI Must Do at Minimum
- Install dependencies deterministically
- Run lint + tests
- Produce artifacts (optional)
- Block merges on failure
Typical CI commands:
npm ci
npm run lint
npm test
or:
poetry install --sync
poetry run ruff check .
poetry run pytest -q
7.2 CD and Release Hygiene
For deployments:
- Keep them frequent and small
- Automate rollback
- Track versions (Git SHA, semantic version)
- Emit deployment events (for auditability)
Example: embed Git SHA into build:
GIT_SHA="$(git rev-parse --short HEAD)"
echo "$GIT_SHA" > build/version.txt
8) Documentation That Actually Helps
Intermediate documentation is:
- Task-oriented
- Accurate
- Minimal but complete
- Maintained with the code
Docs you should have:
README.mdwith quickstartdocs/architecture.mdwith diagrams/decisionsdocs/runbook.mdfor operationsdocs/api.mdor OpenAPI specCHANGELOG.mdor release notes
Example README quickstart section (commands):
git clone https://github.com/yourorg/yourrepo.git
cd yourrepo
cp .env.example .env
make run
9) Debugging and Incident Response: A Practical Workflow
9.1 Debugging Checklist (Production Mindset)
When something breaks:
- Define the symptom (what is failing, for whom)
- Check recent changes (deploys, config, migrations)
- Check dashboards (errors, latency, saturation)
- Inspect logs with correlation IDs
- Reproduce in staging or locally if possible
- Mitigate first (rollback, disable feature flag)
- Root cause analysis after stability
Find recent Git changes:
git log --oneline --decorate -n 20
Compare two versions:
git diff v1.3.0..v1.4.0
9.2 Runbooks and “Known Good” Actions
A runbook should include:
- How to identify the issue
- How to mitigate safely
- How to verify recovery
- Escalation steps
Example verification commands:
curl -fsS http://localhost:8080/health
curl -fsS http://localhost:8080/ready
10) Next Steps: How to Level Up Intentionally
Intermediate growth comes from choosing a direction and building depth. Pick one track for 4–8 weeks and ship something real.
Track A: Backend Reliability
Focus topics:
- Timeouts, retries, idempotency
- Database migrations at scale
- Observability (metrics + tracing)
- Load testing
Load testing example (wrk):
wrk -t4 -c50 -d30s http://localhost:8080/api/items
Track B: Security Engineering
Focus topics:
- Threat modeling
- AuthN/AuthZ (OAuth2/OIDC)
- Secrets management
- Secure SDLC
Dependency scanning examples:
npm audit
pip-audit
Track C: Platform/DevOps
Focus topics:
- Containers, Kubernetes basics
- Terraform/IaC
- CI/CD pipelines
- SRE fundamentals
Terraform basics (real commands):
terraform fmt -recursive
terraform validate
terraform plan
terraform apply
Track D: Frontend Systems
Focus topics:
- Performance budgets (LCP, CLS)
- Bundling and caching
- Component architecture
- Testing (unit + E2E)
Example (measure bundle size with a typical script):
npm run build
npm run analyze
A Practical “Intermediate” Checklist (Use This on Your Next Project)
Build & Run
- One-command local run (
make runor equivalent) - Deterministic installs (
npm ci,poetry install --sync) - Tool versions pinned (Node/Python/etc.)
Quality
- Linting and formatting in CI
- Unit + integration tests
- Meaningful coverage of critical logic
Security
- No secrets in repo; scanning enabled
- Least privilege for DB and cloud roles
- Input validation at boundaries
Reliability
- Timeouts everywhere
- Structured logs with correlation IDs
- Health/readiness endpoints
- Safe migrations and rollback plan
Delivery
- Small PRs with clear descriptions
- CI gates for merges
- Tagged releases or versioned builds
Closing Notes
The intermediate leap is mostly about turning knowledge into habits:
- Automate repeatable work.
- Make changes safe.
- Make systems observable.
- Document what matters.
- Choose a specialty track and go deep.
If you share what stack you’re using (Node/Python/Go/Java, Docker/K8s, Postgres/MySQL, cloud provider), I can tailor the commands, repo layout, and a concrete 2–4 week learning plan to match your environment.