Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

The Programming Task Library is a collection of practical exercises designed to improve problem-solving, analytical thinking, and software design skills.

The library is intended both for developers who are beginning their software development journey and for experienced developers who want structured technical practice.

Although the tasks were originally designed with Go and Rust in mind, the underlying problems are language-independent and can be implemented in other programming languages as well.

Purpose

The main goal of this library is not only to produce a correct result.

Each task should encourage you to think about:

  • algorithmic efficiency
  • execution performance
  • resource consumption
  • simplicity of the solution
  • flexibility
  • abstraction
  • code organization
  • reusability

A working solution is only one part of the exercise.

You should also consider whether the same problem can be solved with fewer operations, less memory, clearer abstractions, or a design that can be reused in other scenarios.

Think Before You Code

Before starting an implementation, analyze the problem first.

For more complex tasks, especially modeling tasks, it is recommended to draw the possible execution flows, relationships, states, or outcomes before writing code.

Do not immediately implement the first solution that comes to mind.

Instead:

  1. Understand the problem.
  2. Identify the inputs and expected outputs.
  3. Consider multiple possible solutions.
  4. Compare their complexity and resource requirements.
  5. Choose an approach.
  6. Implement it.
  7. Test and evaluate the result.

The objective is not to classify a task as easy or difficult.

The objective is to find an efficient and well-structured solution for the given scenario.

Library Structure

The library is divided into two major sections:

Algorithms

The Algorithms section contains seven groups.

Each group contains five tasks, for a total of 35 algorithmic exercises.

The tasks cover different types of problems involving data transformation, searching, filtering, grouping, sorting, combinations, validation, and other forms of algorithmic processing.

Modeling

The Modeling section contains six larger tasks.

These exercises focus on designing data structures, relationships, behavior, validation rules, queries, and complete domain models.

Compared with the algorithmic exercises, modeling tasks are intentionally broader and may require multiple types, components, and operations working together.

Programming Languages

The original task library was designed primarily for:

  • Go
  • Rust

However, unless a task explicitly depends on a language-specific feature, any programming language may be used.

When solving a task in another language, preserve the intended behavior, constraints, inputs, outputs, and validation requirements.

Approach

Treat every task as a small engineering problem rather than only as a coding exercise.

A good solution should aim to be:

  • correct
  • understandable
  • efficient
  • testable
  • maintainable
  • reusable where appropriate

Different implementations may solve the same task correctly.

The important part is being able to explain why a particular approach was chosen and what trade-offs it introduces.

About Author

Nikola Djurdjevic

Nikola Djurdjevic

Backend & Systems Software Architect

Go · Rust · Distributed Scalable Systems · Infrastructure

Nikola Djurdjevic is a backend and systems software architect focused on building infrastructure, distributed services, developer tooling, automation systems, benchmarking software, and internal engineering platforms. His primary development languages are Go & Rust, with previous development experience using C++ & Java.

His work is primarily centered around backend and infrastructure software rather than application frontend development.

The systems developed as part of this work include components for:

  • service management
  • remote execution
  • infrastructure agents
  • artifact distribution
  • build orchestration
  • release automation
  • reverse proxying
  • load balancing
  • documentation distribution
  • system benchmarking
  • hardware analysis
  • deployment
  • monitoring
  • authentication and authorization
  • distributed resource management

The common objective behind these systems is to build infrastructure that remains understandable, deterministic, independently deployable, and operational without unnecessary external dependencies.

Engineering Focus

The primary engineering focus is on systems where several independent services cooperate through clearly defined protocols and responsibilities.

Typical areas include:

  • Backend Services
  • Distributed Systems
  • Infrastructure Tooling
  • Developer Tooling
  • Remote Agents
  • Artifact Distribution
  • Build and Release Automation
  • Service Management
  • Reverse Proxies
  • Load Balancing
  • Documentation Infrastructure
  • System Benchmarking
  • Hardware Detection
  • Monitoring
  • Deployment
  • Automation

The work frequently involves networking and communication technologies such as:

HTTP/1.1 HTTP/2 REST TCP UDP WebSocket TLS gRPC

The architecture of these systems generally favors small specialized services over one large application.

A typical Scalionix system may therefore consist of several independent components:

        Client => Gateway => Service / Engine
                                     |
                                     +-- Agent
                                     +-- Registry
                                     +-- Storage
                                     +-- Worker
                                     +-- External Services

Each component owns a clearly defined part of the system.

Engineering Philosophy

A recurring design principle throughout the Scalionix ecosystem is to build the required infrastructure directly when doing so provides better control, simpler deployment, or clearer operational behavior. This has resulted in custom systems for:

  • artifact distribution
  • service orchestration
  • remote execution
  • documentation serving
  • benchmarking
  • reverse proxying
  • deployment
  • build orchestration

rather than automatically introducing large infrastructure platforms for every problem.

This does not mean avoiding existing technologies.

The objective is to understand which infrastructure is actually required and to avoid introducing dependencies whose complexity is greater than the problem they solve.

Another important principle is separation of responsibilities. For example:

        [Build System] => [Artifact System] => [Deployment / Agent System]

are related systems, but they do not need to be the same service. Similarly:

        Documentation Source
                ↓
        Documentation Build
                ↓
        Artifact Distribution
                ↓
        Documentation Server

forms a pipeline composed of independent responsibilities.

This makes individual components easier to:

  • develop
  • test
  • replace
  • deploy
  • scale
  • debug
  • version

Scalionix

Scalionix is the current ecosystem under which a collection of backend services, infrastructure tools, developer utilities, and experimental systems are being developed. The systems are designed around a service-oriented architecture in which individual components can be developed and deployed independently. Major areas currently represented in the ecosystem include:

  • Agent Infrastructure
  • Artifact Distribution
  • Build Orchestration
  • Release Management
  • Reverse Proxy Infrastructure
  • Documentation Infrastructure
  • System Benchmarking
  • Service Management
  • Automation
  • Monitoring

Agent Infrastructure

A major part of the infrastructure work has involved agent-based service management and remote execution.

The general architecture uses agents installed on target machines.

These agents provide a controlled interface between central infrastructure services and the operating system running the managed services.

Conceptually:

        Management Service
               ↓
             Agent
               |
               +-- Service Operations
               +-- Script Execution
               +-- Deployment Operations
               +-- Monitoring
               +-- System Information
               +-- Resource Information

The agent model allows infrastructure operations to be executed on remote machines without requiring the central management service to directly control the operating system.

Operations developed around this model include:

  • start service
  • stop service
  • restart service
  • remove service
  • retrieve service status
  • execute scripts
  • execute deployment operations
  • retrieve system information
  • monitor resource usage

The design also supports environments containing multiple:

  • servers
  • groups
  • regions
  • services

This agent-based approach became one of the foundations for later infrastructure components.

Artifact Distribution System

The Scalionix Artifact system was designed to provide controlled distribution of service binaries, tools, scripts, documentation packages, and other release artifacts.

The system is divided into specialized components.

Conceptually:

        Artifact Engine
              |
              +-------------------+
              ↓                   ↓
        Artifact Server      Artifact Gateway
              ↓
        Artifact Storage

A registry component is also part of the broader architecture.

Artifact Engine

The Artifact Engine acts as an orchestration layer.

It is responsible for coordinating artifact-related operations across one or more targets.

Targets can be organized using concepts such as:

  • Sectors
  • Groups
  • Targets
  • Items

A sector represents a larger service or infrastructure grouping. Groups represent subsets within a sector. Targets represent destination systems such as:

  • artifact servers
  • artifact gateways
  • managed infrastructure

Items represent deployable or executable resources such as:

  • artifacts
  • scripts
  • service packages

The Engine supports different execution patterns. Examples include:

  • single-target execution
  • multi-target execution
  • broadcast execution

Execution can therefore be directed toward one specific target or distributed across several infrastructure nodes.

The Engine also maintains execution history and supports snapshot-oriented operations for comparing or reproducing infrastructure state.

Artifact Server

The Artifact Server provides artifact storage and retrieval. Its responsibilities include:

  • artifact upload
  • artifact storage
  • artifact download
  • authorization
  • release organization

Artifacts may be organized by:

  • service
  • version
  • operating system
  • architecture
  • edition

This allows the same service release to contain several independently generated binaries.

For example:

        service
        ├── linux
        │   ├── amd64
        │   └── arm64
        ├── darwin
        │   ├── amd64
        │   └── arm64
        └── windows
            ├── amd64
            └── arm64

Artifact Gateway

The Artifact Gateway provides a distribution layer in front of artifact servers.

Instead of storing artifacts itself, it can redirect requests toward available storage nodes.

The gateway supports weighted distribution behavior, allowing several artifact servers to participate in release delivery.

Conceptually:

        Client
           ↓
        Artifact Gateway
           |
           +------> Artifact Server A
           |
           +------> Artifact Server B
           |
           +------> Artifact Server C

This separates artifact storage from artifact routing.

Artifact Registry

The broader Artifact architecture also includes the concept of a registry responsible for centralized metadata and infrastructure relationships.

The registry architecture includes concepts such as:

  • users
  • roles
  • engines
  • authentication keys
  • snapshots
  • online engine state

This provides a foundation for managing larger installations containing multiple Artifact Engines and infrastructure nodes.

Build Orchestration

Another part of the Scalionix infrastructure is a native build orchestration system.

The build system was created to automate compilation across a matrix of:

  • services
  • operating systems
  • architectures
  • editions

A single service may need artifacts for:

  • Linux [AMD64/ARM64]
  • Darwin [ARM64]
  • Windows [AMD64/ARM64]

and may additionally provide editions such as [Community/Enterprise].

When applied across many services, a release can produce hundreds of individual artifacts. The build orchestrator automates this process. Its responsibilities include:

  • repository preparation
  • build-directory creation
  • build cleanup
  • dependency synchronization
  • build-script generation
  • parallel build execution
  • version stamping
  • artifact packaging
  • release metadata generation
  • tagging
  • push operations

Builds may be selected by:

  • Service
  • Group
  • Sector

allowing both individual service builds and larger ecosystem releases.

Build Metadata

Generated services contain build information such as:

  • Version
  • Commit
  • Build Time

This allows a running binary to identify exactly which source revision produced it.

A typical release artifact follows a deterministic naming convention containing information such as:

  • service
  • edition
  • operating system
  • architecture
  • version

This provides traceability between:

        Source => Commit => Build => Artifact => Release

Go and Rust Build Infrastructure

The build infrastructure supports both Go and Rust services.

Go services can be built using options for:

  • trimpath
  • symbol stripping
  • empty build IDs
  • build obfuscation
  • version stamping

Rust services use a corresponding cross-platform build pipeline.

Rust targets include environments such as:

  • Linux [AMD64/ARM64]
  • Darwin [ARM64]
  • Windows [AMD64/ARM64]

Cross-compilation environments were created where practical, while native builds are used where platform requirements make them preferable.

The objective is for Go and Rust services to participate in the same higher-level release process even though their underlying build systems are different.

Release Pipeline

The combination of the build and artifact systems creates a larger release pipeline.

Conceptually:

        Source Repository
               ↓
        Build Orchestrator
               ↓
        Build Matrix
               ↓
        Package Artifacts
               ↓
        Artifact Server
               ↓
        Artifact Gateway
               ↓
        Target Infrastructure

This architecture keeps compilation, storage, distribution, and deployment as separate responsibilities.

Scalionix Gateway

Scalionix also includes a native reverse-proxy and gateway service.

The gateway was developed to provide:

  • TLS termination
  • HTTP/2
  • HTTP redirects
  • reverse proxying
  • backend routing
  • load balancing
  • health checking

It can route several public endpoints toward independent internal services. Conceptually:

        Internet
           ↓
        Scalionix Gateway
           |
           +------> Website
           |
           +------> Platform
           |
           +------> API
           |
           +------> Documentation

The gateway allows internal services to remain on independent ports while presenting a unified public interface.

Backend pools can contain multiple service instances. Requests can then be distributed between healthy backend nodes. This provides a foundation for:

  • redundancy
  • service replacement
  • horizontal expansion
  • maintenance
  • backend failover

without requiring clients to know the internal infrastructure topology.

Scalionix Documentation Infrastructure

Scalionix documentation is built using mdBook.

Documentation is maintained separately from application source where independent versioning and release history are useful.

This makes it possible for each service or tool to maintain its own:

  • documentation repository
  • documentation versions
  • release tags
  • build history
  • language separation

rather than placing every document in one monolithic repository.

The generated documentation is static HTML. A typical pipeline is:

        Documentation Repository
                ↓
             mdBook
                ↓
              book/
                ↓
        Documentation Artifact
                ↓
        Artifact Distribution
                ↓
        Scalionix Docs Server

Scalionix Docs Server

The Scalionix Docs Server is a Go service developed for serving documentation generated by systems such as mdBook.

The original implementation supported documentation embedded directly into the service binary.

This is useful for:

  • local tools
  • portable documentation
  • single-book applications
  • self-contained binaries

However, embedding every documentation version does not scale well for a centralized documentation service.

For example:

        100 projects × 100 documentation versions × 3 MB per book ≈ 30 GB

Embedding that data would also require rebuilding the Docs Server whenever a new documentation release was added.

The architecture was therefore expanded to support a filesystem-backed documentation library.

Conceptually:

        Scalionix Docs Server
                ↓
        Documentation Library
                |
                +-- service-a
                |     +-- 1.0
                |     +-- 1.1
                |     +-- 2.0
                |
                +-- service-b
                |     +-- 1.0
                |     +-- 2.0
                |
                +-- tool-a
                      +-- 3.0

The server can resolve documentation dynamically from the active library.

A central installation may therefore use a layout such as /srv/scalionix/docs/, with individual books organized by:

  • project
  • version
  • book

This allows the Docs Server binary to remain unchanged when documentation is added. Documentation deployment becomes:

        Git Repository
              ↓
        mdbook build
              ↓
            book/
              ↓
           Package
              ↓
        Artifact Server
              ↓
        Documentation Deployment
              ↓
        /srv/scalionix/docs/<project>/<version>/

Version directories can remain immutable while aliases such as latest identify the currently recommended version.

This separates [Documentation Server Release] from [Documentation Content Release], which allows both to evolve independently.

Documentation Portal

The Docs Server also provides a central interface for discovering available documentation.

Instead of requiring users to manually know every documentation URL, the server maintains information about:

  • services
  • tools
  • versions
  • default versions
  • documentation URLs

This allows one Docs Server instance to become an entry point for documentation across the Scalionix ecosystem.

Scalionix System Benchmark

Another major project is the Scalionix System Benchmark.

It is a cross-platform benchmark written in Rust for measuring system performance using workloads designed to resemble real software operations.

The benchmark targets [Linux/Darwin] and collects information about the system being tested.

The benchmark architecture contains several workload categories. These include:

  • JSON
  • Hashing
  • Encryption
  • Compression
  • Collections
  • Concurrency
  • Compilation
  • Storage

The benchmark is designed around repeatable scenarios rather than a single synthetic loop.

JSON Benchmarking

JSON workloads measure serialization, deserialization, memory processing, and filesystem interaction involving structured data.

Operations include scenarios such as:

  • serialization
  • deserialization
  • memory processing
  • file processing
  • round trips
  • raw file reads
  • pre-serialized file writes

Scenarios are executed across different:

  • dataset sizes
  • object counts
  • worker counts

This makes it possible to observe both single-thread and multi-thread behavior across workloads of different sizes.

Hashing Benchmarking

Hashing workloads measure the performance of commonly used cryptographic hash functions.

Algorithms include:

  • BLAKE3
  • SHA-256
  • SHA-512

Execution styles include:

  • chunked
  • one-shot

These scenarios make it possible to compare hashing performance across different algorithms, data sizes, and execution strategies.

Encryption Benchmarking

Encryption workloads measure authenticated encryption performance using commonly used algorithms.

Algorithms include:

  • AES-128-GCM
  • AES-256-GCM
  • ChaCha20-Poly1305

Operations include:

  • encrypt
  • decrypt
  • round trip

The workloads are designed to measure both individual cryptographic operations and complete encrypt-decrypt processing paths.

Compression Benchmarking

Compression workloads measure the performance of data compression and decompression operations.

The scenarios are designed to observe the computational cost of processing data through compression workloads rather than measuring only raw memory throughput.

They can be used to evaluate behavior across different:

  • dataset sizes
  • compression operations
  • decompression operations
  • worker counts

This makes it possible to compare both single-thread and multi-thread compression performance under repeatable workloads.

Collections Benchmarking

Collections workloads measure operations performed on in-memory data structures.

The objective is to observe behavior that appears frequently in real applications, where data is inserted, accessed, searched, modified, iterated, or removed from collections.

Scenarios can exercise operations such as:

  • insertion
  • lookup
  • iteration
  • modification
  • removal

The workloads can be executed with different collection sizes and execution configurations, allowing the benchmark to observe how performance changes as the amount of managed data increases.

Concurrency Benchmarking

Concurrency workloads measure the behavior of the system when work is distributed across multiple execution contexts.

The objective is not simply to maximize CPU utilization, but to observe the cost and scalability of concurrent processing.

Scenarios can evaluate behavior across different:

  • worker counts
  • task counts
  • synchronization patterns
  • shared workloads

This makes it possible to compare single-worker execution with increasingly parallel workloads and observe how effectively the system scales as concurrency increases.

Compilation Benchmarking

Compilation benchmarking measures real build workloads.

The objective is to measure production-style compilation rather than an artificial CPU loop.

Compilation scenarios can record:

  • toolchain
  • worker count
  • iterations
  • best duration
  • worst duration
  • duration spread
  • throughput
  • artifact verification

Generated artifacts are verified so that a failed or incomplete compilation cannot be treated as a successful benchmark result.

This allows compilation performance to represent an actual developer workload, including both execution time and successful artifact production.

Storage Benchmarking

Storage workloads measure filesystem behavior rather than only raw sequential throughput.

Operations include scenarios such as:

  • file operations
  • directory traversal
  • metadata operations
  • rename operations

The benchmark is designed to observe end-to-end storage behavior through the operating system and filesystem.

This provides a workload-oriented view of storage performance that is closer to application behavior than a simple sequential read or write benchmark.

Hardware Detection

The benchmark collects a hardware snapshot describing the machine on which the benchmark was executed.

Information may include:

  • CPU
  • physical cores
  • logical processors
  • memory
  • storage devices
  • operating system
  • architecture

Platform-specific detection is used where necessary.

For example, hardware information may be obtained from operating-system facilities such as:

  • SMBIOS
  • udev
  • APIs

The objective is to identify the tested hardware accurately enough for comparison and result validation.

Benchmark Concurrency

Benchmark scenarios are executed using multiple worker configurations.

This allows comparison of behavior at levels such as:

        [ 1 / 2 / 4 / 6 / 8 / 12 / 16 / 20 / 24 / 32 ] number of workers

The objective is not only to determine maximum throughput.

The benchmark can also show how efficiently a machine scales as additional concurrency is introduced.

Benchmark Scoring

Raw benchmark results are transformed into a structured scoring model.

Conceptually:

        Scenario Results => Workload Score => Category Score => Global Score

This produces several levels of comparison.

A machine can therefore be compared using:

  • individual scenario performance
  • workload performance
  • category performance
  • global performance

The scoring system uses versioned references so that score calculation can evolve while remaining identifiable.

Benchmark Reports

The benchmark produces structured reports containing:

  • benchmark results
  • hardware information
  • scoring information
  • verification data
  • report identifiers

The reporting model separates detailed local benchmark data from the compact information required for global comparison.

This allows large raw benchmark reports to remain local while only the required Global Score Report is submitted to a ranking service.

Global Benchmark Ranking

The broader benchmark architecture includes a public ranking system for comparing hardware configurations.

The ranking system is designed to support comparisons across:

  • Global Score
  • Categories
  • Workloads
  • Hardware Classes

Rather than storing only one final number, the architecture can preserve enough information to understand where a system performs well or poorly.

The ranking architecture also considers:

  • hardware fingerprints
  • duplicate detection
  • scoring versions
  • historical results
  • machine categories

This makes the benchmark useful for more than a single local performance test.

Native Infrastructure

A recurring characteristic of the Scalionix ecosystem is the preference for native service execution where appropriate.

Many services are designed to run directly as binaries rather than requiring a container runtime as part of their normal deployment model.

This keeps the operational path simple:

        Binary => Operating System

and allows the surrounding infrastructure to manage:

  • service lifecycle
  • configuration
  • networking
  • deployment
  • monitoring
  • artifact delivery

directly.

Containers and virtualized environments may still be useful for isolated build or compatibility scenarios, but they are not assumed to be a mandatory runtime dependency for the ecosystem.

Cross-Platform Development

The ecosystem targets several operating systems and architectures.

Common targets include [Linux/Darwin/Windows] and [AMD64/ARM64].

This influences the design of:

  • build systems
  • artifact naming
  • hardware detection
  • benchmarking
  • filesystem handling
  • service packaging

Cross-platform behavior is treated as an architectural concern rather than an afterthought.

Programming Task Library

The Programming Task Library was originally created in January 2024 as a practical training resource for software developers.

The library was designed to encourage developers to think beyond:

the code compiles

or:

the expected value was returned

and instead consider:

  • correctness
  • algorithm design
  • data modeling
  • performance
  • resource consumption
  • validation
  • abstraction
  • flexibility
  • edge cases
  • maintainability

The original library focused primarily on Go and Rust. The current version reorganizes and expands that material into structured documentation.

It contains two major sections: Algorithms & Modeling.

The Algorithm section focuses on problem-solving and data processing. The Modeling section focuses on transforming real-world requirements into software systems containing entities, relationships, state, resources, scheduling, transactions, failures, recovery, and domain invariants.

Technology

The primary technologies and engineering areas represented across the projects include:

  • Go / Rust
  • HTTP/1.1 - HTTP/2
  • REST
  • TCP / UDP
  • WebSocket
  • TLS
  • Linux / Darwin / Windows
  • AMD64 / ARM64
  • Git / Gitea / mdBook

The exact technology is selected according to the requirements of each system.

Go is used extensively for network services, infrastructure components, and backend tooling.

Rust is used for systems where low-level control, performance, concurrency, hardware interaction, or systems-programming practice is particularly valuable.

Current Direction

The current Scalionix work is gradually forming a connected engineering platform.

Individual systems remain independent, but they can participate in larger workflows.

For example:

        Source
           ↓
        Build Orchestrator
           ↓
        Artifact System
           ↓
        Deployment Infrastructure
           ↓
        Agent
           ↓
        Running Service

Documentation follows a parallel lifecycle:

        Documentation Source
           ↓
        mdBook Build
           ↓
        Documentation Artifact
           ↓
        Artifact Distribution
           ↓
        Docs Server
           ↓
        Scalionix Gateway
           ↓
         User

Benchmarking forms another independent subsystem:

        Machine
           ↓
        System Benchmark
           |
           +-- Hardware Snapshot
           +-- Workload Results
           +-- Verification
           +-- Scoring
           ↓
        Global Score Report
           ↓
        Ranking Infrastructure

The common theme is not one specific technology or product.

It is the development of infrastructure where each layer has an explicit responsibility and can be understood independently while still participating in a larger system.

Original Publication

The original version of the Programming Task Library was published as:

Training Task Library — Scalionix Development Team

Created by: Nikola Djurdjevic [LinkedIn]
Published: January 2021
Original technology focus: Golang & Rust & C++

The current documentation preserves the original exercises while reorganizing, correcting, documenting, and extending the collection into a larger programming and software-modeling resource.

Terms of Use

Effective date: [28-01-21]
Last updated: [17-09-26]

These Terms of Use govern access to and use of the Scalionix Task Library, including its documentation, exercises, tasks, code examples, illustrations, diagrams, supporting materials, and any related content made available through the Scalionix platform.

By accessing or using the Scalionix Task Library, you agree to these Terms of Use.

If you do not agree with these terms, you should not access or use the Task Library.


1. Purpose of the Task Library

The Scalionix Task Library is an educational resource intended to help developers improve their technical knowledge, problem-solving ability, software engineering judgment, and practical development skills.

The material may include:

  • technical explanations
  • programming exercises
  • engineering problems
  • architectural scenarios
  • debugging exercises
  • code examples
  • configuration examples
  • command-line examples
  • design discussions
  • performance-related exercises
  • security-related exercises
  • questions intended to develop analytical and critical thinking

The Task Library is designed for education and professional development.

It is not intended to provide legal, financial, security, compliance, or other regulated professional advice.


2. License to Use the Task Library

Unless otherwise stated, Scalionix grants you a limited, non-exclusive, non-transferable, non-sublicensable, and revocable right to access and use the Task Library for:

  • personal education
  • professional education
  • individual study
  • internal learning
  • research
  • software development practice
  • technical experimentation

Access to the Task Library does not transfer ownership of the Task Library or any intellectual property rights associated with it.

All rights not expressly granted are reserved.


3. What You May Do

You may:

  • read and study the Task Library;
  • complete the exercises and tasks;
  • write your own solutions to the tasks;
  • take reasonable personal notes;
  • quote limited portions of the material where permitted by applicable law and with appropriate attribution;
  • use the knowledge, concepts, techniques, and skills you learn in your own software projects;
  • discuss the concepts covered by the Task Library in your own words;
  • use individual code examples as permitted under the section below.

You may apply knowledge learned from the Task Library in both personal and commercial work.

These Terms are intended to protect the content of the Task Library, not to restrict your ability to use the knowledge and skills you acquire from it.


4. Code Examples

Unless a code example is explicitly marked with a different license or restriction, you may copy, modify, and adapt individual code examples from the Task Library for:

  • learning;
  • experimentation;
  • personal software projects;
  • internal company projects;
  • commercial software projects.

This permission applies to individual technical examples, not to the Task Library as a collection.

It does not permit you to reproduce or redistribute a substantial collection of Scalionix examples, exercises, explanations, or tasks.

For example, using a code snippet demonstrating a concurrency pattern in your application is permitted.

Copying hundreds of Scalionix code examples into another tutorial, book, training platform, repository, dataset, or course is not.

Third-party code included in the Task Library may be subject to separate license terms. Where applicable, those terms take precedence for that specific material.


5. Your Solutions

Code, notes, designs, or other material that you independently create while solving Scalionix tasks remain yours, subject to any third-party rights that may apply.

Scalionix does not claim ownership of an independently developed solution merely because it was created in response to a Task Library exercise.

However, the underlying Scalionix task statement, description, supporting explanation, diagrams, reference material, and other protected Task Library content remain subject to Scalionix intellectual property rights.

You may therefore publish your own independently written code where appropriate, but you may not reproduce substantial portions of the original Scalionix task or accompanying material without permission.


6. Prohibited Uses

Unless you have prior written permission from the copyright holder, you may not:

  • reproduce substantial portions of the Task Library;
  • republish the Task Library on another website or platform;
  • create or operate a mirror of the Task Library;
  • redistribute complete chapters, task collections, or substantial extracts;
  • sell, sublicense, rent, or commercially distribute the Task Library;
  • package the Task Library as part of another commercial product;
  • create a competing book, course, training system, task library, or educational product by substantially reproducing protected Scalionix material;
  • systematically copy or download the Task Library;
  • use automated tools to harvest substantial portions of the content;
  • remove copyright notices, attribution notices, watermarks, identifiers, or other rights-management information;
  • falsely claim authorship or ownership of Scalionix material;
  • falsely imply endorsement, partnership, certification, or affiliation with Scalionix;
  • circumvent access controls, authentication systems, rate limits, or other technical restrictions;
  • share access credentials where access is intended for an individual account;
  • use the platform in violation of applicable law.

Nothing in these Terms is intended to prohibit an activity that cannot legally be restricted under applicable law.


7. Automated Extraction and Dataset Creation

Automated access intended to systematically collect substantial portions of the Task Library is prohibited unless explicitly authorized.

This includes, without limitation:

  • scraping;
  • crawling for content extraction;
  • bulk downloading;
  • systematic page harvesting;
  • automated reconstruction of the Task Library;
  • dataset generation;
  • corpus generation.

Ordinary browser access and legitimate search-engine indexing authorized by Scalionix are not considered prohibited automated extraction.


8. Artificial Intelligence and Machine Learning

Unless expressly authorized in writing, substantial portions of the Task Library may not be collected or used as:

  • machine-learning training data;
  • fine-tuning data;
  • retrieval-augmented generation corpora;
  • embedding corpora;
  • benchmark datasets;
  • evaluation datasets;
  • synthetic-data source material;
  • datasets for reproducing or approximating the Task Library.

This restriction is directed at systematic ingestion or exploitation of the Task Library as a dataset.

It is not intended to prohibit ordinary personal use of development tools or assistants involving limited excerpts during legitimate study, unless a specific part of the Task Library states otherwise.


9. Educational Products and Training

The Task Library may not be substantially reproduced or incorporated into:

  • university or school course materials;
  • commercial training programs;
  • corporate training packages;
  • certification programs;
  • online courses;
  • books;
  • tutorials;
  • coding challenge platforms;
  • interview preparation platforms;
  • educational datasets

without prior written permission.

Educators, universities, companies, and training organizations interested in using the Task Library may contact Scalionix to request permission or a separate license.

Independent teaching of the same general subject matter is not prohibited.

The restriction applies to protected Scalionix content, expression, selection, organization, and materials.


10. Accounts and Access

Where access to the Task Library requires an account, you are responsible for maintaining the confidentiality and security of your account credentials.

You may not provide another person with access through your account where access is licensed or granted on an individual basis.

Scalionix may restrict, suspend, or terminate access where there is reasonable evidence of:

  • unauthorized redistribution;
  • credential sharing;
  • systematic content extraction;
  • attempts to circumvent technical restrictions;
  • abuse of the platform;
  • serious or repeated violations of these Terms.

11. Intellectual Property

The Scalionix Task Library and its protected contents remain the property of their respective copyright holders.

Protected material may include, where applicable:

  • written explanations;
  • task descriptions;
  • exercises;
  • examples;
  • illustrations;
  • diagrams;
  • graphics;
  • original tables;
  • original datasets;
  • original code;
  • chapter organization;
  • selection and arrangement of material;
  • educational progression;
  • accompanying documentation.

Additional information is available on the Copyright & Intellectual Property page.


12. Scalionix Name and Branding

The Scalionix name, logos, visual identity, product names, and related branding may be protected by trademark or other applicable law.

Nothing in these Terms grants permission to use Scalionix branding in a manner that suggests:

  • endorsement;
  • sponsorship;
  • partnership;
  • certification;
  • affiliation;
  • official status

unless such permission has been granted in writing.

Referencing Scalionix for legitimate identification, discussion, citation, or review is not prohibited where allowed by applicable law.


13. Third-Party Material

The Task Library may reference or link to:

  • third-party software;
  • programming languages;
  • frameworks;
  • libraries;
  • standards;
  • documentation;
  • websites;
  • publications;
  • trademarks.

Such references do not imply ownership by Scalionix.

Third-party materials remain subject to their respective licenses, copyright terms, trademarks, and terms of use.


14. Accuracy of Technical Information

The Task Library is developed with the goal of providing accurate and useful technical material.

However, software engineering evolves continuously.

Programming languages, libraries, operating systems, APIs, security practices, tools, standards, and implementation details may change over time.

Scalionix does not guarantee that every example or statement will remain accurate, complete, secure, or suitable for every environment.

You are responsible for independently evaluating material before using it in production systems.


15. Security-Sensitive Material

Some exercises may involve topics related to:

  • security;
  • networking;
  • cryptography;
  • authentication;
  • authorization;
  • operating systems;
  • system administration;
  • infrastructure.

Examples are provided for educational purposes.

You are responsible for testing and reviewing any implementation before using it in a security-sensitive or production environment.


16. No Warranty

To the maximum extent permitted by applicable law, the Task Library and Scalionix platform are provided on an “as is” and “as available” basis.

No guarantee is made that the Task Library will:

  • satisfy a particular requirement;
  • be error-free;
  • remain continuously available;
  • be suitable for a particular project;
  • produce a specific educational or professional outcome.

17. Limitation of Liability

To the maximum extent permitted by applicable law, Scalionix and the respective authors and right holders shall not be liable for indirect, incidental, consequential, special, or similar damages resulting from the use of, inability to use, or reliance upon the Task Library.

You remain responsible for decisions made based on the material and for verifying software before deploying it in real systems.

Nothing in these Terms excludes or limits liability where such exclusion or limitation is prohibited by applicable law.


18. Changes to the Task Library

The Task Library is an evolving publication.

Scalionix may:

  • add new material;
  • remove material;
  • reorganize chapters;
  • revise exercises;
  • correct errors;
  • change examples;
  • introduce new editions or versions;
  • discontinue older versions.

Different versions of the Task Library may therefore contain different material.


19. Changes to These Terms

These Terms may be updated when necessary to reflect:

  • changes to the Task Library;
  • changes to the Scalionix platform;
  • new platform functionality;
  • changes in applicable law;
  • changes in licensing or intellectual property policy.

The current version will identify its effective or last-updated date.

Material changes will apply prospectively as required by applicable law.


20. Governing Law

Unless mandatory law requires otherwise, these Terms are governed by the laws of the Republic of Serbia.

Any dispute relating to these Terms or use of the Task Library shall be handled by the competent courts of the Republic of Serbia, subject to any mandatory jurisdiction or consumer-protection rules that may apply.


21. Severability

If any provision of these Terms is found to be invalid or unenforceable, the remaining provisions shall continue in effect to the extent permitted by law.


22. Contact

Questions regarding:

  • permitted use;
  • licensing;
  • educational use;
  • commercial use;
  • republication;
  • copyright;
  • partnerships

may be directed to: Nikola Djurdjevic [LinkedIn]

Written permission should be obtained before undertaking any use for which these Terms require authorization.

Copyright & Intellectual Property

© [2021]–[2026] [Nikola Djurdjevic / Scalionix]. All rights reserved.

The Scalionix Task Library, including its original written material, exercises, task descriptions, explanations, examples, illustrations, diagrams, organization, and other protected content, is protected by applicable copyright and intellectual property law.

Unless otherwise stated, no ownership rights are transferred to users by providing access to the Task Library.

Access grants only the rights expressly described in the Terms of Use.


Ownership

The original content of the Scalionix Task Library is owned by its respective author or right holder.

Depending on the material, protected content may include:

  • chapter text;
  • technical explanations;
  • task descriptions;
  • exercise descriptions;
  • problem statements;
  • original examples;
  • original source code;
  • diagrams;
  • illustrations;
  • graphics;
  • tables;
  • original datasets;
  • reference material;
  • original classifications;
  • educational sequences;
  • selection and arrangement of material.

Individual components may also contain third-party material subject to separate rights or licenses.

Where third-party material is used, its respective copyright, license, and attribution requirements continue to apply.


Copyright protection does not depend on the presence of this notice.

The copyright notice is provided to clearly communicate ownership and permitted use.

The omission of a copyright notice from a particular page, example, illustration, file, or earlier version does not mean that the material is in the public domain or may be freely reproduced.


Copyright protects original expression fixed in a particular form.

Within the Task Library, this can include the particular way in which a technical subject is:

  • explained;
  • written;
  • illustrated;
  • organized;
  • demonstrated;
  • presented through exercises;
  • structured into chapters;
  • arranged into a progression of tasks.

A task may therefore contain protected expression even when the underlying programming concept itself is widely known.

The same applies to original collections and arrangements of material where the selection or organization reflects original intellectual creation.


Copyright does not grant ownership over general knowledge.

It does not prevent others from independently using:

  • ideas;
  • programming concepts;
  • algorithms as abstract concepts where not otherwise legally protected;
  • general engineering principles;
  • mathematical concepts;
  • general methods of work;
  • programming languages;
  • public APIs;
  • generally known software patterns;
  • facts.

For example, Scalionix does not claim ownership of concepts such as:

  • concurrency;
  • serialization;
  • hashing;
  • encryption;
  • caching;
  • distributed systems;
  • dependency injection;
  • data structures;
  • REST;
  • networking;
  • benchmarking.

You are free to learn these subjects and apply that knowledge independently.

What may be protected is the specific Scalionix expression, explanation, task design, illustration, example, selection, or arrangement used to teach them.


Task Design and Educational Structure

A significant part of the Scalionix Task Library is the design and progression of its tasks.

Individual tasks may be intentionally selected and ordered to develop specific abilities, including:

  • analytical reasoning;
  • debugging;
  • problem decomposition;
  • architectural judgment;
  • performance reasoning;
  • concurrency reasoning;
  • code review;
  • failure analysis;
  • systems thinking.

The value of the Task Library therefore does not consist only of individual paragraphs or code examples.

The selection, formulation, organization, progression, and relationship between tasks may form part of the protected work.

This means that reproducing the overall Task Library by rewriting individual sentences while substantially preserving its protected structure or content may still raise intellectual property concerns.

This statement does not claim ownership over general educational methods, ideas, or programming concepts themselves.


Personal and Educational Use

Users may access and study the Task Library in accordance with the Terms of Use.

Reasonable personal activities are permitted, including:

  • reading;
  • studying;
  • taking personal notes;
  • solving tasks;
  • experimenting with examples;
  • discussing concepts in your own words;
  • applying acquired knowledge.

Learning from the Task Library does not create any obligation to Scalionix regarding software or other work that you later develop independently.


Quotation and Reference

Limited quotation may be permitted where allowed by applicable law.

When quoting Scalionix material, attribution should identify the source clearly.

A recommended attribution format is:

Scalionix Task Library, “[Chapter or Task Title],” version [VERSION].

Where technically practical, a reference to the official Scalionix publication should also be included.

A citation does not by itself authorize reproduction of an otherwise substantial portion of the work.


Use in Books, Publications, and Educational Material

Scalionix content may not be reproduced in books, publications, articles, training material, course material, documentation, educational platforms, or similar works merely by providing attribution.

Attribution does not replace the requirement to obtain permission where permission is required.

Limited quotation may be permitted where allowed by applicable law, provided that the Scalionix Task Library is clearly identified as the source.

Any reproduction beyond a limited quotation, including reproduction of substantial passages, complete tasks, exercises, diagrams, examples, chapter structures, or collections of material, requires prior written permission from the copyright holder.

Where permission is granted, the resulting publication must include the attribution specified by Scalionix.

A recommended attribution format is:

Scalionix Task Library, “[Chapter or Task Title],” version [VERSION], © [YEAR] [Copyright holder].

Permission to reproduce one specific portion of the Task Library does not grant permission to reproduce other portions or the Task Library as a whole.


Commercial Exploitation and Paid Educational Use

Unless expressly authorized in writing by the copyright holder, Scalionix material may not be used to generate revenue for another person, organization, company, educator, training provider, or platform.

This includes using protected Scalionix content, in whole or in substantial part, within:

  • paid online courses;
  • classroom courses for which tuition or participation fees are charged;
  • commercial workshops;
  • corporate training programs;
  • bootcamps;
  • certification programs;
  • paid mentoring programs;
  • subscription-based educational platforms;
  • books or publications offered for sale;
  • paid newsletters;
  • premium documentation;
  • commercial coding challenge platforms;
  • interview-preparation services;
  • consulting or training packages;
  • any other product or service for which payment, subscription fees, licensing fees, advertising revenue, sponsorship revenue, or other commercial benefit is received.

Providing attribution to Scalionix does not grant permission for commercial use.

For example, the following is not permitted without prior written authorization:

A training provider reproduces Scalionix tasks, explanations, diagrams, examples, or educational sequences inside a paid course and identifies Scalionix as the original source.

Attribution is required where applicable, but attribution does not replace the requirement to obtain permission.

A person may use the knowledge and skills learned from the Task Library in professional or commercial work.

A person may also independently teach the same general technical subjects using independently created explanations, exercises, examples, and course structures.

The restriction applies to the commercial reuse of protected Scalionix material, not to the independent use of knowledge acquired from it.

Any commercial licensing of Scalionix material requires a separate written agreement or explicit written permission from the copyright holder.


Code Examples

Technical documentation must remain practical.

Accordingly, unless otherwise stated, users may copy and adapt individual code examples for their own software projects.

This includes commercial software development.

This permission does not authorize reproduction of the Task Library itself.

In particular, it does not authorize someone to collect substantial numbers of examples and redistribute them as:

  • another documentation set;
  • a tutorial collection;
  • a code-example repository;
  • a book;
  • a training course;
  • an educational website;
  • a coding challenge platform;
  • a dataset.

Code carrying a separate license notice remains governed by that license.


Your Own Solutions

Solutions independently written by a user in response to a Task Library exercise generally remain the user’s own work, subject to applicable law and third-party rights.

Scalionix does not claim ownership of a user’s independently written implementation merely because the Task Library inspired or requested it.

However, publishing your own solution does not automatically grant permission to reproduce the original Scalionix:

  • task statement;
  • explanation;
  • diagrams;
  • test data;
  • accompanying text;
  • protected examples.

Where necessary, describe the problem in your own words rather than reproducing substantial protected material.


Redistribution

Unless prior written permission has been obtained, the Task Library may not be substantially:

  • copied;
  • reproduced;
  • mirrored;
  • republished;
  • translated;
  • redistributed;
  • resold;
  • sublicensed;
  • commercially exploited;
  • incorporated into another educational publication.

This includes redistribution that is provided free of charge.

The absence of a commercial purpose does not automatically authorize reproduction.

Applicable statutory exceptions remain unaffected.


Translations

Official translations of the Scalionix Task Library remain protected works.

Creating or distributing an unofficial translation of a substantial portion of the Task Library requires permission unless applicable law provides otherwise.

When Scalionix publishes the same material in multiple languages, copyright applies to the respective original and translated expression as applicable.


Derivative Educational Material

You may create your own original educational material about the same technical subjects.

You may explain the same programming concepts.

You may teach similar technologies.

You may create your own exercises independently.

You may not, without authorization, reproduce protected Scalionix content or create another publication that substantially copies protected elements of the Task Library.

This distinction is important:

Knowledge is meant to be learned and used.
The publication itself is not being placed into the public domain.


Automated Collection

Systematic automated extraction of Scalionix content is not authorized unless explicitly permitted.

Examples include:

  • scraping entire sections;
  • bulk chapter collection;
  • automated task harvesting;
  • rebuilding the Task Library from downloaded pages;
  • creating large text corpora from the Task Library.

Normal human use of the website is unaffected.


AI, Machine Learning, and Dataset Use

The publication of the Task Library does not grant permission to systematically use its protected content for:

  • model training;
  • model fine-tuning;
  • retrieval databases;
  • embedding corpora;
  • evaluation datasets;
  • benchmark datasets;
  • synthetic-data generation;
  • commercial data products.

Separate permission may be requested for such uses.

This restriction concerns systematic use of the protected corpus and does not prohibit ordinary limited use of assistive software during personal study where otherwise permitted.


Scalionix may maintain records relating to published versions of the Task Library.

These records may include:

  • source-control history;
  • publication dates;
  • version numbers;
  • release tags;
  • archived source packages;
  • cryptographic hashes;
  • digitally signed manifests;
  • publication logs;
  • deposited copies of published editions.

These records may be used to establish the provenance, integrity, publication history, or authorship of particular versions of the work.

Where an edition has been formally deposited or recorded with an intellectual property authority, additional identifying information may be provided here.

Deposit / record reference: [optional]


Versioned Publications

The Scalionix Task Library is maintained as a versioned publication.

Content may change between releases.

A specific copyright claim or attribution should therefore refer, where relevant, to the applicable version.

For example:

Scalionix Task Library
Version: 1.4.0
Published: 2026-09-17

Historical releases may be preserved for evidentiary, archival, compatibility, or educational purposes.


Integrity of the Work

Unauthorized modification of Scalionix material must not be presented as an official Scalionix publication.

Modified copies must not use Scalionix branding in a way that could reasonably cause users to believe that they are authentic or officially maintained versions.


Scalionix Branding

Copyright permission does not automatically grant trademark permission.

The Scalionix name, logos, product names, graphical identity, and other branding may be protected separately.

They may not be used to falsely imply:

  • official status;
  • endorsement;
  • sponsorship;
  • partnership;
  • certification;
  • authorship;
  • affiliation.

Legitimate references to Scalionix for identification, commentary, attribution, or discussion remain subject to applicable law.


If you believe Scalionix material has been reproduced or distributed without authorization, please contact:

Scalionix
Email: [copyright contact email]

Where possible, include:

  • the Scalionix material involved;
  • the location of the suspected unauthorized copy;
  • the relevant URL or platform;
  • screenshots or other evidence;
  • the date on which the material was observed.

Permission and Licensing Requests

Permission may be requested for uses including:

  • university teaching;
  • commercial training;
  • corporate education;
  • translation;
  • republication;
  • substantial quotation;
  • book publication;
  • course development;
  • platform integration;
  • dataset use;
  • other commercial licensing.

Contact:

Scalionix
Email: [licensing contact email]

Permission is valid only when granted by an authorized right holder or representative.


Reservation of Rights

Except for rights expressly granted by the Terms of Use or required by applicable law, all rights are reserved.

Nothing in the publication of the Scalionix Task Library should be interpreted as a waiver, abandonment, or transfer of intellectual property rights.

AI Models and Software Engineering

One of the most important subjects in modern software engineering is the influence of Large Language Models, generative AI systems, and increasingly autonomous AI agents on the way software is designed, implemented, reviewed, tested, and operated.

For people entering this profession, one question appears almost immediately:

Does it still make sense to learn programming and software engineering if AI systems can already generate code?

This question is understandable.

The public discussion around AI is often dominated by extreme positions.

One side presents AI agents as systems that will replace almost every developer and engineer.

The other side treats them as useless toys that have no serious place in professional engineering.

I do not believe either position is useful.

AI systems are already capable of producing meaningful software, accelerating certain tasks, generating useful technical material, assisting with debugging, navigating repositories, writing tests, and automating parts of development workflows.

That capability should be taken seriously.

But generating code is not the same thing as owning an engineering problem.

Producing a locally correct implementation is not the same thing as designing, operating, evolving, and taking responsibility for a system over years.

That distinction is the starting point of this chapter.


Is Software Engineering Still Worth Learning?

Absolutely, YES.

But the reason is not that AI systems are weak or that they will stop improving. They will improve.

The reason is that software engineering has never been only about typing source code.

Code is one representation of a much larger process. A real engineering process may include:

  • understanding incomplete requirements;
  • identifying the actual problem;
  • rejecting unnecessary complexity;
  • designing system boundaries;
  • selecting technologies;
  • reasoning about failure;
  • understanding security consequences;
  • planning migrations;
  • preserving compatibility;
  • operating production systems;
  • debugging behavior that was never expected;
  • controlling cost;
  • communicating across teams;
  • making trade-offs;
  • deciding what should not be built;
  • taking responsibility for the final result.

An AI system may participate in many of these activities. That does not automatically make it the owner of those activities.

A useful mental model is:

Code generation ≠ Software engineering.

and:

Producing an answer ≠ Owning the consequences of that answer.

If your entire professional value is based on manually producing predictable boilerplate, then AI will place increasing pressure on that part of your work.

If your value comes from understanding systems, making decisions, solving unfamiliar problems, validating results, designing for change, and taking responsibility for technical consequences, then the profession remains much broader than code generation.

The correct response to AI is not to stop learning. The correct response is to learn more deeply.


AI Models, Infrastructure, and Intellectual Property

One of the biggest problems surrounding AI models is the violation of intellectual property.

If you give any AI agent access to your infrastructure, your network, or your source code base, then every later discussion about security, data protection, and intellectual property becomes largely pointless. The moment you give an AI model access, you have already allowed a third party into your infrastructure.

At that point, if an unwanted scenario happens, you cannot act as though that access appeared out of nowhere.

You gave it permission. You gave it access. And you accepted that risk yourself.

We have already seen situations where all kinds of things happen in environments like this:

  • A problem suddenly appears and nobody inside the technical teams knows how to fix it.
  • A part of a database disappears.
  • Certain permissions suddenly exist even though nobody knows how they got there.
  • A system is modified in a way nobody clearly remembers authorizing.

And then everyone starts asking:

How did this happen?

The better question is:

Why was a third-party agent given access to that part of the infrastructure in the first place?


Do not think that absolutely everyone, from every technical environment, is enthusiastic about AI models or willing to give them access.


That is simply not true.

Our environment, as well as the environments of the partners we work with, has strict rules and conventions written into agreements with technical personnel.

One of many such rules is that technical staff are strictly forbidden from using AI agents for writing code.

Giving any AI agent access to any part of company intellectual property is also treated extremely seriously.

We will see what happens when legal cases begin appearing around questions such as:

  • who created what;
  • how something was created;
  • who owns a specific implementation;
  • what part was written by a human;
  • what part was generated by an AI system;
  • what data was exposed during that process;
  • who can prove the origin of the implementation.

And then another question appears. What happens when:

  • the companies operating AI models start claiming that they have evidence showing that their system generated a certain implementation inside someone else’s infrastructure?
  • arguments begin over intellectual property ownership because a third-party model participated in building part of a proprietary system?
  • nobody can clearly prove where one part ends and another begins?

Think about that.

Intellectual property around generative AI is still an active legal and regulatory area.

The correct response is not to invent certainty where certainty does not yet exist. Training datasets can involve combinations of:

  • publicly available material;
  • licensed material;
  • proprietary material;
  • synthetic data;
  • other sources depending on the provider.

There are ongoing legal disputes and policy questions concerning the use of copyrighted works in AI training.

The U.S. Copyright Office has published a multi-part AI study addressing copyrightability and generative AI training. In the European Union, obligations for providers of general-purpose AI models include maintaining a copyright compliance policy and publishing a sufficiently detailed summary of training content. The legal environment is therefore actively evolving.

For engineering organizations, this creates practical questions:

  • Are we allowed to send this source code to this provider?
  • What does our customer contract allow?
  • What does the provider’s agreement say about submitted data?
  • Can generated code contain material that creates licensing concerns?
  • Can we prove which parts of a product were human-authored?
  • Do we maintain provenance for important generated artifacts?
  • Are we exposing trade secrets?

These are governance questions. They should be answered before an agent is connected to sensitive systems.


Another point needs precision.

Using AI during development does not automatically mean that the resulting software has no copyright protection.

For example, the U.S. Copyright Office has stated that human-authored expressive contributions can remain protected even when AI is used as an assistive tool.

The difficult questions concern the amount and nature of human authorship, the provenance of generated material, training-data rights, and the contractual terms under which specific tools are used.

Therefore, organizations should not rely on simplistic statements such as:

        AI touched the code
                ↓
        nobody owns it

or:

        AI wrote part of the code
                ↓
        the AI provider owns part of the product

Those conclusions do not follow automatically. The responsible approach is:

  • define company policy;
  • understand provider terms;
  • preserve provenance where important;
  • protect confidential material;
  • obtain legal advice where the risk justifies it.

Application Development and Technology Development

At the beginning of this discussion, we need to separate software development sharply into two different areas.

There is:

  • application development
  • technology development

Application development is the kind of development in which engineers use existing technologies to create new services and applications.

Technology development is the kind of development in which developers create new technologies and systems that engineers and other technical people will later use when building services and applications. A practical example of application development would be building infrastructure for aviation traffic.

Technology development made it possible to create things such as:

  • different types of software communication systems;
  • different programming languages;
  • different database architectures;
  • different mechanisms and standardized protocols;
  • different security systems;
  • different operating systems;
  • different server models and runtime environments.

All of these are then used by the application layer under the hood. There are AI agents that can help in certain areas of application development.

That is not the problem. The point is that their role is that of an assisting tool.

They should not become the mechanism that a technical person depends on.

And they should not become the foundation of an engineering process where the person becomes incapable of functioning without them.

LLM and AI systems build their outputs from statistical patterns, metrics, and previously existing data.

A mechanism built around existing patterns can be useful when working with things that have a generic form or that contain relatively small automated processes. A simple informational website is a good example. In that kind of work, an AI agent can be extremely useful. It can generate layouts, components, basic backend logic, configuration, boilerplate, and repetitive structures very quickly. But only up to a certain point.

As complexity increases, as the number of features increases, and as the amount of required context grows, the system becomes harder and harder for the model to hold together coherently.

At some point, the agent stops being useful. After that point, it can become actively dysfunctional.

Then it can:

  • starts losing track of earlier decisions.
  • begins introducing contradictions.
  • changes one part of the system without understanding what that change breaks somewhere else.
  • starts inventing things that were never defined.
  • begins presenting unverified assumptions as though they were facts.
  • eventually, instead of reducing work, it starts generating additional work.

We started with the most basic possible form: A website. Now take a much larger infrastructure.

Imagine a platform containing many services and applications, built by many teams:

  • Different teams are building different parts.
  • Some teams operate in completely separate environments.
  • Services depend on other services.
  • Internal protocols exist.
  • Different databases exist.
  • Different security rules exist.
  • bDifferent deployment environments exist.
  • Different versions exist.
  • Different migration paths exist.

Now the situation changes completely. At that point it becomes very obvious what an AI agent actually is:

A tool. It can be useful. It can be functional.

It can be extremely powerful in the right hands.


AI model is not the initiator, owner, or creator of the entire engineering process.


The context limit of an AI system represents a numerical limitation on how much information the system can actively process within a given interaction. Once the amount of relevant information becomes too large, the model begins losing parts of the conversation and system context.

The larger that problem becomes, the easier it is for the model to start hallucinating, contradicting earlier information, or writing things that were never verified and may never have existed in the first place. Now increase the size of the infrastructure. Think about:

  • financial systems;
  • stock exchanges;
  • electrical power systems;
  • security systems;
  • operating systems;
  • cloud infrastructure;
  • distributed storage systems;
  • large internal platforms.

The operational scope, complexity, number of dependencies, and long-term development of systems like these are far beyond what can be treated as one simple AI conversation. And in environments like these, some of the most important properties are:

  • flexibility;
  • modularity;
  • scalability.

Only after that do we start talking about:

  • performance;
  • resource usage.

Large infrastructure has to be designed many steps ahead. Sometimes fifty steps. Sometimes a hundred. You have to think about what happens after:

  • the next feature;
  • the next service;
  • the next migration;
  • the next operating-system version;
  • the next protocol change;
  • the next security requirement;
  • the next scaling problem;
  • the next five years of development.

AI agents naturally tend to satisfy the current request. You give the system a problem. It attempts to produce something that works for that problem now. If the current context allows it, it generates a solution that appears correct at that moment.

That is a terrible way to design serious long-term infrastructure.

Infrastructure built to the highest technical standards does not work like that.

It requires:

  • planning;
  • sketching;
  • designing;
  • rejecting ideas;
  • redesigning;
  • thinking through future interactions;
  • thinking through failure cases;
  • considering changes that have not happened yet.

The problem is that an AI agent does not have an independent understanding of what will happen in future iterations of your system. It knows what you give it now. If every future update or upgrade requires large parts of the generated code to be rewritten, then the original design was not good. A system that constantly requires massive refactoring because every new feature breaks the previous assumptions was badly designed from the beginning.


That is not scalability. That is not modularity. That is not good engineering.


This becomes even more obvious in very large infrastructures and technical conglomerates. AI models can be strongly promoted internally. They can be introduced aggressively. They can be used everywhere people believe they may reduce cost or increase speed. But once the work reaches a certain level of complexity, teams very quickly discover the difference between:

Generating code and Understanding the entire system.

Those are not the same thing.

AI models begin losing the overall picture as the amount of code, dependencies, interaction, historical decisions, and system-specific knowledge grows.

Now imagine an infrastructure like ours.

Eighteen services. More than twenty thousand lines of code per service on average. A large number of interactions between those services.

Multiple factors on infrastructure level:

  • Shared behavior.
  • Dependencies.
  • Protocols.
  • Versioning.
  • Deployment logic.
  • Internal assumptions.
  • Future development.

And that is still a relatively small infrastructure compared with the systems operated by the largest technology companies in the world. Now imagine what happens at that scale. The idea that one AI agent simply “understands the whole thing” because it can generate code inside one part of it is completely unrealistic. That is exactly where the distinction has to be made:

  • AI can assist development.
  • AI can automate repetitive work.
  • AI can generate useful implementation.
  • AI can help experienced engineers move faster.

But a tool that generates code should never be confused with the engineering system, the engineering process, or the people responsible for designing and owning the infrastructure.


The Mental Effect of Constant AI Dependency

Another subject that needs to be discussed much more openly is the effect that frequent AI use can have on technical people themselves.


Not only technically. Mentally as well.


AI models can be extremely useful for generating generic structures and reducing the amount of repetitive work that does not require much creativity, design, or deep technical reasoning.

That is useful, but there is another side to it.

The more frequently people delegate even basic technical thinking to AI systems, the easier it becomes to become mentally lazy about things that they previously would have solved themselves.

And if you become lazy about basic problems, you will not suddenly become disciplined when a truly difficult problem appears.

You gradually lose patience. You gradually lose tolerance for uncertainty.

You become less willing to sit with a problem for hours or days.

You become accustomed to receiving some form of result almost immediately.

That is completely incompatible with serious engineering.

No serious science, engineering discipline, or long-term technical development works on the assumption that every meaningful problem must produce a visible result within several hours.

Real work often does not behave like that.

Sometimes you will spend:

4 hours

and achieve almost nothing visible.

Sometimes:

3 days

Sometimes:

3 weeks

And sometimes you will work on a problem for months before the pieces finally connect into something functional.

That process develops:

  • patience;
  • endurance;
  • analytical depth;
  • problem decomposition;
  • emotional stability;
  • technical intuition;
  • the ability to remain functional while the answer is still unknown.

If every difficult moment immediately becomes [Ask the AI].

you can slowly destroy your tolerance for exactly the kind of mental state that serious engineering requires.

There is also another effect.

People begin to feel less capable without the tool. They become less confident in their own ability to investigate.

They become less satisfied with their own work. They stop feeling productive unless something visible is generated quickly.

Anything that cannot be completed within a few hours or within a single working day starts to feel mentally heavy.

This is dangerous. You are conditioning yourself to expect immediate output. But immediate output does not automatically have:

  • depth;
  • quality;
  • reliability;
  • technical weight;
  • maintainability;
  • credibility.

A large amount of generated output can look impressive while containing very little actual engineering value.

This problem can become even more obvious in technical areas, ecosystems, or programming languages where there is less high-quality public information available.

AI systems are strongly dependent on the quality and quantity of the material from which they have learned and the context they are given.

Where the public technical ecosystem is weaker, narrower, poorly documented, or dominated by repetitive material, the quality of generated output may also degrade significantly.

That is another reason why you need your own technical judgment.


Do not measure yourself by how quickly an AI agent can produce something.


Measure yourself by whether you understand what was produced.

Whether you can verify it. Whether you can improve it. Whether you can repair it when it fails.

And whether you can continue working when the tool is removed.

If you cannot function without the AI system, then the AI system is no longer helping your engineering ability.

It has replaced part of it. That is dependency and dependency should never be confused with productivity.


Generation Is Not Verification

One of the biggest changes created by AI-assisted development is that generating code can become much faster than validating code.

Before widespread generative AI, one bottleneck was often: Writing the implementation.

With AI assistance, the bottleneck can move:

        Understanding the requirement
                ↓
        Reviewing generated code
                ↓
             Testing
                ↓
        Security verification
                ↓
           Integration
                ↓
        Operational validation
                ↓
        Long-term maintenance

This gives us an important equation:

10× faster code generation ≠ 10× faster software engineering.

DORA’s 2026 analysis describes a similar tension: AI can reduce friction during initial code generation, while part of the time saved is later reallocated to auditing and verification.

That does not mean AI is ineffective.

It means that measuring only the speed of code production can be misleading.

The final unit of value is not:

How many lines were generated?

The useful questions are:

  • Was the correct system built?
  • How much review was required?
  • How many defects were introduced?
  • How maintainable is the result?
  • How much operational risk was created?
  • How easily can the system evolve?
  • Can the team explain what was produced?

Context Windows Are Not System Understanding

Every LLM has a finite working context.

A context window defines how much information can be made available to the model during a particular interaction.

But one thing needs to be made absolutely clear:

Being able to receive information is not the same thing as understanding the entire system that information belongs to.

A model can technically accept a very large amount of text and still fail to correctly connect every relevant dependency, architectural decision, historical assumption, or interaction inside that material.

As the amount of information grows, the problem is no longer simply:

    Can the model read this?

The real question becomes:

    Can the model preserve the correct relationships between all relevant parts of the system?

Those are completely different problems.

A large context window does not automatically mean that the model has a complete and coherent understanding of everything inside it.

It may receive:

  • source code;
  • documentation;
  • configuration;
  • logs;
  • database schemas;
  • API definitions;
  • architectural descriptions.

That still does not mean it understands all of the interactions between them correctly.

And the larger and more interconnected the system becomes, the harder that problem becomes.

Modern agents can partially compensate for this through:

  • repository search;
  • retrieval systems;
  • code indexes;
  • tools;
  • subagents;
  • summaries;
  • external memory;
  • iterative exploration.

That absolutely makes them more capable.

But none of those mechanisms change the fundamental problem.

A repository is not the same thing as a complete system.

And a complete system is not only source code. It is also:

        source code
        +
        service dependencies
        +
        database behavior
        +
        deployment rules
        +
        network topology
        +
        security assumptions
        +
        historical decisions
        +
        production incidents
        +
        migration plans
        +
        internal conventions
        +
        future requirements
        +
        knowledge that exists only inside engineers' heads

That is the real context of a serious infrastructure.

So the deeper limitation is not simply the number of tokens that fit inside a context window.

The deeper limitation is system knowledge.

An AI agent may be able to inspect thousands of files. It may search them efficiently. It may summarize them. It may even make useful changes across many of them.

But that should never be confused with having complete ownership and understanding of the entire technical system. Those are not the same thing.


Large Systems Are Designed for Change

In large infrastructure, immediate functionality is only one requirement.

A system also needs to survive change. Some of the most important qualities are:

  • modularity;
  • clear boundaries;
  • flexibility;
  • scalability;
  • observability;
  • maintainability;
  • recoverability;
  • compatibility.

Performance and resource consumption matter as well, but optimizing those properties without preserving the ability to evolve can produce a system that is fast today and extremely expensive to change tomorrow. A locally functional implementation may still be a poor engineering decision. For example:

        Requirement satisfied today
                ↓
               but
                ↓
        every future feature requires rewriting three services

That is not a good design. This is why experienced engineers frequently spend significant time on:

  • diagrams;
  • interfaces;
  • data ownership;
  • failure boundaries;
  • migration paths;
  • future extension points;
  • operational behavior.

AI can assist with all of these, but it cannot know requirements that nobody has communicated to it.

It cannot reliably optimize for future constraints that the organization itself has not yet identified.

And it cannot take responsibility for strategic decisions that were never made explicit.


The Problem of Future Iterations

Engineering is rarely a single interaction. A production system may live for:

  • 5 years
  • 10 years
  • 20 years

During that period:

  • teams change;
  • requirements change;
  • dependencies change;
  • security threats change;
  • regulations change;
  • traffic changes;
  • hardware changes;
  • business priorities change.

A design must therefore have some ability to absorb unknown future changes.


No human architect can predict every future requirement in 100% of the cases. AI cannot either.


The goal is not prediction. The goal is designing systems with boundaries and abstractions that make future change less destructive. That requires judgment. And judgment is built from:

  • theory;
  • implementation experience;
  • failed designs;
  • production incidents;
  • migrations;
  • performance problems;
  • security failures;
  • maintenance experience.

An AI system may encode patterns derived from enormous amounts of technical material.

But an engineering organization still needs people who can evaluate whether those patterns fit its actual environment.


Benchmarks Are Not Production

AI coding benchmarks are useful. They tell us something. They do not tell us everything. A benchmark normally defines:

  • an input;
  • a task;
  • an evaluation method;
  • a success condition.

Real software engineering often has none of those properties clearly defined. The specification may be incomplete. The tests may be wrong.

The correct behavior may depend on undocumented business knowledge. Two technically valid solutions may have completely different operational consequences. A system may pass every available test and still be unacceptable in production. A useful 2026 METR study examined AI-generated pull requests that had already passed the automated SWE-bench Verified grader. Maintainers reviewing those patches would reject roughly half of the test-passing AI pull requests in the studied sample. The researchers explicitly caution that this does not establish a fundamental capability limit, because real agents could iterate after code review feedback.

The important lesson is simpler:

Passing an automated benchmark is not equivalent to passing real engineering review.

That difference matters.


Developer Sentiment Is More Complicated Than the Marketing

The 2025 Stack Overflow Developer Survey provides another useful perspective.

AI adoption is very high. At the same time, trust is significantly lower than adoption.

Among respondents:

  • 84% were using or planning to use AI tools in development;
  • more developers reported distrusting AI output accuracy than trusting it;
  • 66% identified “almost right” AI solutions as a major frustration;
  • 45% reported that debugging AI-generated code could take more time;
  • developers showed especially strong resistance to delegating high-responsibility areas such as deployment and monitoring.

This is a useful reminder: High adoption ≠ High trust.

A tool can be useful enough to use every day while still requiring constant verification.


AI Should Amplify Knowledge, Not Replace It

This is the rule I would strongly recommend during learning:

Never allow a tool to become the owner of a thought process that you are still supposed to be learning.

There is a major difference between these two scenarios.

Scenario A

An experienced engineer understands:

  • the problem;
  • the implementation;
  • the failure modes;
  • the architecture.

They ask an AI system to generate repetitive code.

The engineer reviews it, modifies it, tests it, and accepts responsibility for it.

AI reduced mechanical work.

Scenario B

A beginner does not understand:

  • the problem;
  • the implementation;
  • the failure modes;
  • the architecture.

They ask an AI system for the complete solution.

The code runs.

They move to the next task.

These two people may produce the same visible output today.

They are not building the same capability.

The first person automated something they already understood.

The second person may have automated the exact cognitive process that was supposed to develop their understanding.

This is where AI can become dangerous for learning.

Not because asking AI a question is bad.

Not because generated code is automatically bad.

The danger is dependency.


Cognitive Dependency

If every obstacle immediately becomes [Ask the model] your tolerance for unresolved problems can gradually decrease.

You can become accustomed to: [question => immediate answer].

But serious engineering often looks like:

        problem
            ↓
        wrong hypothesis
            ↓
        investigation
            ↓
        new evidence
            ↓
        another wrong hypothesis
            ↓
        documentation
            ↓
        experiment
            ↓
        partial understanding
            ↓
        more investigation
            ↓
        solution

Sometimes that process takes twenty minutes. Sometimes it takes three days. Sometimes it takes months.

Your ability to remain calm inside an unresolved problem is part of your engineering ability.

If every unresolved state becomes psychologically uncomfortable because you are used to immediate generated answers, that can become a real weakness.

I am not presenting this as proof that AI necessarily makes engineers mentally weaker.

Research on the human effects of AI-assisted work is still developing, and some studies report positive effects on developer well-being and flow.

My point is narrower:

Using AI without discipline can remove exactly the friction from which some forms of technical ability are developed.

Use the tool. Do not surrender the learning process to it.


Ask Yourself One Question

When AI gives you an implementation, ask yourself:

Could I explain why this works without asking the model again?

Then go further:

  • Can I explain every important dependency?
  • Can I explain the failure paths?
  • Can I modify it safely?
  • Can I debug it when the model is unavailable?
  • Can I recognize when the answer is wrong?
  • Can I defend the architectural decision?
  • Can I operate it in production?

If the answer is no, then the AI has probably moved faster than your understanding.

That may be acceptable for a temporary experiment. It is dangerous as a permanent engineering model.


AI-Generated Code Still Requires Ownership

Imagine that an agent generates:

  • service
  • database migration
  • deployment configuration
  • tests
  • monitoring

Everything passes. Three months later, the migration fails under a production edge case and corrupts data.

Who owns the incident? Not the prompt. Not the token stream. Not the model. The engineering organization owns it.

Responsibility cannot be delegated simply by delegating implementation.

This is why high-responsibility environments require human verification.

NIST’s DevSecOps guidance explicitly recommends that AI-generated software content be monitored and validated by humans, with verifiable processes to prevent uncritical acceptance of insecure or non-functional code. That is exactly the right mental model.


AI Access Creates a New Trust Boundary

Another major issue is not code quality at all. It is access.

If you connect an AI system or agent to:

  • private source code;
  • production systems;
  • databases;
  • internal documentation;
  • issue trackers;
  • logs;
  • credentials;
  • deployment systems;
  • customer data

you have created a new trust boundary.

That does not mean that you automatically lose ownership of your intellectual property.

It means that your threat model has changed.

You now need to understand:

  • what information is transmitted;
  • where it is processed;
  • how long it is retained;
  • whether it is used for training;
  • which subprocessors can access it;
  • which jurisdictions are involved;
  • what contractual protections exist;
  • what permissions the agent has;
  • whether actions are logged;
  • whether actions require approval;
  • how secrets are protected;
  • how access is revoked.

This should be treated like any other serious external dependency. The correct question is not:

Is AI secure?

That question is too vague.

The correct questions are:

Which model?
Which provider?
Which contract?
Which data?
Which permissions?
Which environment?
Which retention policy?
Which actions?
Which audit controls?

Least Privilege Applies to Agents Too

An AI agent should not receive more access merely because giving it broad access is convenient.

The same security principles used for human users and services should apply:

  • least privilege;
  • separation of duties;
  • explicit authorization;
  • environment isolation;
  • secret management;
  • audit logging;
  • approval gates.

For example:

    Read repository

does not automatically imply:

    Write repository

and:

    Write repository

does not automatically imply:

    Deploy production

and:

    Inspect database schema

does not automatically imply:

    Modify production data

Tool capability should never be confused with appropriate permission.


AI Can Increase the Value of Strong Engineers

There is another side of this discussion that should not be ignored.

AI does not only help beginners.

A strong engineer can use AI as a multiplier.

Why?

Because the engineer already has the ability to:

  • specify the problem precisely;
  • recognize incorrect assumptions;
  • review generated code quickly;
  • reject poor architecture;
  • test adversarial cases;
  • ask better questions;
  • integrate the result into a larger system.

The AI can therefore remove lower-value mechanical work while the engineer remains responsible for higher-value decisions.

This is one reason I do not see AI as an argument against learning fundamentals.

I see the opposite.

The better your fundamentals are, the more safely and effectively you can use increasingly powerful tools.


The Skill Moves Upward

As tools improve, the value distribution changes.

At one time, manually writing assembly code was a large part of programming.

Higher-level languages automated much of that work.

Compilers became better.

Frameworks automated recurring application structures.

Cloud platforms automated infrastructure primitives.

None of these changes eliminated the need for engineering.

They moved the abstraction boundary.

AI may do the same thing to a much larger portion of implementation work.

The consequence may be that more value moves toward:

  • specification;
  • architecture;
  • integration;
  • validation;
  • security;
  • systems thinking;
  • domain knowledge;
  • technical judgment.

That does not guarantee that every current job survives unchanged.

It means that learning only the easiest mechanical layer of development is an increasingly weak career strategy.


Do Not Become an AI Operator Who Cannot Engineer

There is a new failure mode that I believe students should take seriously.

A person can become highly skilled at:

  • prompting;
  • selecting models;
  • running agents;
  • copying generated patches;
  • iterating until tests pass

while remaining unable to independently explain the system being produced.

That can create an illusion of rapid progress. The repository becomes larger. Features appear. Tests become green.

But the person’s internal technical model remains shallow.

Eventually something happens outside the patterns the agent can resolve immediately.

At that point, the apparent productivity collapses.

Your goal should be [Engineer using AI] not AI operator dependent on generated engineering.

The difference is ownership of understanding.


A Practical Rule for Learning

During the learning phase, I recommend dividing AI use into three categories.

Green — Usually Safe

AI can help you with:

  • explaining terminology;
  • generating additional examples;
  • checking grammar;
  • creating practice questions;
  • comparing approaches after you have attempted the problem;
  • explaining compiler errors;
  • finding relevant documentation;
  • producing boilerplate you already understand.

Yellow — Use Carefully

AI can help with:

  • debugging;
  • refactoring;
  • test generation;
  • architecture brainstorming;
  • unfamiliar libraries;
  • performance ideas.

But you should independently verify the result.

Red — Do Not Delegate Your Learning

Be very careful about asking AI to:

  • solve every task before you try;
  • design complete systems you do not understand;
  • make security decisions for you;
  • perform destructive operations without review;
  • choose architecture only because the answer sounds convincing;
  • write code that you cannot explain;
  • replace your own investigation every time you become stuck.

The purpose of learning is not to produce the final file.

The purpose is to build the mind that can produce, understand, and repair the file.


What I Expect From You in This Task Library

Using AI while working through this Task Library is not automatically wrong.

But the purpose of the Task Library is to develop your own technical capability.

If you give every task to an agent and copy the result, you can technically complete hundreds of tasks while learning very little.

That defeats the entire purpose.

When you use AI, I want you to be able to answer:

  • What did the model contribute?
  • What did you contribute?
  • What did you verify?
  • What did you reject?
  • What did you change?
  • Why does the final implementation work?
  • What would you do if the model were unavailable?

The final question is especially important.

If removing access to an AI tool completely removes your ability to continue working, you have created dependency rather than leverage.


The Goal Is Leverage Without Dependency

This is the balance I recommend:

Knowledge + Experience + Judgment + AI = Leverage

Not:

AI = Substitute for knowledge

AI should allow a capable engineer to explore more possibilities, automate repetitive work, access information faster, and reduce mechanical effort.

It should not remove the requirement to think.


Public Research: What the Evidence Currently Suggests

The current evidence is not consistent with either extreme claim:

AI does nothing useful.

or:

AI has already made software engineers obsolete.

Research currently shows a more complicated picture.

AI Can Substantially Accelerate Bounded Tasks

A controlled GitHub Copilot experiment reported a large speedup on a specific JavaScript HTTP-server implementation task.

This demonstrates that AI assistance can have very real productivity value.

AI Can Also Slow Experienced Engineers in Some Environments

METR’s early-2025 randomized study found experienced developers were slower with AI on the mature open-source repositories studied.

The same researchers later reported that newer tools probably improved productivity, but measurement became difficult because developers increasingly did not want to work without AI.

Benchmark Performance Can Overstate Production Usefulness

METR’s 2026 maintainer-review experiment found a substantial gap between automated SWE-bench success and whether maintainers would merge the resulting patches.

Agents Can Already Complete Some Substantial Long-Horizon Implementation Tasks

MirrorCode provides evidence that modern agents can autonomously perform some implementations estimated to require humans weeks.

This means we should expect capability to continue expanding.

Organizational Quality Still Matters

DORA’s research suggests AI behaves as an amplifier of the engineering environment around it.

Strong processes can capture more benefit.

Weak processes can produce more instability.

Developers Use AI Heavily but Do Not Blindly Trust It

The Stack Overflow 2025 survey shows widespread adoption together with substantial distrust of output accuracy and significant concern around AI-agent security and privacy.

These findings are not contradictory.

They describe a technology that is already extremely useful, improving rapidly, and still dependent on careful human integration.


Final Perspective

I do not recommend fearing AI. I also do not recommend worshipping it. Learn to use it.

Learn where it is strong. Learn where it is weak.

Learn how to verify it. Learn how to restrict it.

Learn how to measure whether it is actually helping you.

And most importantly:

build enough knowledge that you remain the engineer in the relationship.

The future of software engineering will almost certainly involve increasingly capable AI systems.

That makes deep understanding more valuable, not less.

The person who understands the system can use the tool.

The person who only understands the tool remains dependent on whatever the tool produces.

My recommendation is simple:

Use AI to increase the reach of your engineering ability.
Do not use AI to avoid developing engineering ability.


Public Research and References

The sources below are included because AI capability and AI-assisted development are changing rapidly. Results should always be interpreted in the context of the models, tools, tasks, and time period studied.

  1. DORA — State of AI-assisted Software Development 2025
    AI is described primarily as an amplifier of existing organizational strengths and weaknesses.
    https://dora.dev/research/2025/dora-report/

  2. DORA — Balancing AI Tensions: Moving from AI Adoption to Effective SDLC Use (2026)
    Discusses the trade-off between faster generation and increased auditing and verification work.
    https://dora.dev/insights/balancing-ai-tensions/

  3. DORA — Impact of Generative AI in Software Development
    Reports individual productivity and well-being benefits alongside software-delivery trade-offs.
    https://dora.dev/ai/gen-ai-report/report/

  4. METR — Measuring the Impact of Early-2025 AI on Experienced Open-Source Developer Productivity
    Randomized study in which experienced developers working on mature repositories took approximately 19% longer when AI tools were allowed.
    https://metr.org/blog/2025-07-10-early-2025-ai-experienced-os-dev-study/

  5. METR — We Are Changing Our Developer Productivity Experiment Design (2026)
    Follow-up explaining why newer data suggests greater AI productivity benefits but cannot provide a clean estimate because of selection effects.
    https://metr.org/blog/2026-02-24-uplift-update/

  6. METR — Many SWE-bench-Passing PRs Would Not Be Merged into Main (2026)
    Maintainer review showed a substantial gap between automated benchmark success and merge-quality code.
    https://metr.org/notes/2026-03-10-many-swe-bench-passing-prs-would-not-be-merged-into-main/

  7. Epoch AI / METR — MirrorCode: Evidence AI Can Already Do Some Weeks-Long Coding Tasks (2026)
    Demonstrates that frontier agents can autonomously complete some substantial, checkable software reimplementation tasks.
    https://epoch.ai/publications/mirrorcode-preliminary-results

  8. Stack Overflow — 2025 Developer Survey: AI
    Reports widespread AI adoption alongside low trust, accuracy concerns, debugging friction, and security/privacy concerns.
    https://survey.stackoverflow.co/2025/ai

  9. Peng, Kalliamvakou, Cihon, Demirer — The Impact of AI on Developer Productivity: Evidence from GitHub Copilot
    Controlled experiment reporting a 55.8% completion-time improvement on a bounded JavaScript implementation task.
    https://arxiv.org/abs/2302.06590

  10. Liu et al. — Lost in the Middle: How Language Models Use Long Contexts
    Research demonstrating that having a long context window does not guarantee equally reliable use of all information within it.
    https://arxiv.org/abs/2307.03172

  11. NIST — Secure Software Development, Security, and Operations Practices: The Role of AI in Software Development
    Recommends human monitoring, validation, and scrutiny of AI-generated software content.
    https://pages.nist.gov/nccoe-devsecops/introduction.html

  12. NIST — Artificial Intelligence Risk Management Framework: Generative Artificial Intelligence Profile
    Provides a risk-management framework covering security, privacy, reliability, intellectual property, and other generative-AI risks.
    https://www.nist.gov/publications/artificial-intelligence-risk-management-framework-generative-artificial-intelligence

  13. European Commission — General-Purpose AI Obligations Under the AI Act
    Describes GPAI-provider obligations including copyright policies and publication of training-content summaries.
    https://digital-strategy.ec.europa.eu/en/factpages/general-purpose-ai-obligations-under-ai-act

  14. U.S. Copyright Office — Artificial Intelligence Study
    Public work covering AI-generated outputs, copyrightability, digital replicas, and generative-AI training.
    https://www.copyright.gov/policy/artificial-intelligence/

Preserve The Engineering Development Chain

We Must Preserve the Engineering Development Chain

There is another problem that is rarely discussed seriously enough.

Software engineering has a development chain.

That chain looks approximately like this:

          Training
              ↓
          Internship
              ↓
        Junior Engineer
              ↓
        Mid-Level Engineer
              ↓
        Senior Engineer
              ↓
        Technical Lead      
              ↓
        Technical Principal      
              ↓
          Architect

You cannot permanently remove one part of that chain and expect the rest of it to continue existing. There are no new architects without new senior engineers. There are no new senior engineers without new junior engineers. And there is no architect in the world who somehow appeared at the architect level without previously going through earlier stages of development. Every architect was once a junior. Every senior engineer was once a junior. Every experienced engineer once had questions that today may appear completely basic. That is how technical development works.

A junior engineer is not simply a cheaper version of a senior engineer.

A junior engineer is a future senior engineer in development.

If companies stop hiring and training junior engineers because they believe AI can perform the simpler parts of their work, they are not solving the problem. They are postponing it. For several years, everything may appear completely fine. Existing senior engineers still exist. Architects still exist. Technical leads still exist.

The organization continues working. But then time passes. People leave. People retire. People move into management. People change industries. Some stop working completely. And suddenly the organization discovers that it has created no new generation capable of replacing them.

The pipeline has been broken. The situation then looks like this:

        Existing Architects
        Existing Seniors
                ↓
                ↓
                ↓
        No new Juniors
                ↓
        No new Mid-Level Engineers
                ↓
        No new Seniors
                ↓
        No new Architects

You cannot solve that problem later by simply deciding:

Now we need more senior engineers.

Senior engineers are not manufactured in six months.

Architects are not created by changing someone’s job title.

Technical depth requires years of:

  • implementation;
  • debugging;
  • failure;
  • production incidents;
  • bad decisions;
  • good decisions;
  • refactoring;
  • migrations;
  • system design;
  • maintenance;
  • learning from more experienced people.

That process cannot begin if there is nobody at the beginning of the chain.


Junior Work Is Part of the Training Process

Another mistake is to look at junior-level work and conclude:

AI can do this task, therefore we no longer need juniors.

That completely misses the point.

Some tasks given to junior engineers are not important only because the organization needs the task completed.

They are important because the engineer needs to go through the process of completing them.

There is a major difference.

A junior may spend hours understanding:

  • why a test fails;
  • how an HTTP request works;
  • why a database query behaves differently;
  • how a race condition appears;
  • why a process cannot open a file;
  • why an interface was designed in a certain way.

A senior engineer may solve the same problem in ten minutes.

An AI agent may generate a working answer in several seconds.

But the junior’s purpose is not merely to compete with either of them on speed.

The junior is building the mental models that will later allow them to solve much larger problems.

If AI removes every small problem before the junior has the opportunity to struggle with it, investigate it, understand it, and finally solve it, then we may be removing part of the mechanism through which future expertise is created.

The output may appear faster.

The engineer may develop more slowly.

Those are two completely different measurements.


Someone Has to Train the Next Generation

Engineering knowledge does not transfer automatically. Someone has to:

  • review code;
  • explain mistakes;
  • show alternatives;
  • explain why a solution is dangerous;
  • demonstrate debugging;
  • teach system thinking;
  • expose less experienced engineers to real problems.

That responsibility usually belongs to people further along the development chain.

A healthy engineering organization therefore has something like:

        Architects
            ↓ mentor and guide
        Senior Engineers
            ↓ mentor and guide
        Mid-Level Engineers
            ↓ help and guide
        Junior Engineers

But knowledge also moves upward. Junior engineers ask questions. They challenge assumptions. They encounter problems from a different perspective. They force experienced engineers to explain decisions that may have become automatic after many years. A healthy technical organization is not simply a collection of isolated experts.

It is a continuous transfer of knowledge between generations of engineers.


AI Cannot Be Allowed to Break That Chain

AI can absolutely become part of that development process. It can:

  • explain concepts;
  • provide additional examples;
  • generate practice tasks;
  • assist debugging;
  • reduce repetitive work;
  • provide another perspective.

But it should not become an excuse for removing the beginning of the engineering career path.

If the industry reaches the conclusion:

        AI can perform junior tasks
                ↓
        therefore we stop hiring juniors

then several years later the same industry may discover:

        we cannot find enough seniors

And several years after that:

        we cannot find enough architects

At that point, the cause should not be difficult to understand. The chain was broken at the beginning.


There Is No Shortcut to Experience

Experience is accumulated. You cannot prompt your way into ten years of production experience.

You cannot skip [Junior/Mid-Level/Senior] and simply become [Architect], because an AI model can generate architecture diagrams and code.

An architect needs to understand the consequences behind those diagrams. A senior engineer needs to recognize why something that appears correct today may create a serious problem two years later. That ability comes from accumulated exposure to real systems.

And that exposure begins somewhere. Usually, it begins with small problems. Junior problems.

Tasks that look insignificant to an architect today but were once necessary steps in that architect’s own development. Do not forget that.

There is no architect who was never a junior.

If we want future senior engineers, we need junior engineers today.

If we want future architects, we need future senior engineers.

If we want software engineering to remain a serious profession, we have to preserve the complete development chain.


AI can become part of that chain. It should not be allowed to destroy it.


Programming and Software Engineering

Programming and software engineering are closely related.

But they are not the same thing. Programming is one part of software engineering.

And this distinction becomes more important as the size, lifetime, complexity, and responsibility of a system increase.

A programmer primarily asks:

How do I make this work?

A software engineer eventually has to ask:

How do I make this work correctly, safely, predictably, maintainably, and as part of a much larger system that will continue changing for years?

Those are different levels of responsibility.


Programming

Programming is the act of creating instructions that a computer can execute.

At its most basic level, programming includes:

  • variables;
  • conditions;
  • loops;
  • functions;
  • data structures;
  • algorithms;
  • file operations;
  • network calls;
  • database operations;
  • error handling;
  • concurrency;
  • interaction with operating systems and libraries.

A programming problem may look like:

Read a file, parse the data, calculate a result, and write the output.

Or:

Accept an HTTP request, validate it, store something in a database, and return a response.

Or:

Sort this collection efficiently.

Or:

Process these jobs concurrently.

The central concern is usually the implementation itself. You are given a problem. You write code that solves it. At that level, success may be defined very simply:

          Input
            ↓
         Program
            ↓
        Expected Output

If the output is correct, the program may be considered successful. For small and isolated problems, that can be completely sufficient.


Software Engineering

Software engineering begins when the problem is no longer only:

Can I make this code work?

The questions become much broader. For example:

  • Should this functionality exist in this service?
  • Who owns this data?
  • What happens if the database is unavailable?
  • What happens if the same request arrives twice?
  • Can the operation be safely retried?
  • What happens under concurrent access?
  • How does the system behave under load?
  • How do we deploy this change?
  • How do we roll it back?
  • How do we migrate old data?
  • How do we maintain backward compatibility?
  • How do we monitor failures?
  • How do we debug the system in production?
  • What happens when requirements change next year?
  • Can another team understand and maintain this?
  • How much will this cost to operate?
  • What security assumptions are we making?

At this point, code becomes only one part of the problem.

Software engineering is concerned with the complete lifecycle of a software system. That includes:

        Requirements
            ↓
        Analysis
            ↓
         Design
            ↓
        Implementation
            ↓
        Testing
            ↓
        Deployment
            ↓
        Operation
            ↓
        Monitoring
            ↓
        Failure handling
            ↓
        Maintenance
            ↓
        Evolution

A programmer can write a correct function.

A software engineer/developer has to understand what that function means inside the system around it.


A Simple Example

Imagine that the requirement is:

Save a user into a database.

At a programming level, the solution may look like this:

        Receive request
            ↓
        Validate fields
            ↓
        INSERT INTO users
            ↓
        Return success

That may work perfectly.

But software engineering starts asking questions that are not visible in that four-step implementation.

For example:

What makes a user unique?
Can two requests create the same user concurrently?
What happens if the client retries the request?
Do we need idempotency?
Which service owns user data?
Can another service modify it?
What happens when the database is temporarily unavailable?
Do we retry?
How many times?
What happens if the database write succeeds
but the response never reaches the client?
How is this audited?
Who is allowed to perform the operation?
How do we migrate the schema later?
How do we delete the data if required?
How do we restore it after failure?

The code may still be only twenty lines.

The engineering problem may be much larger than those twenty lines.


Working Code Is Not Necessarily Good Software

This is one of the first important distinctions you need to understand.

Code can work and still be bad.

A system can produce the correct output and still have serious problems.

For example, it may be:

  • impossible to maintain;
  • insecure;
  • inefficient;
  • tightly coupled;
  • difficult to test;
  • impossible to scale;
  • unreliable;
  • difficult to deploy;
  • difficult to observe;
  • impossible to modify without breaking something else.

The statement:

It works.

is therefore not the end of an engineering discussion.

It is often only the beginning.

A stronger question is:

Under which conditions does it work, and what happens when those conditions are no longer true?


Programming Solves the Immediate Problem

Programming often focuses on the current implementation.

For example:

I need to parse this JSON.
I need to send this HTTP request.
I need to process these records concurrently.
I need to write this file.

Those are valid technical problems.

Software engineering asks how those solutions interact with a larger system.

For example:

Why are we using JSON here?
What is the contract between these services?
What happens if the schema changes?
How do we preserve compatibility?
What happens if one worker fails?
What happens if the file is partially written?
How do we detect corruption?

The programmer is often solving the visible task.

The engineer is also responsible for the consequences around that task.


Software Engineering Is About Trade-Offs

Programming problems often have answers that are clearly correct or incorrect.

Software engineering frequently does not.

Instead, you work with trade-offs.

For example:

Performance
    vs
Readability
Consistency
    vs
Availability
Development speed
    vs
Long-term maintainability
Flexibility
    vs
Complexity
Redundancy
    vs
Cost
Abstraction
    vs
Simplicity

There is rarely one perfect answer.

The correct solution depends on:

  • requirements;
  • constraints;
  • scale;
  • risk;
  • cost;
  • expected lifetime;
  • team structure;
  • operational environment.

That is why experience becomes increasingly important.

Syntax alone cannot teach judgment.


Scale Changes the Nature of the Problem

A program with:

500 lines of code

and an infrastructure with:

20 services
+
multiple databases
+
several environments
+
many teams
+
years of development

are not the same engineering problem at different sizes.

At some point, scale changes the nature of the work itself.

New problems appear:

  • ownership;
  • coordination;
  • contracts between teams;
  • versioning;
  • compatibility;
  • observability;
  • distributed failure;
  • deployment sequencing;
  • data migration;
  • operational responsibility.

The complexity does not increase only because there is more code.

It increases because there are more relationships.

This is extremely important.

A system with ten components does not simply have ten independent things to understand.

Those components may interact in dozens or hundreds of different ways.

That interaction is where much of software engineering begins.


Engineering Includes Failure

Programming education often focuses on the success path.

For example:

Open file
Read file
Process file
Close file

Engineering asks:

What if the file does not exist?
What if permission is denied?
What if the disk is full?
What if the process crashes during the write?
What if two processes modify the file?
What if the data is corrupted?
What if the machine loses power?

Real systems fail.

Networks fail.

Disks fail.

Processes crash.

Dependencies become unavailable.

Humans make mistakes.

Configurations drift.

Certificates expire.

The ability to think about failure is one of the major differences between simply writing code and engineering systems.


Engineering Includes Time

A program may be written once and used once.

Software systems often live for years.

That changes everything.

You now have to think about:

  • upgrades;
  • migrations;
  • compatibility;
  • deprecations;
  • old clients;
  • changing dependencies;
  • new team members;
  • historical data;
  • evolving requirements.

Something that looks elegant today may become a serious problem after five years.

Engineering therefore requires you to think not only about:

Does it work now?

but also:

Can it evolve later?

That is a completely different question.


Engineering Includes Other People

Programming can be an individual activity.

Software engineering usually is not.

A production system may involve:

  • developers;
  • engineers;
  • QA;
  • DevOps;
  • SysOps;
  • security teams;
  • database teams;
  • product teams;
  • architects;
  • operations;
  • customers.

Your code has to coexist with the work of other people.

That means software engineering also includes:

  • documentation;
  • code review;
  • naming;
  • interfaces;
  • standards;
  • communication;
  • predictable behavior.

Code that only its author understands may technically work.

It is still poor engineering if ten other people need to maintain it.


Engineering Includes Responsibility

This is probably the most important distinction.

Programming asks whether the implementation is correct.

Engineering also asks:

Who is responsible when it fails?

If you write software for:

  • banking;
  • healthcare;
  • aviation;
  • electrical infrastructure;
  • authentication;
  • security;
  • large cloud systems

the consequences of bad decisions can become serious.

At that point, software is no longer simply an intellectual exercise.

It has operational consequences.

An engineer must understand that.


Knowing a Programming Language Is Not Software Engineering

Knowing:

Go

or:

Rust

or:

Java

does not automatically make someone a software engineer.

A language is a tool.

The same applies to frameworks.

Knowing:

React
Kubernetes
PostgreSQL
Docker

does not by itself create engineering ability.

Engineering ability appears when you understand:

  • when to use something;
  • why to use it;
  • when not to use it;
  • what consequences it introduces;
  • what alternatives exist.

The difference is significant.

A programmer may know how to use Kafka.

An engineer should also be able to ask:

Do we actually need Kafka?

That question can be more valuable than knowing every Kafka configuration option.


Software Engineering Does Not Mean Writing Less Code

Do not misunderstand this distinction.

Software engineering does not mean that you become “too senior” to write code.

Quite the opposite.

Strong engineering is usually built on strong implementation experience.

It is difficult to make good architectural decisions if you do not understand what those decisions mean at code level.

Drawing:

Service A
    ↓
Queue
    ↓
Service B

takes seconds.

Implementing that correctly may involve:

  • retries;
  • ordering;
  • deduplication;
  • serialization;
  • backpressure;
  • monitoring;
  • dead-letter queues;
  • failure recovery;
  • deployment;
  • schema evolution.

Architecture without implementation understanding is dangerous.


The Progression

A useful way to visualize the difference is through progression.

At the beginning, you may focus primarily on:

How do I write this?

Then:

How do I write this correctly?

Then:

How does this interact with the rest of the application?

Then:

How does this behave inside the service?

Then:

How does this service behave inside the system?

And eventually:

Should this system be designed this way at all?

That progression is software engineering.


Programming Is Necessary

None of this means programming is somehow less important.

You cannot become a strong software engineer without knowing how software is actually built.

Programming is the foundation.

If you cannot:

  • write code;
  • debug code;
  • read code;
  • understand memory;
  • understand concurrency;
  • understand data structures;
  • understand networking;
  • understand operating-system behavior

then higher-level engineering discussions quickly become theoretical.

Software engineering builds on programming. It does not replace it.


The Simplest Difference

If I had to reduce the entire distinction to one statement, it would be this:

Programming is primarily about creating software that works. Software engineering is about creating software that continues to work as the system, requirements, environment, teams, and time around it change.

That difference is enormous.

Do not rush to call yourself an engineer because you learned a programming language. And do not think that programming is somehow beneath engineering. They are parts of the same progression:

  • First, learn to make things work.
  • Then learn why they work.
  • Then learn how they fail.
  • Then learn how they interact with other systems.
  • Then learn how to maintain them.
  • Then learn how to design them for change.
  • Then learn how to make decisions that other people can depend on.
  • That is the path from programming toward software engineering.

Programming Languages Are Not Interchangeable

You will often hear the following statement:

The programming language does not matter. A programming language is only a tool.

The second sentence is partially true.

A programming language is a tool.

But the conclusion that the choice of language therefore does not matter is completely wrong.

Tools are chosen according to the problem.

A hammer and a microscope are both tools.

That does not make them interchangeable.

Programming languages are the same.

The language you choose affects:

  • performance;
  • memory consumption;
  • memory safety;
  • concurrency;
  • latency;
  • startup time;
  • runtime requirements;
  • deployment;
  • binary size;
  • portability;
  • hardware access;
  • operating-system integration;
  • development speed;
  • type safety;
  • failure modes;
  • maintainability;
  • available libraries;
  • ecosystem maturity.

Different languages were designed around different priorities.

Because of that, there is no serious engineering environment in which language choice is completely irrelevant.


The Problem Determines the Language

If you are building:

a small internal automation script

your priorities may be completely different from those of someone building:

a database engine

or:

an operating-system component

or:

a real-time rendering engine

or:

a distributed infrastructure platform processing millions of concurrent operations

Using the same language for all of those problems simply because:

language does not matter

is not technical flexibility.

It is refusing to understand the problem.

The correct approach is:

Problem
   ↓
Requirements
   ↓
Constraints
   ↓
Runtime characteristics
   ↓
Operational environment
   ↓
Programming language

Not:

I know language X
   ↓
therefore every problem will be solved with X

Low-Level and High-Level Programming Languages

The distinction between low-level and high-level programming languages is important because different languages expose very different levels of control over the machine, runtime, memory, and execution model.

This is not a question of which group is better.

It is a question of:

How much responsibility remains in the hands of the programmer, and how much is handled by the language, compiler, runtime, virtual machine, or framework?

There is also one important thing to understand:

Low-level and high-level are not perfectly separated categories. They exist on a spectrum.

Some languages expose direct control over memory and hardware.

Some hide almost all of that complexity.

Some sit somewhere in between.

The engineering value comes from understanding what each language gives you, what it hides from you, and where those trade-offs become useful or dangerous.


Low-Level Languages

Low-level languages give the programmer much more direct control over:

  • memory;
  • pointers;
  • allocation;
  • deallocation;
  • data layout;
  • resource lifetime;
  • system calls;
  • operating-system interfaces;
  • hardware interaction;
  • synchronization;
  • CPU-level behavior.

With that control comes responsibility.

You gain the ability to optimize deeply.

You also gain the ability to create very serious failures.

The clearest examples in modern software engineering are:

  • C;
  • C++;
  • Rust.

C

C is one of the foundational low-level languages of modern computing.

It gives the programmer direct control over:

  • memory;
  • pointers;
  • data representation;
  • system calls;
  • hardware access;
  • resource management.

C is heavily used in:

  • operating systems;
  • kernels;
  • device drivers;
  • firmware;
  • embedded systems;
  • networking stacks;
  • database engines;
  • compilers;
  • runtime libraries;
  • hardware interfaces.

Its strength is simplicity and directness.

There is very little between your code and the machine.

Its weakness is the same thing.

C allows you to create extremely efficient software, but it also allows you to create:

  • buffer overflows;
  • use-after-free bugs;
  • memory corruption;
  • undefined behavior;
  • security vulnerabilities.

C gives you control.

It does not protect you from incorrect use of that control.


C++

C++ keeps the low-level capabilities of C while adding much more abstraction.

It allows you to work with:

  • pointers;
  • manual memory management;
  • RAII;
  • templates;
  • classes;
  • generic programming;
  • metaprogramming;
  • low-level optimization;
  • high-performance libraries.

C++ is especially strong in areas where performance, latency, graphics, or hardware control are critical.

Typical examples include:

  • game engines;
  • graphics engines;
  • browsers;
  • rendering systems;
  • simulation;
  • computer vision;
  • databases;
  • high-frequency trading;
  • CAD systems;
  • audio engines;
  • native desktop applications.

C++ remains especially important in graphics because the surrounding ecosystem has decades of maturity.

If you work with:

  • Vulkan;
  • DirectX;
  • OpenGL;
  • rendering engines;
  • physics engines;
  • graphics drivers;
  • large native game engines

C++ remains one of the strongest choices available.


Rust

Rust belongs to the systems-programming category.

It is designed to provide performance and low-level control comparable to C and C++, while preventing many classes of memory-safety bugs at compile time.

Rust provides:

  • native performance;
  • memory safety without a garbage collector;
  • explicit ownership;
  • predictable resource lifetime;
  • strong type safety;
  • strong concurrency guarantees;
  • low-level control.

Rust is increasingly used for:

  • system services;
  • high-performance backend components;
  • distributed infrastructure;
  • networking;
  • security-sensitive software;
  • storage systems;
  • command-line tools;
  • WebAssembly;
  • embedded systems;
  • developer tooling.

Rust is especially attractive when the system requires:

        performance
        +
        memory safety
        +
        predictable resource control
        +
        concurrency

The cost is complexity.

Rust forces the programmer to understand ownership, borrowing, lifetimes, and resource relationships that many other languages hide.

That can make development harder at the beginning.

But it can also eliminate entire classes of runtime failures.


High-Level Languages

High-level languages hide more machine-level complexity from the programmer.

Instead of constantly thinking about:

  • raw memory addresses;
  • manual allocation;
  • pointer arithmetic;
  • resource lifetime;
  • machine instructions;

the programmer can focus more on:

  • application logic;
  • business rules;
  • service communication;
  • data processing;
  • user-facing behavior;
  • product functionality.

High-level languages often rely on:

  • garbage collectors;
  • virtual machines;
  • managed runtimes;
  • interpreters;
  • runtime type systems;
  • large standard libraries.

Examples include:

  • Go;
  • Java;
  • C#;
  • Python;
  • JavaScript.

These languages are not identical.

Some are much closer to systems programming than others.


Go

Go is a high-level compiled language with a strong systems and infrastructure orientation.

It was designed around:

  • simplicity;
  • networking;
  • concurrency;
  • fast compilation;
  • easy deployment;
  • server-side software;
  • infrastructure development.

Go provides:

  • garbage collection;
  • native compilation;
  • goroutines;
  • channels;
  • strong networking support;
  • a large standard library;
  • easy single-binary deployment.

Go is extremely common in:

  • backend services;
  • distributed systems;
  • cloud infrastructure;
  • microservices;
  • networking systems;
  • DevOps tooling;
  • observability platforms;
  • infrastructure automation;
  • control planes.

Go became one of the dominant languages of the cloud-native ecosystem because it gives developers a very strong balance between:

        development speed
        +
        concurrency
        +
        networking
        +
        deployment simplicity

Compared with Rust:

           Go
            ↓
        simpler development
        garbage collection
        excellent networking
        fast iteration
        easy deployment

while:

          Rust
            ↓
        more control
        no garbage collector
        stronger memory guarantees
        predictable resource lifetime
        lower-level systems work

Both are extremely strong for modern infrastructure. They simply solve the problem from different positions on the abstraction spectrum.


Java

Java is a high-level language built around the Java Virtual Machine.

It provides:

  • garbage collection;
  • strong static typing;
  • mature concurrency support;
  • JIT compilation;
  • large library ecosystems;
  • mature profiling;
  • mature observability;
  • portability through the JVM.

Java is extremely common in:

  • enterprise systems;
  • banking;
  • financial infrastructure;
  • large backend systems;
  • distributed systems;
  • data platforms;
  • messaging systems;
  • long-lived business applications.

Its strength comes from:

  • ecosystem maturity;
  • runtime optimization;
  • tooling;
  • long-term stability;
  • massive industry adoption.

C#

C# is a high-level language primarily associated with the .NET ecosystem.

It provides:

  • garbage collection;
  • strong static typing;
  • async programming;
  • mature tooling;
  • a cross-platform runtime;
  • a strong enterprise ecosystem.

C# is widely used for:

  • enterprise applications;
  • backend services;
  • APIs;
  • cloud systems;
  • business applications;
  • desktop applications;
  • Microsoft-oriented infrastructure;
  • game development through Unity.

Python

Python is a very high-level language designed around:

  • simplicity;
  • readability;
  • development speed;
  • rapid experimentation.

Python hides a large amount of low-level system complexity. It is especially strong for:

  • automation;
  • scripting;
  • data science;
  • machine learning;
  • AI;
  • scientific computing;
  • testing;
  • backend applications;
  • data processing;
  • prototyping.

Python is not primarily chosen for raw execution performance.

Its value comes from how quickly developers can build, test, and change things.

An important detail is that much of the high-performance work in the Python ecosystem is actually executed by native code written in:

  • C;
  • C++;
  • CUDA;
  • Rust;
  • Fortran.

Python often acts as the high-level control layer. That is a perfect example of how different levels of programming languages can work together.


JavaScript

JavaScript is a high-level language that became dominant because it is the native programming language of the web browser.

Its strongest areas include:

  • frontend development;
  • browser applications;
  • interactive websites;
  • web interfaces;
  • Node.js backend services;
  • full-stack web applications;
  • desktop applications built on web technologies.

Modern JavaScript runtimes are highly optimized.

But JavaScript is still designed around a completely different problem space from C, C++, or Rust.

JavaScript is an obvious choice for:

browser application

It is not an obvious choice for:

operating-system kernel

The fact that both are programming languages does not make them interchangeable.


Practical Comparison

A simplified overview looks like this:

LanguageApproximate LevelMemory ModelStrongest Areas
CLow-levelManualKernels, drivers, firmware, embedded, runtimes
C++Low-level / systemsManual / RAIIGames, graphics, browsers, databases, trading
RustLow-level / systemsOwnership modelInfrastructure, networking, security, storage
GoHigh-level / systems-orientedGarbage collectedCloud, distributed systems, backend, DevOps
JavaHigh-levelGarbage collected JVMEnterprise, banking, backend, distributed systems
C#High-levelGarbage collected .NETEnterprise, backend, cloud, desktop, Unity
PythonVery high-levelManaged runtimeAI, ML, data, scripting, automation
JavaScriptHigh-levelManaged runtimeFrontend, browser, Node.js, web applications

Low-Level Does Not Mean Better

One of the worst conclusions you can make is:

Low-level languages are better because they are closer to the machine.

No.

They are better when the problem requires that level of control.

If you need to write:

a short automation script

using C++ may be completely pointless.

Python may be the better engineering choice.

If you are writing:

a high-performance storage engine

Python may be a poor choice.

C++, Rust, or another systems language may make much more sense.

The problem determines the language.


High-Level Does Not Mean Weak

The opposite mistake is also wrong.

High-level languages are not automatically weak.

Java powers enormous financial and enterprise systems.

Go powers a huge part of modern cloud infrastructure.

C# powers large enterprise platforms.

Python powers a large part of the AI and data ecosystem.

JavaScript powers almost the entire interactive web.

High-level languages remove certain responsibilities from the programmer so that effort can be spent elsewhere.

That is often exactly what you want.


The Real Difference

The simplest way to understand the distinction is:

Low-level languages
        ↓
more machine control
more explicit resource management
more responsibility
more opportunity for optimization

while:

High-level languages
        ↓
more abstraction
more runtime assistance
less manual resource management
faster application development

Neither side wins universally. The question is always:

What does this system actually require?

That is the engineering decision.

Clean Code and a Quality Engineering Environment

Clean and high-quality software is not defined only by whether the code compiles or whether the application currently works.

A serious engineering environment is defined by the complete system around the code.

That includes:

  • clean and understandable code;
  • flexibility;
  • modularity;
  • scalability;
  • level of testing;
  • migrations;
  • backup and recovery;
  • automation processes;
  • CI/CD;
  • documentation.

A system can be technically functional and still be a terrible engineering environment.

The goal is not only:

Make it work.

The goal is:

Make it understandable, changeable, testable, recoverable, deployable, scalable, and maintainable.


Clean and Understandable Code

Clean code is not code that looks clever.

It is code that other people can understand.

A clean codebase should make it relatively easy to answer:

  • What does this component do?
  • Why does it exist?
  • Where does this data come from?
  • What depends on this function?
  • What can fail here?
  • What happens when it fails?
  • Where should I make a change?
  • What should not be changed?

Clean code usually has:

  • clear naming;
  • small and understandable responsibilities;
  • predictable structure;
  • limited hidden behavior;
  • minimal unnecessary abstraction;
  • consistent error handling;
  • clear dependencies;
  • understandable control flow.

A function should not require archaeology.

A developer should not need hours to understand why a variable exists.

Complex systems are already difficult enough.

The code should not make them more difficult.

Clean code is not about style for the sake of style.

It is about reducing unnecessary mental load.


Flexibility

Flexibility means that the system can absorb change without requiring destructive rewrites:

  • Requirements change.
  • Business rules change.
  • Protocols change.
  • Dependencies change.
  • Infrastructure changes.
  • A rigid system turns every change into a large refactor.
  • A flexible system allows change to remain local.

For example:

new storage backend

should not require rewriting the entire application.

new authentication provider

should not require changing every service.

Flexibility comes from:

  • good boundaries;
  • clear interfaces;
  • limited coupling;
  • replaceable components;
  • separation of responsibilities.

The goal is not infinite abstraction.

The goal is controlled change.


Modularity

Modularity means splitting a system into components with clear responsibilities.

A good module should have:

  • a clear purpose;
  • a clear interface;
  • controlled dependencies;
  • limited knowledge of other modules.

Bad modularity looks like:

        Module A
        depends on
        Module B
        depends on
        Module C
        depends on
        Module A

Everything knows about everything.

At that point, the system is not modular.

It is only divided into files.

Good modularity allows you to:

  • change one part without breaking everything;
  • test components independently;
  • replace components;
  • understand boundaries;
  • assign ownership.

A thousand directories do not automatically create modularity.

The boundaries must be real.


Scalability

Scalability means that the system can handle growth without collapsing. Growth may mean:

  • more users;
  • more requests;
  • more data;
  • more services;
  • more teams;
  • more regions;
  • more infrastructure.

Scalability is not only about CPU performance. A system may scale technically and still fail operationally or organizationally. For example:

100 services

are useless if every small change requires coordination between thirty teams.

Scalability includes:

  • technical scalability;
  • operational scalability;
  • organizational scalability.

You should think about:

  • horizontal scaling;
  • vertical scaling;
  • bottlenecks;
  • database limits;
  • network limits;
  • lock contention;
  • queue behavior;
  • storage growth;
  • deployment complexity;
  • team ownership.

A scalable system is not simply a fast system. It is a system that can grow without becoming unmanageable.


Level of Testing

Testing is one of the clearest indicators of engineering maturity. A system without serious testing forces every change to depend on hope. Testing should exist on several levels.

Unit Tests

Unit tests validate isolated pieces of logic.

They should be:

  • fast;
  • deterministic;
  • easy to run;
  • focused.

Integration Tests

Integration tests validate that multiple components work together.

Examples include:

  • service and database;
  • service and message broker;
  • client and API;
  • storage and migration logic.

End-to-End Tests

End-to-end tests validate complete workflows.

They answer:

Does the system actually work from the user’s perspective?

Regression Tests

Regression tests protect against bugs that have already happened once.

If a production bug is fixed and no regression test is added, the organization has learned very little from that incident.

Performance Tests

Performance tests validate:

  • latency;
  • throughput;
  • resource consumption;
  • behavior under load.

Failure Tests

Serious systems should also test failure.

For example:

  • dependency unavailable;
  • network timeout;
  • partial response;
  • corrupted data;
  • process crash;
  • retry behavior.

A high-quality engineering environment does not test only the success path.

It tests how the system behaves when reality becomes ugly.


Migrations

Systems evolve. Schemas change. Data formats change. Protocols change. APIs change. Infrastructure changes. Migration strategy is therefore part of engineering.

A good migration process should consider:

  • backward compatibility;
  • rollback;
  • partial deployment;
  • old clients;
  • data conversion;
  • versioning;
  • failure recovery.

One of the worst migration strategies is to deploy everything at once and hope for the best.

A better migration may look like:

        introduce new schema
                ↓
        support old and new format
                ↓
          migrate data
                ↓
          move clients
                ↓
             verify
                ↓
        remove old path

Migration is not some maintenance task that happens after engineering. Migration is engineering.


Backup and Recovery

A backup that has never been restored is not a proven backup.

A backup strategy should answer:

  • What is backed up?
  • How often?
  • Where is it stored?
  • How long is it retained?
  • Is it encrypted?
  • Who can access it?
  • Can it actually be restored?
  • How long does restoration take?

Two important concepts are:

Recovery Point Objective

How much data can we afford to lose?

Recovery Time Objective

How long can the system remain unavailable?

A production system needs both backup and recovery planning.

The worst time to discover that your backup process is broken is after the primary database has already been lost.


Automation Processes

Repeated manual work creates repeated human error.

If a process is:

  • predictable;
  • repetitive;
  • frequent;
  • important;

it should probably be automated.

Examples include:

  • builds;
  • tests;
  • packaging;
  • artifact generation;
  • versioning;
  • deployments;
  • environment provisioning;
  • database migrations;
  • security checks;
  • backups;
  • report generation.

Automation should reduce:

        manual steps
        +
        human error
        +
        inconsistent execution

The goal is not to automate everything blindly.

The goal is to automate processes where repetition creates unnecessary risk.


CI/CD

CI/CD is not simply:

Push code and automatically deploy production.

That is a shallow interpretation.

Continuous Integration should continuously validate changes.

A CI pipeline may include:

        checkout
            ↓
        dependency validation
            ↓
          build
            ↓
        unit tests
            ↓
        integration tests
            ↓
        static analysis
            ↓
        security checks
            ↓
        artifact creation

Continuous Delivery or Continuous Deployment adds controlled release processes.

That may include:

        artifact verification
                ↓
        staging deployment
                ↓
        smoke tests
                ↓
        migration checks
                ↓
        production deployment
                ↓
        health verification
                ↓
        rollback if required

A good CI/CD system should make deployments:

  • repeatable;
  • predictable;
  • traceable;
  • reversible.

The goal is to remove improvisation from the release process.


Documentation

Documentation is part of the system.

It is not decoration around the system.

A codebase without documentation forces knowledge to exist only inside people’s heads.

That is dangerous.

Documentation should exist on several levels.

Code Documentation

Explains:

  • non-obvious behavior;
  • public interfaces;
  • important invariants;
  • complex algorithms.

Service Documentation

Explains:

  • what the service does;
  • how to configure it;
  • dependencies;
  • APIs;
  • deployment;
  • monitoring;
  • failure behavior.

Architecture Documentation

Explains:

  • system boundaries;
  • service relationships;
  • data flows;
  • important technical decisions;
  • security boundaries.

Operational Documentation

Explains:

  • deployment;
  • recovery;
  • incident procedures;
  • backup restoration;
  • maintenance.

Documentation should answer one very simple question:

If the person who built this system disappears tomorrow, can someone else understand, operate, and continue developing it?

If the answer is no, the system has a knowledge problem.


These Properties Are Connected

These qualities do not exist independently.

For example:

        poor modularity
                ↓
        difficult testing
                ↓
        dangerous migrations
                ↓
        fragile CI/CD
                ↓
        fear of deployment

Or:

        poor documentation
                ↓
        slow onboarding
                ↓
        incorrect changes
                ↓
        production incidents

Or:

        manual processes
                ↓
        inconsistent execution
                ↓
        human error
                ↓
        unreliable releases

Engineering quality is the result of the complete environment.


A Useful Quality Model

You can think about a mature engineering environment like this:

        Clean Code
            +
        Flexibility
            +
        Modularity
            +
        Scalability
            +
        Testing
            +
        Migration Strategy
            +
        Backup and Recovery
            +
        Automation
            +
        CI/CD
            +
        Documentation
            =
        Maintainable Engineering Environment

Every missing part increases risk.


Final Perspective

A professional engineering environment should not depend on heroics.

It should not require one specific person to remember everything.

It should not depend on manual rituals that only one engineer understands.

It should not create fear every time something is deployed.

The system should be designed so that:

  • code is understandable;
  • components are isolated;
  • change is controlled;
  • growth is possible;
  • tests provide confidence;
  • migrations are planned;
  • data can be recovered;
  • repetitive work is automated;
  • releases are predictable;
  • knowledge is documented.

That is the difference between:

software that currently works

and:

an engineering environment that can survive change

Getting Started

This section explains how to approach and work with the exercises in the Programming Task Library.

The library contains two main types of exercises:

  • Algorithms — focused problems with defined inputs, processing requirements, and expected outputs.
  • Modeling — larger scenarios focused on data modeling, relationships, behavior, validation, and system design.

The tasks were originally designed for Go and Rust, but most exercises can be implemented in other programming languages as well.

Before You Start

Do not begin by immediately writing code.

First, analyze the task and make sure you understand:

  1. What data is provided as input?
  2. What result is expected?
  3. What conditions must be satisfied?
  4. What edge cases may exist?
  5. What validation is required?
  6. Are there restrictions on the implementation?
  7. Can the problem be divided into smaller operations?

For more complex tasks, especially modeling exercises, draw the relationships, execution flow, or possible outcomes before starting the implementation.

The goal is to understand the problem before choosing the solution.

Solving Algorithm Tasks

Algorithm tasks usually define:

  • input data
  • one or more function arguments
  • processing rules
  • expected return values
  • example cases
  • implementation constraints

Read the complete task before implementing the function.

Some requirements may depend on several conditions at the same time.

When examples are provided, use them to understand the expected behavior of the implementation.

Function Signatures

Function names and signatures shown in the tasks should be treated as part of the exercise when they are explicitly specified.

For example:

FindAllGreaterThen(data, 4.8)

or:

CreateGroups(list, 30, 90, 40, 70)

The exact type definitions and signatures will be shown on individual task pages when they are part of the original task specification.

Solving Modeling Tasks

Modeling tasks are intentionally broader than algorithm tasks.

Instead of implementing only a single function, you may need to design multiple structures and define relationships between them.

Before implementing a modeling task, identify the main entities in the scenario.

Then determine:

  • what data belongs to each entity
  • how entities are related
  • what operations are required
  • what validation rules exist
  • what information must be searchable or queryable
  • how data should be loaded, stored, or transformed

Do not create the complete model immediately.

Start with the domain and relationships, then build the implementation around them.

Validation and Error Handling

When a task explicitly requires validation or error handling, those requirements are part of the exercise and should not be ignored.

Consider situations such as:

  • invalid input values
  • malformed data
  • failed type conversions
  • missing values
  • unsupported operators or options
  • invalid ranges
  • inconsistent input collections

The exact validation requirements depend on the individual task.

Do not assume additional restrictions unless they are stated or logically required by the task.

Expected Results

Many tasks provide one or more example inputs together with an expected result.

For example:

Input:
data = [[3.2, 5.4], [6.3, 1.4], [2.5, 6.5]]
threshold = 4.8

Expected Result:
[
    {5.4, 1, 2},
    {6.3, 2, 1},
    {6.5, 3, 2}
]

Your implementation should produce behavior consistent with the examples and requirements described by the task.

Examples should be treated as reference cases, not as the only possible inputs.

Implementation Restrictions

Always check whether a task or an entire group defines additional implementation restrictions.

Some exercises intentionally remove convenient language features in order to practice implementing the underlying algorithm manually.

For example, Algorithm Group 5 prohibits the use of built-in functions from the language core.

Such restrictions are part of the exercise.

Performance and Resource Usage

A correct result does not automatically mean that an implementation is efficient.

When solving a task, consider:

  • time complexity
  • memory usage
  • unnecessary allocations
  • repeated iterations
  • unnecessary data copies
  • appropriate data structures
  • opportunities to simplify processing

You do not always need the theoretically fastest possible implementation.

The objective is to understand the trade-offs and avoid unnecessary work.

Testing

Use the examples provided by each task as initial test cases.

When appropriate, also test additional cases that may expose problems in the implementation.

Depending on the task, these may include:

  • empty inputs
  • boundary values
  • duplicate values
  • invalid values
  • values outside expected ranges
  • single-element collections
  • large inputs

For modeling tasks, testing may also include validation of relationships, queries, state changes, and data-loading behavior.

If a task explicitly defines testing requirements, those requirements take priority.

Language Choice

Go, Rust and C++ are the primary languages associated with this library.

You are free to solve the exercises using either language.

Unless a task explicitly depends on a particular language feature, another programming language may also be used.

The important part is preserving the behavior and constraints defined by the task.

A practical workflow for each exercise is:

        Read
          ↓
        Analyze
          ↓
        Model the problem
          ↓
        Identify edge cases
          ↓
        Choose an approach
          ↓
        Implement
          ↓
        Test
          ↓
        Review

After the implementation works, review it again.

Ask whether the solution can be made simpler, clearer, more efficient, or more reusable without unnecessarily increasing its complexity.

The purpose of the library is not only to complete the exercises.

The purpose is to practice the engineering process used to arrive at a good solution.

Mental Preparation

Before we begin with the technical side of things, we need to define something that is, in my opinion, even more important at the very beginning: mental preparation.

Unfortunately, this is something that is discussed very little, if at all.

You need to know what to expect. You need to have at least some understanding of what kind of profession you are entering, or what kind of profession you have just started building yourself into, and what you should be prepared for along the way.

This kind of training, learning, and mentorship is primarily intended for people who aim, at least theoretically, for some of the highest and most ambitious goals in software engineering, and for people who want to pursue this field because they are deeply interested in it and genuinely care about it.

What do I mean by genuinely caring about it? Very simply.

From my perspective, there is a fundamental difference between liking someone or something and loving someone or something. Those two things are not comparable.

I love what I do, and personally, I would not want to choose any other profession.

I think this is important for you to know from the beginning.

You should know who you are speaking with, how that person thinks, and how much this profession actually means to them.

The starting point of our conversation is based on three factors that, in my opinion, should form the foundation of any serious learning and development process:

  • there are no easy and difficult tasks;
  • do not compare yourself to others;
  • exponential learning.

There Are No Easy and Difficult Tasks

There are no easy and difficult tasks.

There are tasks that you can currently do and tasks that you currently cannot do, depending on your goals, ambitions, needs, affinities, natural tendencies, general level of ability, and intellectual capacity. Do not begin by asking yourself questions such as:

  • How difficult is this?
  • How long will it take me to learn this?
  • Will I be functional in three to six months?

When you approach learning this way, you unnecessarily create mental limits for yourself.

At that point, you almost do not need an external enemy anymore, because you have already started limiting yourself before the real work has even begun.

Instead, ask yourself questions such as:

  • How strongly do I want to commit to this?
  • At what level do I want to pursue this?
  • What am I willing to give up in order to reach that level?
  • How much do I need to change my current way of life in order to align it with my goals, and how ready am I to do that?
  • How patient and calm am I?
  • How much endurance do I have?
  • How strong is my focus?
  • What are my working habits like?

These questions force you into a much more objective analysis of your current position.

They help you understand what your next steps in the learning process should actually be.

Of course, this requires a certain level of self-awareness and self-criticism.

But those are also necessary parts of development and progress.

You need to be aware of what you can currently do, what you cannot yet do, and what exactly is missing between those two points.


Do Not Compare Yourself to Others

Do not compare yourself to other people.

Do not compare yourself to colleagues who are approximately at your level, or to people who started at roughly the same time as you in terms of functionality and productivity.

Focus on yourself. Focus on what you are doing, not on what everyone else is doing.

Comparison will usually bring very little value during the learning process and the early stages of development.

In many cases, it can become counterproductive or even self-destructive.

Be especially careful when looking at people who have ten, fifteen, or twenty years of experience.

Do not spend too much time observing or researching things that normally require years, and sometimes decades, of experience to fully understand.

From the perspective of someone who is still at an early stage of development, the level of complexity involved can easily appear completely unreachable.

That impression is often misleading. Instead, focus on what you are doing right now. Focus on your next step.

Focus on gradually expanding the range of things you are capable of understanding and doing.


Linear and Exponential Learning

There are linear and exponential ways of learning.

Linear vs Exponential Learning

Linear learning means that, approximately speaking, every new unit of invested time or effort gives you a similar amount of new knowledge.

You learn one subject after another, but your previous knowledge does not dramatically accelerate your ability to understand the next thing.

In many cases, each new piece of information can also be learned and applied to a particular scenario relatively quickly.

You process the information, understand the basic mechanism, apply it, and very soon you can see a functional result.

Learning how to drive a car is one example. Learning how to prepare a specific meal is another.

You learn a sequence of actions, practice them, and relatively quickly you can produce a visible and functional result.

In practical software engineering, however, learning rarely works this way.

Software engineering is exponential learning.

You will not usually encounter one isolated thing, understand it immediately, and then apply it functionally to a real situation.

Instead, the process often looks more like this:

  • You encounter ten different things.
  • After some time, three of them begin to make sense.
  • Eventually, those three things connect in a way that produces one functional result.
  • As your knowledge grows, the number of possible connections between concepts grows as well.
  • The learning process begins to branch.
  • And as the branches expand, the overall level of complexity increases with them.

This applies to almost every field and subfield within software engineering.

That is something you need to understand from the beginning, because it directly affects the amount of patience, calmness, and mental stability you will need.

You may work on something for two months before you see a meaningful result. For some things, it may take a year.

There will be situations where you spend days or weeks preparing, drawing diagrams, analyzing systems, designing structures, or thinking through different approaches before you write any meaningful implementation at all.

And that is completely normal.

The important thing is to understand that lack of immediate visible output does not necessarily mean that nothing is happening.

A large part of software engineering learning happens before the result becomes visible.

You are building connections. You are building mental models.

You are gradually creating the internal structure that will later allow many different pieces of knowledge to work together.

That is why patience is not an optional quality in this profession. It is one of the foundations.


Time and Energy Management

The next step in our discussion is how you distribute your time and energy.

Of course, enthusiasm, ambition, motivation, and excitement about something new are important.

But it is equally important to understand how you spend and distribute your energy, time, and effort.

When you are absorbing a new type of information and learning how to analyze that information in a new way, maintaining a high level of focus over a relatively short period of time is one of the keys to adapting effectively and absorbing new knowledge.

People often say:

I currently have seven or eight free hours every day. I am going to spend all of that time learning.

Theoretically, you can do that.

The much more important question is how effective those seven or eight hours are actually going to be.

And this applies regardless of whether you have previously worked in a technical field or are entering one for the first time.

Instead, my suggestion is to begin with shorter and more focused sessions.

During the first week, experiment with schedules such as:

  • 3 × 30 minutes
  • 2 × 45 minutes
  • 4 × 30 minutes
  • 3 × 45 minutes
  • 5 × 30 minutes
  • 3 × 60 minutes
  • 4 × 45 minutes

These are not rigid rules.

They are examples of different ways to distribute focused work while your mind is adapting to a new type of workload.

Focus Level Importance

Alongside intensity, another extremely important factor is continuity.

During the adaptation period, you should work every day.

That does not mean that every day must be equally productive.

If there is a day when you can only manage two focused sessions of thirty minutes, that is completely acceptable.

What I would strongly avoid is a pattern such as:

  • working for three days;
  • stopping for two;
  • working for five days;
  • stopping for another three.

That kind of inconsistency can make adaptation significantly more difficult and can negatively affect both efficiency and productivity. During the first several days, observe yourself. Analyze which pace works best for you at the beginning. Once you understand that, gradually increase the intensity over time. Think about it in the same way you would think about starting physical training.

If you begin going to the gym, there is a period of adjustment, adaptation, and progressive loading. The principle here is similar. The difference is that sustained intellectual work can eventually become extremely demanding in its own way. Do not compare your initial working intervals with those of someone who works nine or ten hours a day and has ten or fifteen years of experience in the field. That comparison gives you almost nothing useful.


Continuity has to exist. Intensity increases over time.


A good approach is to create several different schedules and use them depending on the type of day you are having. Some days you will have more energy. Some days you will have less. The important thing is to preserve continuity while gradually increasing your capacity for concentrated work.


Health, Lifestyle, and Quality of Work

At the highest levels of development, software engineering can become more of a way of life than simply a choice of profession.

But health is far more important than software engineering.

From my personal perspective, I think about health and long-term functioning through five major areas:

  • what is eating away at you mentally — stress, nervousness, and internal pressure;
  • what you eat and consume;
  • your general lifestyle and habits;
  • your level of physical activity;
  • how you sleep and how well you recover.

The quality of your work depends on many things:

  • continuity;
  • focus;
  • persistence;
  • determination;
  • calmness;
  • composure;
  • attention to detail;
  • patience;
  • quality of adaptation;
  • quality of analysis.

Your health and overall quality of life can influence every one of these factors.

I will give you an example from my own life:

  • I try to reduce unnecessary distractions and disruptions to my focus as much as possible.
  • I do not watch television.
  • I do not spend time on social media.
  • I try not to involve myself in conversations that I already know are likely to be unproductive, pointless, or mentally draining.
  • I do not drink alcohol. I do not smoke. I do not use psychoactive substances.
  • I do not drink carbonated soft drinks. I do not eat sweets.
  • The sugars I consume come almost entirely from fruit, together with some honey that I occasionally put into tea.
  • In general, I don’t eat highly processed foods.
  • I try as much as possible to prepare my own food.
  • I train 4-5 times a week, physical activity is very important for health.

That is essentially it. When written as a list, it may sound as though I have given up many things, but once those choices become normal habits and you live with them for a long enough period of time, you begin to notice how they affect you and how they influence different parts of your life.

I am not trying to act as a doctor here, and none of this should be interpreted as medical advice.

I am simply pointing out how important lifestyle can be for many different factors, including the quality of your work.


My own way of working, and the quality of work I am capable of producing, would not be anywhere near what it is today without the habits I have developed.


Some of those habits have always been part of my life. Others were developed gradually over time.


Mentorship or Courses?

One of the important questions in any technical field is:


Is it better to learn through mentorship or through courses?

My personal experience is strongly on the side of mentorship.


I have always been willing to invest my time in people who are stable, composed, calm, patient, and understanding. People in whose eyes I could see a genuine desire to transfer knowledge. People who were visibly satisfied when something they had taught me was later understood, applied, or successfully used somewhere else.

I have always been drawn to people who genuinely enjoy what they do and who have a certain amount of passion and enthusiasm for it. That does not mean that every mentor will have the same level of knowledge or experience. They will not. If a mentor gives you the maximum value they can provide from the knowledge and experience they currently possess, that relationship can be far more functional and effective than almost any course.

Unfortunately, I have met many people with extremely broad technical knowledge who simply did not know how to transfer that knowledge to someone else. They knew the subject. But they could not bring it closer to another person. They could not explain the same concept in several different ways. They could not adapt the explanation depending on who was sitting in front of them.

Part of this may depend on how developed someone’s social and interpersonal abilities are.

But ultimately, it is entirely up to you to decide who deserves your time and energy.

I have never particularly preferred courses. The courses I completed rarely gave me any significant long-term benefit, especially compared with what I gained by learning directly from experienced people. I always tried to spend time around people who had been working directly in the industry for ten years or more and who were dealing with problems significantly more complex than the ones I was working on at the time. I did not spend much time worrying about the complexity of what they were doing.

My focus was always much simpler:

What can I learn from this person that I can understand and apply at my current level?

That was what mattered to me. I also never placed much weight or credibility on people who primarily copied existing solutions, rarely created anything of their own and whose industry experience existed more on paper than in the depth of the work they had actually done.


Experience is not defined by the amount of time that has passed.

It is defined by the quality of the time that was spent.


Someone can say that they have twenty years of experience. They can call themselves a senior engineer or an architect. But if they have spent those twenty years repeating essentially the same type of work, often relying heavily on existing solutions without significantly expanding the scope or complexity of what they do, then the number itself tells you very little. On the other hand, someone may have ten years of experience and spend those ten years constantly entering unfamiliar territory, solving new problems, working with increasingly complex systems, and continuously pushing themselves toward a higher technical level.

Decide for yourself which of those two people would likely be the more suitable mentor.

Keep in mind that real mentorship has to be earned through effort, persistence, and attitude.

You also need to become someone worth investing time in.

A mentor needs to feel that their effort matters and that you are serious enough for them to want to continue training you.


I never watched the clock.

I never waited for five minutes before the end of the workday so I could shut down my machine and leave.

I never found it difficult to work through a weekend or skip going out so that I could sit down and study, especially when someone had already invested their own time in helping me learn.

Those things are noticed. They are valued. And the right people will appreciate them.

When someone gives you their time, experience, patience, and attention, the best way to respect that is to show through your own actions that their effort is not being wasted.


Train Creativity and Intuition

There is another thing that I consider extremely important.

Do not train only memory. Do not train only repetition.

Do not spend your entire development process trying to become better at remembering patterns that can already be repeated mechanically. Train what is, in my opinion, far more valuable:

  • creativity;
  • intuition;
  • the ability to connect things that initially appear unrelated;
  • the ability to look at the same problem from several different directions;
  • the ability to recognize that something is wrong even before you can fully explain why.

These are some of the areas where human thinking is fundamentally different from mechanical repetition.

A machine can:

  • process enormous amounts of information.
  • reproduce patterns.
  • generate variations of things that already exist.

But your job should not be to compete with a machine in memorization. That is the wrong competition.

The goal should be to develop the parts of your thinking that become stronger through diversity of experience.

That means:

  • Doing different things.
  • Learning different things.
  • Solving different kinds of problems.
  • Reading outside of your immediate technical area.
  • Building things.
  • Breaking things.
  • Fixing things.
  • Talking to people from completely different professions.
  • Learning music.
  • Drawing.
  • Training.
  • Traveling.
  • Studying science.
  • Working with hardware.
  • Writing.
  • Designing.
  • Observing.
  • Thinking.

Not everything you do has to produce an immediate and measurable software-engineering result. That is an extremely important point. We have become too accustomed to asking:

What is the direct benefit of this?

Sometimes there is no immediate visible benefit. That does not mean there was no benefit.

A large amount of personal and intellectual development happens indirectly. Some experiences influence:

  • how you think;
  • how you organize information;
  • how you recognize patterns;
  • how patient you are;
  • how creative you become;
  • how quickly you notice relationships between things;
  • how well you remain mentally balanced.

You may not be able to measure those effects immediately. They may become visible years later.


Not Everything Valuable Produces an Instant Result

We should be very careful with the idea that something has value only if it immediately produces an efficient and visible result.

That way of thinking can become extremely limiting.

Imagine that you spend three hours learning something and at the end of those three hours you cannot point to:

  • a completed feature
  • a new service
  • a finished application
  • a measurable performance improvement

Does that automatically mean those three hours were wasted? No.

Maybe you in that case:

  • developed a new way of thinking about a problem.
  • encountered a concept that will become useful six months later.
  • improved your patience.
  • improved your ability to concentrate.
  • created a connection between two completely different areas that will later lead to a solution you would otherwise never see.

Not every useful process has an immediate output. This becomes especially important when developing creativity and intuition.

Those qualities are not built through one isolated exercise. They are built through accumulated experience.


Diversity of Experience Matters

If you do the same thing every day, you may become extremely efficient at that one thing. But efficiency and creativity are not the same thing.

Creativity often comes from connecting things that were previously separate. You see something in one field and realize that the same principle can be applied somewhere completely different:

  • You recognize a structure.
  • A rhythm.
  • A relationship.
  • A pattern.

That is why I believe people should expose themselves to more than one narrow area of activity.

You do not need to become an expert in everything. That is not the point.

The point is to give your mind enough different material to work with.

The more different forms of experience you have, the more possible connections your mind can create between them. That is where a large part of creativity comes from.


My Personal Example: Piano

I have played the piano throughout my life. Someone could reasonably ask me:

What benefit does playing the piano give you as a software engineer?

My answer would be: Directly related to programming? Almost none.

Playing the piano has practically almost nothing to do with programming.

I do not become directly better at Go or Rust because I can play the piano. I do not understand distributed systems because I know how to play a piece of music. There is no direct technical relationship, but that does not mean there is no benefit.

Playing the piano can completely change my mental state.

It can clear my head. It can reduce mental pressure after hours of concentrated technical work. It helps me maintain balance. From my own experience, it strongly affects my creativity and intuition.

When you spend a large part of your day solving technical problems, your brain does not always need another technical problem.

Sometimes it needs something completely different. Something that uses attention differently. Something that changes rhythm. Something that allows the mind to move away from the problem without becoming completely inactive. Music does that for me.

And very often, after stepping away from a technical problem for some time, I return to it and see something that I did not see before. The problem did not change. My perspective did.


Intuition Is Built Through Experience

People sometimes talk about intuition as though it were something mystical. I do not see it that way. In engineering, intuition is often accumulated experience that has become so deeply internalized that you recognize patterns before you consciously reconstruct every step of the reasoning. An experienced engineer may look at a system and say:

Something about this design does not feel right.

At that moment, they may not immediately be able to explain exactly why, but after deeper analysis, the reason often becomes visible:

  • Maybe they have seen a similar dependency pattern before.
  • Maybe they have experienced a similar failure.
  • Maybe the system reminds them of an architecture that became impossible to maintain.
  • Maybe the interaction between components creates a pattern they have encountered in another context.

That intuition did not appear from nowhere. It was built through:

  • different systems;
  • different failures;
  • different technologies;
  • different people;
  • different problems;
  • different experiences.

The broader the experience, the more material intuition has to work with.


Do Not Reduce Yourself to Repetition

There is little value in spending your entire development process becoming better at repeating something that can already be automated:

  • Memorizing every command is not the goal.
  • Memorizing every API is not the goal.
  • Memorizing every syntax detail is not the goal.

Those things can be looked up. What matters much more is whether you understand:

  • what problem you are solving;
  • why the problem exists;
  • which information matters;
  • which information does not matter;
  • what is missing;
  • what could fail;
  • what could be done differently;
  • what connection others may not have noticed.

That is where engineering becomes creative work. And that is where human development should be focused.


Give Your Mind More Than One Direction

Do not be afraid to spend time on things that appear unrelated to your primary profession. Not everything needs to:

  • become a business.
  • become a portfolio project.
  • improve your CV.
  • produce money.
  • produce an immediate result.

Some things exist simply because they make you a more complete person. And a more complete person can become a better engineer.

That relationship is not always direct. But direct relationships are not the only relationships that matter.

A person who has experienced more things has more perspectives from which to observe a problem. A person who has created in several different forms has more ways to approach creation. A person who knows how to move away from work and return mentally refreshed can often work better than someone who spends every available hour forcing the same type of concentration.


Creativity Cannot Be Scheduled Like a Build

You can schedule:

  • build
  • test
  • deploy
  • backup
  • benchmark

You cannot schedule:

        14:00 — have a genuinely original idea

Creativity does not work like that. Sometimes an idea appears while you are:

  • walking;
  • playing music;
  • exercising;
  • reading something unrelated;
  • talking to someone;
  • doing absolutely nothing technical.

That does not mean the technical work stopped. Very often, part of the problem is still being processed in the background.

This is another reason why I do not believe every useful activity needs to have an immediate measurable output. Some of the most valuable results appear indirectly.


Do Not Compete With Machines at Being Machines

This is particularly important in the age of AI. If machines become increasingly good at:

  • memorization;
  • pattern reproduction;
  • repetitive implementation;
  • rapid information retrieval;
  • mechanical transformation;

then your response should not be:

I need to become even better at behaving like a machine.

That makes no sense. Develop the things that make your thinking more valuable. Develop:

  • judgment;
  • creativity;
  • intuition;
  • adaptability;
  • curiosity;
  • originality;
  • the ability to connect unrelated concepts;
  • the ability to work with incomplete information;
  • the ability to recognize a problem before it has been formally described.

Use machines for what machines are good at. Do not reduce yourself to the same category of work.


Final Perspective

Do not judge every activity by whether it gives you an immediate technical result:

  • Some things improve you indirectly
  • Some experiences will make sense only years later
  • Some things will never have a measurable professional return and will still be worth doing

Your mind is not only a storage system. Do not train it only to remember.

Train it to:

  • create
  • connect
  • recognize
  • imagine
  • observe
  • analyze
  • refactor

Train your intuition. Give yourself enough different experiences for that intuition and creativity to have something to grow from.


Do not spend your life training yourself to become better at the things machines already do well.

Train the parts of your mind that allow you to see what the machine does not.


Developing an Engineering Mindset

Learn to Stay With a Problem

One of the most valuable abilities you can develop as an engineer is the ability to remain with a problem even when the solution is not immediately visible.

Many people are comfortable while progress is obvious. The real difference appears when progress disappears.

You may spend an hour investigating something and still not understand what is happening. You may test five hypotheses and discover that all five were wrong. You may read documentation, inspect logs, change the implementation, go back to the previous version, and still have no final answer. That is normal. Engineering does not reward you immediately.

You need to become comfortable with periods in which you are working seriously but cannot yet see the result. This does not mean repeating the same action for five hours without thinking. Persistence is not stubborn repetition. If an approach does not work, change the approach:

  • Ask another question.
  • Reduce the problem.
  • Create a smaller reproduction.
  • Inspect another layer.
  • Read the source.
  • Measure something.
  • Remove assumptions.

The important thing is that you do not mentally collapse simply because the answer has not appeared yet.

A large part of engineering consists of working in a state where:

        I know that something is wrong.
        I do not yet know why.

You need to remain functional inside that state. That is a skill. And like every skill, it can be trained. If every difficult moment immediately results in:

  • ask someone
  • ask AI
  • copy a solution
  • abandon the problem

you remove one of the most important mechanisms through which engineering ability develops. Sometimes you need help. There is nothing wrong with that, but before asking for the answer, give yourself enough time to actually encounter the problem.

Form hypotheses. Be wrong. Investigate. Try again. The objective is not to suffer unnecessarily. The objective is to develop the ability to continue thinking when certainty disappears.

Your ability to remain functional while you do not yet understand the problem is one of the most valuable engineering skills you can develop.


Learn to Work With Uncertainty

Real engineering rarely gives you perfect information. In training exercises, the problem may be clearly defined. You may know:

  • the exact input;
  • the exact expected output;
  • the constraints;
  • the environment;
  • what the system is supposed to do.

Real systems are often different. You may have:

  • incomplete logs
  • partial documentation
  • unclear requirements
  • unknown historical decisions
  • missing reproduction steps
  • multiple possible causes

And you still have to move forward. You cannot always wait until you know everything. Sometimes knowing everything is impossible. A good engineer learns how to reason with incomplete information. That means separating:

  • what I know
  • what I assume
  • what I suspect
  • what I can verify
  • what I still need to discover

This separation is extremely important. One of the easiest ways to make a bad technical decision is to treat an assumption as a fact. Instead, create hypotheses. For example:

  • The database may be slow.
  • The network may be dropping requests.
  • The application may be blocking.
  • The cache may contain stale data.

Then test them. One by one.

A hypothesis is not a conclusion. It is a direction for investigation.

As information increases, your understanding changes. Your responsibility is to adapt with it. Engineering is not always:

        Problem
            ↓
        Known Solution

Very often it is:

        Incomplete Problem
               ↓
        Hypothesis
               ↓
        Experiment
               ↓
        New Information
               ↓
        Better Hypothesis
               ↓
        Solution

Train yourself to work inside that process. Because the more complex the system becomes, the less likely it is that every important fact will be available to you at the beginning.


Think Before You Act

One of the easiest habits to develop in programming is starting implementation too early:

  • You receive a task.
  • You open the editor.
  • You create a package.
  • You add a dependency.

You start writing functions. And only later do you discover that the actual problem was not properly understood. Writing code feels productive because something visible is happening. But visible activity is not automatically progress. Before implementation, ask:

  • What exactly are we trying to solve?
  • Why does this problem exist?
  • What do we already know?
  • What do we not know?
  • What are the constraints?
  • What is allowed to change?
  • What must remain compatible?
  • What happens when this fails?
  • What is the simplest valid solution?
  • What are we probably going to need later?

Sometimes five minutes of thinking prevents five hours of unnecessary implementation. Sometimes one diagram prevents an entire incorrect architecture. Sometimes asking one question changes the task completely.

This becomes more important as your responsibility grows, when you are:

  • modifying one isolated function, a local mistake may be easy to fix.
  • defining an API, database model, communication protocol, migration strategy, or service boundary, your decisions may affect many other parts of the system.

Implementation is important, but implementation should follow understanding.

Do not use planning as an excuse never to act. Overanalysis can become another form of avoidance. The objective is not to predict the entire future.

The objective is to understand enough of the problem that your first action has a reason behind it.

Do not start by asking what code you should write. Start by asking what problem the code is supposed to solve.

Remove Ambiguity From Your Technical Vocabulary

During both learning and professional work, remove vague and undefined expressions from your technical vocabulary whenever they are being used as a substitute for analysis. Expressions such as:

  • maybe
  • probably
  • most likely
  • it should
  • it should probably
  • possibly
  • I think it will
  • it should work

are not technical explanations.

If you do not know something, say that you do not know it. If something has not been verified, say that it has not been verified.

If there are several possible outcomes, identify them. If a conclusion depends on a condition, define the condition.

Instead of saying:

It should probably work.

say:

I have not verified this yet. Based on the current implementation, I expect it to work if conditions A and B are satisfied. I still need to test condition C.

Instead of:

Maybe the database is causing the problem.

say:

The database is one of three current hypotheses. I need to measure query latency and connection saturation before I can confirm or reject it.

Instead of:

This will most likely scale.

say:

We have tested this implementation up to 5,000 concurrent requests. Behavior beyond that point has not yet been measured.

There is nothing wrong with uncertainty.

Engineering is full of uncertainty. The problem is undefined uncertainty.

Do not hide a lack of information behind vague language. Make uncertainty explicit. Define what is known. Define what is unknown. Define what still needs to be tested. Technical communication should reduce ambiguity, not create more of it.

When someone says:

  • maybe
  • probably
  • most likely
  • it should

my next question is usually:

Based on what?

If there is no clear answer to that question, then the statement has very little technical value.

Train yourself to speak in terms of:

  • known
  • unknown
  • verified
  • unverified
  • measured
  • assumed
  • expected
  • required
  • observed

The objective is not to sound more confident than you really are.

The objective is to be precise about the level of confidence you actually have.

Uncertainty is acceptable. Ambiguity is not.


Learn to Explain What You Know

Technical understanding and technical communication are closely connected. You may believe that you understand something because it feels familiar. But try to explain it clearly. Explain:

  • how it works;
  • why it works;
  • what problem it solves;
  • what assumptions it depends on;
  • where it can fail;
  • why you chose it over another approach.

Very often, the moment you attempt to explain something, you discover gaps in your own understanding. That is useful. Explanation forces structure. It forces you to turn:

        I kind of understand this.

into:

        I can describe exactly what is happening.

A strong engineer should be able to explain the same system at different levels. To another engineer, you may discuss:

  • consistency
  • latency
  • failure modes
  • locking
  • replication

To someone less experienced, you may need a simpler explanation. That does not mean making the explanation incorrect. It means understanding the subject well enough to choose the right level of abstraction.

This skill becomes increasingly important as you progress:

  • Senior engineers explain decisions.
  • Architects explain systems.
  • Mentors explain concepts.
  • Teams explain failures.
  • Documentation explains knowledge to people who may not even be present today.

If knowledge exists only inside your head, its value to the organization is limited.

Practice explaining your work. Write documentation. Describe your architecture. Explain why you rejected an alternative. Teach someone else. A useful test is:

Can I explain this clearly without hiding behind terminology?

If the answer is no, investigate whether you actually understand it as well as you think.


Draw the Model Before You Implement It

Before writing any serious model, draw it.

Before creating structures, interfaces, database entities, relationships, services, communication flows, or larger architectural components, first create a diagram or at least a rough sketch.

It does not need to be beautiful. It does not need to be created with professional diagramming software. A piece of paper is enough.

The important thing is that the idea exists visually before implementation begins. For example:

        Client
           ↓
        Gateway
           |
           +------> Service A
           |
           +------> Service B
                        ↓
                     Database

Or for a data model:

        User
          |
          +---- has many ----> Project
                                  |
                                  +---- contains ----> Task

The purpose of the diagram is not decoration. The purpose is to force you to answer questions before code starts hiding the weaknesses in your idea.

A diagram immediately exposes things such as:

  • missing relationships;
  • unclear ownership;
  • incorrect dependencies;
  • circular dependencies;
  • missing data flows;
  • undefined responsibilities;
  • unnecessary components;
  • unclear boundaries;
  • inconsistent cardinality;
  • missing failure paths.

Code can create the illusion of progress. A diagram forces you to look at the whole thing.

Personally, if someone comes to me with a serious model and cannot show me even a basic diagram or sketch, I do not want to hear the implementation yet. For me, that immediately raises several questions:

  • Do you actually know what the final structure should look like?
  • Have you thought through the relationships?
  • Do you understand where the boundaries are?
  • Have you analyzed how information moves through the system?
  • Can you explain the model clearly to another person?
  • Or did you simply start implementing and allow the model to emerge accidentally?

If you cannot draw the idea, there is a good chance that the idea is not yet sufficiently developed. That does not mean the first diagram must be correct. It probably will not be.

That is exactly why you draw it before implementation:

  • Change the boxes
  • Move the arrows
  • Remove components
  • Add missing relationships
  • Destroy the first version
  • Draw another one

Doing that on paper takes minutes. Doing the same thing after thousands of lines of implementation may take days or weeks. The diagram is also one of the simplest tests of whether you can communicate your idea.

If you need twenty minutes of verbal explanation before another engineer can understand where the components are and how they interact, the model may not yet be clear enough. A good diagram gives the conversation a common starting point. You can:

  • point to a component.
  • question a relationship.
  • identify a missing dependency.
  • discuss the same system instead of two different mental interpretations of it.

The workflow should therefore be:

        Problem
           ↓
        Analysis
           ↓
        Sketch
           ↓
        Diagram
           ↓
        Review
           ↓
        Model
           ↓
        Implementation

not:

        Problem
           ↓
        Start Coding
           ↓
        Discover the Architecture While Implementing

There are cases where small, trivial changes do not require formal modeling. Use judgment, but as soon as you are designing something with meaningful relationships, responsibilities, states, dependencies, or data flow, draw it first.

If you cannot draw the model, you are probably not ready to implement the model.


Review Your Own Decisions

Experience alone does not automatically create good judgment. You can repeat the same mistake for ten years and call it ten years of experience. What matters is whether you learn from what happened. One of the best ways to improve engineering intuition is to review your own decisions.

When making an important decision, record:

  • Problem
  • Assumptions
  • Available Information
  • Decision
  • Alternatives
  • Expected Result

Then return to it later and add:

  • Actual Result
  • What Was Correct
  • What Was Wrong
  • What I Would Change
  • What I Learned

This can be extremely valuable. Imagine that you selected a specific database architecture because you expected a certain traffic pattern. Six months later, the traffic pattern is different and the design causes problems. You can simply fix it and move on.

Or you can ask:

Why did I believe this was the right choice six months ago?

Use cases can be:

  • your reasoning was good and reality changed.
  • the data was incomplete.
  • you ignored an important signal.
  • you overestimated one risk and underestimated another.

Those distinctions matter. The objective is not to judge your past self unfairly. You made the decision with the information you had at the time.

The objective is to improve the process through which future decisions are made. This is one of the ways intuition becomes stronger. Intuition is not only created by experiencing many situations. It is strengthened by reflecting on those situations afterward.

If you never review your reasoning, you may remember the result but forget why you arrived there.

Experience gives you events. Reflection turns those events into judgment.


Separate Your Ego From Your Solution

Your code is not you. Your architecture is not you. Your idea is not you.

This sounds simple, but it is extremely important. You will make bad decisions. Everyone does. Someone less experienced than you may occasionally have a better idea. Someone may find a serious problem in something you designed. A code review may completely reject your approach. That should not become a personal conflict. If your objective is to create the best possible system, then discovering that your current solution is wrong is useful information.

The problem begins when the objective changes from:

        Find the best solution.

to:

        Prove that my solution was correct.

At that point, engineering becomes ego defense. You start ignoring evidence. You interpret criticism of the implementation as criticism of yourself. You defend complexity because you created it. You resist removing code because you spent three weeks writing it. That is dangerous.

A strong engineer should be able to say: "I was wrong" and continue working normally. That sentence does not reduce your value. Very often, the opposite is true. The ability to change your opinion when better evidence appears is a sign of technical maturity. You should defend ideas with reasoning, but once the reasoning no longer holds, let the idea go.

The objective is not to win an argument. The objective is to improve the system.


Do Not Confuse Speed With Progress

Software engineering has an unhealthy relationship with visible activity:

  • More commits.
  • More lines of code.
  • More tickets closed.
  • More generated output.
  • More features.
  • More speed.

Those numbers can mean something, but they can also mean almost nothing.

Imagine two engineers. The first engineer writes 5,000 lines of code in two days. The second spends one day analyzing the problem and writes 300 lines on the second day.

Who made more progress?

You cannot answer that from the numbers. The 5,000 lines can create unnecessary complexity. The 300 lines can solve the problem completely. You have to inspect the result. This becomes especially important with AI. If a model generates:

        10,000 lines in ten minutes

that is an impressive generation rate. It does not automatically mean:

        10,000 lines of engineering value

Generated output still needs to fit the system:

  • It needs to be correct
  • Maintainable
  • Secure
  • Testable
  • Compatible
  • Useful

Speed is valuable when you are moving in the right direction. Moving quickly in the wrong direction only increases the distance you later need to travel back. Do not measure your development only through visible volume. Sometimes:

  • deleting 2,000 lines is progress.
  • spending a day without writing code is progress.
  • discovering that a feature should not exist is progress.

Remember:

activity ≠ progress
output ≠ value
speed ≠ quality

Train Observation

A large part of engineering begins with noticing something:

  • strange line in a log.
  • small latency increase.
  • function that has a name that does not match what it actually does.
  • dependency that seems to point in the wrong direction.
  • recurring failure every few hours.
  • memory pattern that looks slightly unusual.
  • database query that should not be executed that often.

None of those things may initially look like a major problem, but someone has to notice them. Good engineers develop observation. They do not only wait for SYSTEM FAILED before becoming interested. They notice smaller signals. Observation leads to questions:

  • Why is this happening?
  • Has it always behaved this way?
  • Why does this service depend on that service?
  • Why is this request slower only sometimes?
  • Why does memory usage increase but never return?

Questions lead to investigation. Investigation produces understanding.

This is also strongly connected with intuition. You cannot build intuition about systems if you do not pay attention to how systems behave.

Train yourself to observe:

  • Read logs even when nothing is broken
  • Look at metrics
  • Inspect generated files
  • Watch network traffic
  • Read code you did not write

Compare expected behavior with actual behavior. Pay attention to things that seem slightly inconsistent. You do not need to investigate every unusual detail forever, but you should develop the habit of noticing them. Sometimes the first sign of a serious problem is simply:

Something here does not look right.


Create More Than You Consume

There is an enormous amount of technical content available today:

  • courses
  • videos
  • books
  • articles
  • documentation
  • tutorials
  • AI explanations

All of them can be useful, but there is a danger in consuming technical information continuously without creating anything yourself.


Watching someone build a system creates familiarity.

Building the system yourself exposes understanding.


Those are not the same thing. You may watch a two-hour tutorial and understand every step. Then close the tutorial. Open an empty directory. And discover that you do not know where to begin. That is valuable information. Creation exposes the gaps that passive consumption can hide.

Instead of only reading about:

  • networking
  • databases
  • concurrency
  • Git
  • Linux
  • APIs

build something with them:

  • Write a small server
  • Create a protocol
  • Break it
  • Measure it
  • Document it
  • Delete it
  • Build another version

If you are learning an algorithm, do not only read the solution.

Implement it. Change the constraints. Create new form.


Create another problem that requires the same idea. If you are learning architecture, design a system yourself before reading someone else’s architecture. Then compare.

Technical education should not become endless consumption. At some point, close the material and create something from nothing. That is where understanding becomes visible.

Consumption creates familiarity. Creation exposes understanding.

The purpose of learning is not to collect information.

The purpose is to become capable of doing something with it.


Learn to Finish

During the learning process, a crucial factor for progress is gradation:

  • Starting something is easy.
  • Finishing it is a different skill.
  • The beginning of a project is exciting.
  • You choose technologies.
  • You create architecture.
  • You write the first clean implementation.

Everything is new. Then comes the part that is usually less exciting:

  • edge cases
  • tests
  • error handling
  • migration
  • documentation
  • cleanup
  • deployment
  • monitoring
  • performance problems
  • compatibility
  • bug fixing
  • maintenance

This is where many projects become difficult and this is where a large part of engineering actually happens:

  • feature is not finished because the primary happy path works.
  • service is not finished because it starts successfully on your machine.
  • system is not finished because the architecture diagram looks good.

You have to ask:

  • Can it fail safely?
  • Can we deploy it?
  • Can we upgrade it?
  • Can we monitor it?
  • Can someone else understand it?
  • Can we recover it?
  • Can we maintain it six months later?

People who constantly move from one new idea to another can accumulate enormous amounts of beginnings and very little completed work. That creates familiarity with starting. It does not create experience with the complete lifecycle.

Finishing teaches you things that starting never will. It:

  • exposes assumptions
  • reveals edge cases
  • forces compromises
  • introduces maintenance
  • teaches responsibility

Starting teaches enthusiasm. Finishing teaches engineering.

Learn to finish what deserves to be finished.


An Idea Without Execution Is Just Another Illusion

Ideas are easy to produce. Execution is what gives them value.

You can imagine:

  • a new product;
  • a better architecture;
  • a new service;
  • a business;
  • a platform;
  • a library;
  • a completely different way of solving a problem.

But until you execute the idea, it exists only in your head. It has not:

  • been tested
  • met reality

nor been forced to deal with:

  • constraints
  • time
  • cost
  • failure
  • complexity
  • people
  • maintenance
  • unexpected behavior

That is why I do not place much value on an idea by itself. An idea can sound brilliant while it remains theoretical. Execution is the point where the idea is forced to prove itself. Reality starts asking questions:

  • Does it actually work?
  • Can it be built?
  • Can it be maintained?
  • Can someone else understand it?
  • Does it solve the problem you thought it solved?
  • Is the complexity justified?
  • What happens when the assumptions are wrong?

Until those questions are answered, you do not really know what you have. You have a possibility. Not a result.

There is also a psychological trap here:

  • Thinking about an idea can create a feeling of progress.
  • Talking about it can create a feeling of progress.
  • Drawing the architecture can create a feeling of progress.
  • Explaining what you are going to build can create a feeling of progress.

But none of those things are the execution itself. At some point, you have to build. You have to test, fail, correct, finish. That is where the idea becomes real.

This is also why unfinished projects are not only unfinished products. They are unfinished lessons. If you:

  • stop before deployment, you do not learn deployment.
  • if you stop before real usage, you do not learn what users actually do.
  • if you stop before maintenance, you do not learn what your decisions cost over time.
  • if you stop before failure, you do not learn whether your recovery strategy was real or theoretical.

Execution closes the loop between imagination and reality. Without that loop, you can spend years believing that your ideas are better than they actually are, simply because they were never tested hard enough to prove otherwise.

An idea without execution is just another illusion.

The value is not in having the idea. The value is in turning the idea into something that survives contact with reality.


Setup Your Environment

Before we start writing code, we need to prepare the environment in which you are going to work.

You do not need an expensive workstation. You do not need the latest processor. You do not need a powerful graphics card.

And you definitely do not need to spend several thousand dollars before you can start learning software engineering.

What you need is a machine that is fast enough not to become an obstacle.

The purpose of your development machine is simple:

It should allow you to focus on learning and engineering instead of constantly fighting your hardware.


Hardware

For the beginning, almost any reasonably modern desktop or laptop computer will be sufficient.

Our general recommendation is:

CPU:     6–8 modern CPU cores
RAM:     16 GB minimum
         32 GB recommended
Storage: SSD strongly recommended
GPU:     no dedicated GPU required

You can absolutely begin with less.

But there is a difference between:

Can this machine run the software?

and:

Is this machine comfortable to work on every day?

Those are not the same question.


CPU

A modern processor with approximately six or eight physical cores is more than enough for the majority of tasks you will encounter at the beginning.

You will use the CPU for things such as:

  • compiling software;
  • running tests;
  • running multiple services;
  • compression;
  • local databases;
  • containers;
  • development tools;
  • virtual machines;
  • benchmarks.

You do not need a high-end workstation CPU.

A reasonable modern desktop or laptop processor is enough.

As your work becomes more complex, stronger hardware can reduce build times and make larger local environments more comfortable.

But hardware should not become an excuse for postponing learning.

Start with what you have if it is reasonably functional.

Upgrade when you actually understand what limitation you are trying to remove.


Memory

We consider:

16 GB RAM

a practical minimum for a comfortable modern development environment.

If possible, use:

32 GB RAM

With 32 GB you have significantly more space for running:

  • your IDE or editor;
  • browser sessions;
  • multiple terminals;
  • databases;
  • several services;
  • containers;
  • build processes;
  • documentation;
  • debugging tools

at the same time.

You do not need 64 GB or 128 GB of memory to learn software engineering.

There are workloads where those amounts become useful.

You are not starting there.


Storage

Use an SSD. Preferably, use an NVMe SSD if your machine supports one.

Development environments perform a huge number of small filesystem operations.

Compilers read and write files.

Git works with thousands of files.

Package managers create caches.

Databases write data.

Build systems constantly create and remove artifacts.

The difference between an old mechanical hard drive and an SSD is very noticeable in development work.

For a dedicated learning machine, something around [512 GB] is enough to start comfortably.


Graphics Card

For the work we are going to do initially, you do not need a dedicated graphics card.

Integrated graphics are completely sufficient.

A powerful GPU becomes relevant if you later move into areas such as:

  • graphics programming;
  • game development;
  • GPU computing;
  • CUDA;
  • local machine-learning workloads;
  • 3D rendering;
  • computer vision workloads.

Do not buy hardware for problems you do not currently have.


Operating System

For this Task Library, we recommend using Linux from the beginning.

More specifically, our recommendation for a first Linux development environment is:

Linux Mint — Cinnamon Edition

Use the latest stable release available when you prepare your machine.

Linux will eventually become necessary or extremely useful if you move deeper into areas such as:

  • backend engineering;
  • distributed systems;
  • networking;
  • DevOps;
  • SysOps;
  • cloud infrastructure;
  • containers;
  • databases;
  • security;
  • operating systems;
  • automation;
  • CI/CD.

A very large part of modern server infrastructure runs on Linux.

You are going to encounter it sooner or later. Our recommendation is simple:

Encounter it sooner.

Do not wait until you are already expected to know it professionally.


Why Linux Mint?

Linux has many distributions. You will eventually hear discussions about:

  • Debian;
  • Ubuntu;
  • Fedora;
  • Arch Linux;
  • openSUSE;
  • Linux Mint;
  • and many others.

Do not waste your energy on distribution wars at the beginning. The operating system is here to provide you with a development environment. You are not choosing a religion.

For beginners, we recommend Linux Mint because it provides a very good balance between:

  • stability;
  • simplicity;
  • hardware support;
  • desktop usability;
  • package availability;
  • documentation;
  • compatibility with the wider Ubuntu ecosystem.

The Cinnamon desktop is also familiar enough that people coming from Windows usually adapt to it quickly.

That matters. At this stage, we want you learning Linux and software engineering.

We do not want you spending three days configuring a desktop because someone on the Internet told you that a more complicated distribution makes you a better engineer.

It does not.


Dedicated Linux Installation or Dual Boot?

If you have a machine dedicated to learning and development, our preferred setup is simple:

        Machine
            ↓
        Linux Mint

Nothing else.

This gives you the cleanest environment and removes unnecessary complexity.

If the machine is also your personal computer and you still need Windows for other software, gaming, or other obligations, dual boot is completely acceptable.

That setup may look like:

        Computer
            │
            ├── Windows
            │
            └── Linux Mint

When the computer starts, you select which operating system you want to use.

If you decide to dual boot, install Windows first and Linux Mint afterward.

Linux Mint can detect the existing Windows installation and configure the boot menu accordingly.


Before Installing Linux

Before changing operating systems or disk partitions:

Back up everything important.

Do not continue until important files exist somewhere other than the machine you are about to modify.

That may be:

  • another computer;
  • an external SSD;
  • an external hard drive;
  • network storage;
  • cloud storage.

Partitioning mistakes can destroy data.

Choosing the wrong drive during installation can destroy data.

Formatting the wrong partition can destroy data.

The installation process itself is not particularly difficult.

But storage operations deserve attention.


Installing Linux Mint

The general installation process is straightforward.

You will need:

  • your computer;
  • a USB flash drive;
  • the Linux Mint ISO image;
  • a tool for creating a bootable USB drive.

1. Download Linux Mint

Download the latest stable Linux Mint Cinnamon Edition from the official Linux Mint website.

Avoid random download websites.

Operating-system installation images should come from the official source.

Where possible, verify the downloaded image using the checksums provided by the Linux Mint project.


2. Create a Bootable USB Drive

Write the Linux Mint ISO image to a USB flash drive.

If you are creating the installation USB from Windows, macOS, or another Linux distribution, tools such as Etcher can be used.

The process is essentially:

Linux Mint ISO
        ↓
USB imaging tool
        ↓
USB flash drive

Be careful when selecting the destination device.

The USB drive will be overwritten.


3. Boot From the USB Drive

Insert the USB drive and restart the machine.

Open your computer’s boot-device menu.

Depending on the manufacturer, the key may be something such as:

F2
F8
F10
F11
F12
Esc
Delete

Select the USB drive.

Linux Mint will start in a live environment.


4. Test the Live Environment

Before installing anything, spend several minutes inside the live Linux Mint environment.

Check that important hardware works:

  • keyboard;
  • mouse;
  • display;
  • Wi-Fi;
  • Ethernet;
  • audio;
  • touchpad;
  • external monitors if you use them.

This gives you an opportunity to detect obvious hardware problems before modifying the disk.


5. Start the Installation

Start the Linux Mint installer from the desktop.

You will be asked to configure things such as:

  • language;
  • keyboard layout;
  • timezone;
  • user account;
  • installation disk.

If this is a dedicated Linux machine and you have already backed up everything important, the simplest installation option is usually to allow Linux Mint to use the entire disk.

If you are configuring dual boot, pay much more attention to disk and partition selection.

Do not confirm disk changes until you understand which disk and partitions are being modified.


6. Create Your User

During installation, create your normal user account.

Choose:

  • a simple username;
  • a sensible hostname;
  • a strong password.

For example:

username: developer
hostname: dev-machine

Avoid unnecessarily complicated hostnames and usernames.

You will type and see them frequently.


7. Complete the Installation

When installation is complete:

  1. restart the computer;
  2. remove the installation USB when requested;
  3. boot into the installed Linux Mint system.

You now have your development operating system.


First System Update

One of the first things you should do after installation is update the system.

Open the terminal and run:

sudo apt update
sudo apt upgrade -y

The first command updates information about available packages.

The second installs available updates.

You will see sudo constantly in Linux.

Do not worry if you do not understand all of this yet.

We are going to cover the terminal separately.


Basic Development Packages

You can also install several tools that will be useful almost immediately:

sudo apt install -y \
    build-essential \
    git \
    curl \
    wget \
    ca-certificates \
    zip \
    unzip

This gives you several fundamental development and system tools.

We will configure Git properly in the dedicated Git section.


Do Not Be Afraid of the Terminal

If you are coming from Windows and have mostly used graphical applications, the Linux terminal may initially feel uncomfortable.

That is normal.

Do not avoid it.

The terminal will become one of your primary tools.

Eventually, operations such as:

cd
ls
mkdir
cp
mv
rm
grep
find
ps
kill
ssh
git

should feel completely normal.

You do not need to memorize everything immediately.

You need to use it.

Repeated use creates familiarity.


Do Not Try to Customize Everything Immediately

Another common beginner mistake is spending enormous amounts of time customizing the environment before doing any actual work.

You do not need:

  • twenty shell plugins;
  • a heavily customized terminal;
  • hundreds of aliases;
  • a custom window manager;
  • ten development environments;
  • a complicated dotfiles repository.

You need a working machine.

Start simple.

Your environment should evolve because you discover a real need.

Not because somebody else’s desktop screenshot looked impressive.


Your Initial Environment

At the end of this setup, your basic environment should look approximately like this:

        Development Machine
                │
                ├── Linux Mint
                │
                ├── Terminal
                │
                ├── Git
                │
                ├── Code Editor / IDE
                │
                ├── Browser
                │
                └── Development Toolchains

That is enough. Everything else can be added when it becomes necessary.


What Comes Next

Once Linux is installed, the next two things you need to understand are:

  1. The Linux Terminal
  2. Git

The terminal is how you will interact directly with a large part of your development environment.

Git is how you will manage source code, track changes, work with repositories, and eventually collaborate with other developers.

Neither of these is optional knowledge for the direction we are taking.

We will cover both separately. Do not rush through them. They will become part of your everyday work.

Fundamental Linux Terminal

The Linux terminal is one of the most important parts of a Linux development environment.

Almost everything that can be done through a graphical interface can also be performed from the terminal. In many engineering workflows, the terminal is faster, easier to automate, easier to reproduce, and more practical for remote systems.

From the terminal, you can:

  • install software and development tools;
  • create, copy, move, rename, and delete files and directories;
  • inspect files and directory trees;
  • modify permissions and ownership;
  • create users and groups;
  • archive and compress data;
  • redirect and combine command output;
  • execute scripts;
  • transfer files;
  • configure environment variables;
  • schedule work;
  • inspect system state;
  • manage packages;
  • work with remote machines.

The goal of this chapter is not to memorize every Linux command.

The goal is to become comfortable enough with the terminal that it stops feeling like a special tool and becomes a normal part of everyday engineering work.

Important: Many Linux commands can modify or permanently delete data. Read commands carefully before executing them, especially when using sudo, rm -rf, recursive permission changes, disk tools, or administrative commands.


Opening the Terminal

On Linux Mint and many other Linux desktop environments, a common shortcut is:

Ctrl + Alt + T

You can also open the terminal from the application menu or define your own shortcut in system settings.

A shell prompt may look similar to:

developer@machine:~$

The exact username, hostname, colors, and symbols depend on your environment.


Navigation and Directory Inspection

ls - List Directory Contents

The ls command lists files and directories in the current working directory.

ls

A more detailed listing can be shown with:

ls -la

Common options include:

OptionMeaning
-aInclude hidden entries beginning with .
-lUse long listing format
-dList directories themselves rather than their contents
-RList subdirectories recursively
-sShow allocated size in blocks
-tSort by modification time

Use:

ls --help

or:

man ls

to explore the full option list.


Understanding ls -l

Example:

drwxr-xr-x  3 developer developers 4096 Sep 17 12:10 project
-rw-r--r--  1 developer developers 3890 Sep 17 11:50 .bashrc

The first character describes the object type:

d    directory
-    regular file
l    symbolic link

The next nine characters describe permissions:

rwxr-xr-x

They are divided into three groups:

rwx  r-x  r-x
│    │    │
│    │    └── others
│    └────── group
└─────────── user / owner

Permission symbols are:

r    read
w    write
x    execute
-    permission not granted

The remaining columns normally show information such as:

  • number of hard links;
  • owner;
  • group;
  • size;
  • modification time;
  • file or directory name.

pwd - Print Working Directory

To see your current location:

pwd

Example:

/home/developer/projects

cd - Change Directory

Move into a directory:

cd projects

Move to an absolute path:

cd /home/developer/projects

Move one level upward:

cd ..

Move two levels upward:

cd ../..

Go to your home directory:

cd ~

or simply:

cd

Absolute and Relative Paths

An absolute path starts from the filesystem root:

/home/developer/projects/demo

A relative path starts from your current working directory:

projects/demo

Example:

cd /home/developer/projects/demo

and:

cd projects/demo

can point to the same location depending on your current directory.

Understanding the difference between absolute and relative paths is fundamental because almost every Linux command accepts paths.


Creating and Removing Directories

mkdir - Create Directories

Create one directory:

mkdir project

Create multiple directories:

mkdir project-a project-b project-c

Brace expansion can also be used:

mkdir {project-a,project-b,project-c}

Creating Parent Directories With mkdir -p

If intermediate directories do not yet exist:

mkdir -p projects/backend/api

Linux creates the missing parent directories as needed.

This works with both relative and absolute paths.


Creating a Directory With Permissions

mkdir -m can assign a mode when creating a directory:

mkdir -m 750 private-project

We will explain numeric permission modes in the next section.


rmdir - Remove Empty Directories

Remove an empty directory:

rmdir project

Remove multiple empty directories:

rmdir project-a project-b

rmdir does not remove non-empty directories.


rm - Remove Files and Directory Trees

Remove a file:

rm file.txt

Remove a directory recursively:

rm -r project

Useful options include:

OptionMeaning
-r, -RRemove recursively
-fForce; do not prompt for nonexistent files
-dRemove empty directories

Be Extremely Careful With rm -rf

This command:

rm -rf some-directory

can permanently remove an entire directory tree without confirmation.

Always verify the path first.

For learning, prefer:

rm -ri some-directory

when you want interactive confirmation.

Never copy and execute an rm -rf command without understanding exactly what it targets.


Permission Modes

Linux permissions can be represented symbolically:

rwxr-xr--

or numerically.

Values are:

r = 4
w = 2
x = 1

Examples:

rwx = 4 + 2 + 1 = 7
r-x = 4 + 0 + 1 = 5
rw- = 4 + 2 + 0 = 6
r-- = 4 + 0 + 0 = 4
--- = 0 + 0 + 0 = 0

Three numbers represent:

user  group  others

Examples:

777 = rwxrwxrwx
750 = rwxr-x---
644 = rw-r--r--
600 = rw-------

Do not automatically use 777.

Permissions should be only as broad as necessary.


Terminal Keyboard Shortcuts

Useful shell shortcuts include:

ShortcutAction
Ctrl + LClear the visible terminal screen
TabAutocomplete commands, paths, and filenames
Up ArrowPrevious command
Down ArrowNext command in history
Ctrl + PPrevious command
Ctrl + NNext command
Ctrl + AMove to beginning of line
Ctrl + EMove to end of line
Ctrl + LeftMove one word left
Ctrl + RightMove one word right
Ctrl + KCut from cursor to end of line
Ctrl + UCut from cursor to beginning of line
Ctrl + Shift + CCopy selected terminal text
Ctrl + Shift + VPaste into terminal
Ctrl + DSend EOF / exit an interactive shell

These shortcuts become extremely useful once the terminal becomes part of your daily workflow.


Working With Files

touch - Create Files

Create an empty file:

touch example.txt

Create multiple files:

touch file1.txt file2.txt file3.txt

You can use either relative or absolute paths:

touch /home/developer/example.txt

mv - Move and Rename

General form:

mv SOURCE DESTINATION

Move a file:

mv example.txt archive/

Move multiple files:

mv file1.txt file2.txt archive/

Rename a file:

mv old-name.txt new-name.txt

Useful options include:

OptionMeaning
-iAsk before overwriting
-fForce overwrite
-nDo not overwrite existing destination
-uMove only when source is newer or destination is missing
-vVerbose output

For learning and important files, -i can be useful:

mv -iv source destination

cp - Copy Files and Directories

General form:

cp [OPTIONS] SOURCE DESTINATION

Copy a file:

cp file.txt backup/

Copy several files:

cp file1.txt file2.txt backup/

Copy a directory recursively:

cp -R project/ backup/

Useful options include:

OptionMeaning
-aArchive mode; preserve attributes where possible
-fForce copy
-iAsk before overwrite
-nDo not overwrite
-R, -rRecursive copy
-uCopy when source is newer
-vVerbose output

Changing Permissions and Ownership

chmod - Change Permissions

Set numeric permissions:

chmod 750 script.sh

Add execute permission for the current user:

chmod u+x script.sh

Apply permissions recursively:

chmod -R 750 project/

Warning: Recursive permission changes can affect every file and directory below a path. Also remember that directories need execute (x) permission to be entered/traversed.


chown - Change Ownership

General form:

chown USER:GROUP FILE

Example:

sudo chown developer:developers file.txt

Recursive ownership change:

sudo chown -R developer:developers project/

Ownership changes usually require administrative privileges when changing objects to another user.


Editing Text With Nano

nano - Terminal Text Editor

Open a file:

nano file.txt

Use sudo nano only when the file genuinely requires administrative privileges:

sudo nano /etc/hosts

Useful Nano shortcuts:

ShortcutAction
Ctrl + XExit
Ctrl + OWrite/save file
Ctrl + WSearch
Ctrl + \Replace
Ctrl + KCut current line
Ctrl + UPaste from Nano cut buffer
Ctrl + CShow cursor position
Alt + GGo to line and column
Alt + UUndo
Alt + ERedo
Ctrl + ABeginning of line
Ctrl + EEnd of line
Ctrl + PPrevious line
Ctrl + NNext line

Standard Output and Redirection

Linux commands normally write output to streams.

The two most important are:

stdout    standard output
stderr    standard error

echo

Print text:

echo "Hello Linux World!"

Redirect output into a file:

echo "Hello Linux World!" > message.txt

Append to an existing file:

echo "Second line" >> message.txt

> - Overwrite Redirection

command > file.txt

The destination file is replaced with the new output.

Example:

echo "new content" > example.txt

>> - Append Redirection

command >> file.txt

New output is added to the end of the file.

Example:

echo "another line" >> example.txt

Redirecting Errors

Redirect standard error:

command 2> errors.txt

Append standard error:

command 2>> errors.txt

Redirect stdout and stderr into the same file:

command > output.txt 2>&1

Bash also supports:

command &> output.txt

Append both streams:

command &>> output.txt

Text Inspection Commands

cat

Print file content:

cat file.txt

Combine several files:

cat file1.txt file2.txt

Redirect combined output:

cat file1.txt file2.txt > combined.txt

wc

Count lines:

wc -l file.txt

You will also commonly see:

cat file.txt | wc -l

but the direct form is simpler when only one file is involved.


Show the first ten lines:

head file.txt

Show a specific number of lines:

head -n 5 file.txt

tail

Show the last ten lines:

tail file.txt

Show the last two lines:

tail -n 2 file.txt

Follow a growing log file:

tail -f application.log

diff

Compare two files:

diff file-a.txt file-b.txt

This is useful for quickly inspecting textual differences.


split

Split a file after a number of lines per output file:

split -l 100 large-file.txt

The original file remains unchanged.


Pipes

The pipe operator:

|

sends the output of one command into the input of another.

Example:

ls -la | grep ".txt"

Conceptually:

command A
    ↓ stdout
command B

Pipes are one of the most important ideas in Unix-like environments.

Small commands can be combined into larger workflows.


Shell Scripts

A shell script is a text file containing shell commands.

Create one:

nano script.sh

Example content:

#!/usr/bin/env bash

ls -la
pwd

Make it executable:

chmod u+x script.sh

Run it:

./script.sh

You can also explicitly invoke Bash:

bash script.sh

The .sh extension is conventional but not what makes a file executable. Permissions and the interpreter determine execution.


Archiving and Compression

tar

Create a tar archive:

tar -cvf archive.tar project/ file1.txt file2.txt

List archive contents:

tar -tf archive.tar

Extract:

tar -xvf archive.tar

tar.gz

Create a gzip-compressed tar archive:

tar -cvzf archive.tar.gz project/

Extract:

tar -xzvf archive.tar.gz

For less verbose output, omit v:

tar -czf archive.tar.gz project/
tar -xzf archive.tar.gz

gzip

Compress a file:

gzip archive.tar

Decompress:

gzip -d archive.tar.gz

Show gzip information:

gzip -l archive.tar.gz

Users and Groups

Inspect the Current User

whoami

Detailed identity information:

id

Show group membership:

groups

Create a User

On Linux Mint and other Debian/Ubuntu-based systems:

sudo adduser student

Add the user to the sudo group when administrative access is intentionally required:

sudo usermod -aG sudo student

Change a Password

sudo passwd student

Delete a User

Keep the user’s home directory:

sudo deluser student

Remove the user’s home directory too:

sudo deluser --remove-home student

Administrative user management can destroy user data. Verify the username before executing removal commands.


Create and Manage Groups

Create a group:

sudo groupadd developers

Append a user to a supplementary group:

sudo usermod -aG developers student

Inspect the group:

getent group developers

Change a user’s primary group:

sudo usermod -g developers student

Important User and Group Files

Linux user and group information is represented through system files including:

/etc/passwd
/etc/shadow
/etc/group
/etc/gshadow

Do not modify these files manually unless you understand the consequences.

For learning, inspect them read-only:

cat /etc/passwd
getent passwd
getent group

/etc/shadow contains protected password-related information and normally requires administrative access.


Soft and Hard Links

Create a symbolic link:

ln -s source.file softlink.file

A symbolic link stores a path to another object.

If the target disappears, the symbolic link becomes broken.

Symbolic links:

  • can cross filesystem boundaries;
  • can point to directories;
  • have their own inode;
  • refer to a path.

Create a hard link:

ln source.file hardlink.file

A hard link refers to the same underlying inode/data as the original directory entry.

Both names reference the same file data.

If one name is removed, the data remains accessible through the other name as long as another hard link still exists.

Hard links normally:

  • remain within the same filesystem;
  • cannot normally be created for directories by ordinary users;
  • share the same inode;
  • reflect content and permission changes because they refer to the same underlying file.

Inspect inode numbers with:

ls -li

Downloading Files With wget

wget is a non-interactive command-line download utility.

Example:

wget https://example.com/file.tar.gz

It supports common protocols such as HTTP and HTTPS and is useful in scripts and remote sessions.


Running Multiple Commands

;

Commands separated with ; are executed sequentially regardless of whether the previous command succeeded:

mkdir demo; cd demo; touch file.txt

&&

The next command runs only if the previous command succeeds:

mkdir demo && cd demo && touch file.txt

This is usually safer when later steps depend on earlier steps.


Multi-Line Commands

Use a backslash at the end of a shell line to continue:

mkdir project \
    && cd project \
    && touch README.md

Aliases

Aliases create short names for commands.

Temporary alias:

alias ll="ls -la"

List aliases:

alias

Remove one:

unalias ll

Permanent Bash Aliases

For Bash, add aliases to:

~/.bashrc

Example:

echo 'alias ll="ls -la"' >> ~/.bashrc

Reload:

source ~/.bashrc

Avoid aliases that make destructive commands easier to execute accidentally.

For example, an alias wrapping rm -rf is a poor default for a learning environment.


The $PATH Environment Variable

When you type:

git

or:

go

the shell needs to find the executable.

The $PATH variable contains directories that the shell searches.

Inspect it:

echo "$PATH"

Find the executable resolved for a command:

which git

A modern shell also provides:

command -v git

Common executable locations include:

/usr/bin
/usr/local/bin
/usr/sbin
/usr/local/sbin

Extending $PATH

Example:

export PATH="$PATH:$HOME/bin"

To persist it for Bash:

echo 'export PATH="$PATH:$HOME/bin"' >> ~/.bashrc
source ~/.bashrc

Always quote $PATH expansions in shell configuration when practical.


Package Management With APT

Linux Mint uses the Debian/Ubuntu package-management ecosystem.

Refresh package information:

sudo apt update

Upgrade installed packages:

sudo apt upgrade

Install a package:

sudo apt install git

Search:

apt search package-name

Remove:

sudo apt remove package-name

apt-get remains available and is widely used in scripts:

sudo apt-get update
sudo apt-get install git

For interactive use, apt is usually more convenient.


sudo

sudo allows an authorized user to execute commands with elevated privileges.

Example:

sudo apt update

Administrative privileges should be used only when necessary.

Do not place sudo in front of commands automatically.

Ask:

Does this operation really need root privileges?


About /etc/sudoers

The original Team413 terminal guide demonstrates passwordless sudo through a NOPASSWD sudoers entry.

For a normal learning workstation, this edition does not recommend disabling sudo password prompts globally.

If you ever need to modify sudo policy, use:

sudo visudo

rather than editing /etc/sudoers directly with a general-purpose editor.

visudo validates the configuration before saving and reduces the chance of breaking administrative access.


Use the Built-In Documentation

Linux commands usually document themselves.

Try:

command --help

or:

man command

Examples:

ls --help
man chmod
man tar
man usermod

Learning how to read command documentation is more valuable than trying to memorize every option.


Fundamental Command Checklist

You should become comfortable with at least these commands and concepts:

ls
pwd
cd
mkdir
rmdir
rm
touch
mv
cp
chmod
chown
nano
cat
echo
wc
head
tail
diff
split
tar
gzip
ln
wget
id
whoami
groups
adduser
usermod
groupadd
apt
sudo
man
--help
PATH
stdout
stderr
>
>>
2>
|
&&
;

You do not need to memorize them in one day.

Use them repeatedly.

The terminal becomes natural through repetition.


Final Perspective

The Linux terminal is not a list of commands.

It is an environment for combining small tools.

The real power appears when you understand:

files
+
paths
+
permissions
+
streams
+
processes
+
shell syntax
+
small commands

and begin combining them.

At that point, the terminal stops being something you “learn for Linux”.

It becomes one of the primary interfaces through which you understand and operate a computer.

Advanced Linux Terminal

Once basic terminal navigation, files, permissions, streams, users, packages, and shell execution are familiar, the next step is learning how to search, transform, automate, inspect, and control a Linux system from the command line.

This chapter focuses on:

  • grep and regular expressions;
  • sed;
  • awk;
  • password aging with chage;
  • find, locate, which, man, and --help;
  • process and network inspection;
  • scheduling with cron and at;
  • firewall configuration with UFW.

These tools are fundamental for backend engineering, DevOps, SysOps, infrastructure, server administration, debugging, and automation.

Important: Advanced terminal commands frequently operate over large file trees, user accounts, running processes, scheduled jobs, and network access. Test commands in a controlled environment before using them on production systems.


Searching Text With grep

grep searches text for lines that match a pattern.

Basic example:

grep "hello" test.txt

The same idea can be expressed through a pipe:

cat test.txt | grep "hello"

When reading a file directly, the first form is simpler:

grep "hello" test.txt

Useful grep Patterns

Count matching lines:

grep -c "hello" test.txt

Lines beginning with hello:

grep '^hello' test.txt

Lines ending with hello:

grep 'hello$' test.txt

Case-insensitive search:

grep -i 'linuxacademy' test.txt

Invert the match:

grep -v '^#' test.txt

Match either lowercase or uppercase first character:

grep '[lL]inuxacademy' test.txt

Extended regular expressions:

grep -E 'hello.*world' test.txt

Case-insensitive extended expression:

grep -Ei 'hello.*world' test.txt

Logical OR:

grep -Ei 'hello|world' test.txt

Historically, egrep was commonly used for extended regular expressions. Modern usage generally prefers:

grep -E

instead of:

egrep

Regular Expressions Overview

Regular expressions describe text patterns.

Common symbols include:

ExpressionMeaning
.Any single character
?Previous element appears zero or one time
*Previous element appears zero or more times
+Previous element appears one or more times
{n}Exactly n repetitions
{n,m}Between n and m repetitions
[abc]One character from the set
[^abc]One character not in the set
[a-z]Character range
()Grouping
``
^Beginning of line
$End of line

Example:

grep -E '^(error|warning):' application.log

Regular expressions are used by many Linux tools, not only grep.


sed - Stream Editor

sed is a stream editor used for operations such as:

  • searching;
  • substitution;
  • insertion;
  • deletion;
  • filtering.

General syntax:

sed [OPTIONS] 'SCRIPT' INPUT_FILE

Replace the First Match on Each Line

sed 's/unix/linux/' file.txt

This prints transformed output.

It does not modify the file unless an in-place option is used.


Replace the Second Match

sed 's/unix/linux/2' file.txt

Replace All Matches on Each Line

sed 's/unix/linux/g' file.txt

Replace From the Third Match Onward

sed 's/unix/linux/3g' file.txt

Replace on a Specific Line

sed '3s/unix/linux/' file.txt

Replace Within a Line Range

sed '1,3s/unix/linux/' file.txt

From line 2 to the end:

sed '2,$s/unix/linux/' file.txt

sed -n 's/unix/linux/p' file.txt

Delete Lines

Delete line 3:

sed '3d' file.txt

Delete the last line:

sed '$d' file.txt

Delete a range:

sed '2,3d' file.txt

Delete from line 3 to the end:

sed '3,$d' file.txt

Delete lines containing a pattern:

sed '/unix/d' file.txt

In-Place Editing

When you intentionally want to modify the file:

sed -i 's/unix/linux/g' file.txt

For important data, create a backup first:

cp file.txt file.txt.backup
sed -i 's/unix/linux/g' file.txt

Or use GNU sed backup suffix syntax:

sed -i.bak 's/unix/linux/g' file.txt

awk - Pattern Scanning and Data Processing

awk is a small programming language designed for text and structured field processing.

It can:

  • scan input line by line;
  • split lines into fields;
  • match patterns;
  • perform actions;
  • calculate values;
  • generate reports;
  • use variables;
  • use conditions and loops.

General form:

awk 'PATTERN { ACTION }' FILE

awk '{print}' employee.txt

awk '/manager/ {print}' employee.txt

Fields

By default, whitespace separates fields.

For a line like:

ajay manager account 45000

the fields are:

$1 = ajay
$2 = manager
$3 = account
$4 = 45000
$0 = entire line

Print name and salary:

awk '{print $1, $4}' employee.txt

Important awk Built-In Variables

NR

Current record/line number:

awk '{print NR, $0}' employee.txt

NF

Number of fields in the current record.

Print the first and last fields:

awk '{print $1, $NF}' employee.txt

FS

Input field separator.

Example CSV-like separator:

awk -F',' '{print $1, $3}' file.csv

OFS

Output field separator:

awk 'BEGIN {OFS=" | "} {print $1, $4}' employee.txt

RS

Input record separator.

The default is a newline.


ORS

Output record separator.

The default is a newline.


More awk Examples

Print lines 3 through 6:

awk 'NR==3,NR==6 {print NR, $0}' employee.txt

Print the second field:

awk '{print $2}' employee.txt

Print non-empty lines:

awk 'NF > 0' employee.txt

Find the longest line length:

awk '{if (length($0) > max) max = length($0)} END {print max}' employee.txt

Count lines:

awk 'END {print NR}' employee.txt

Print lines longer than 25 characters:

awk 'length($0) > 25' employee.txt

chage - Password Aging

chage manages password-expiration and account-aging information.

Inspect a user:

sudo chage -l student

Common options include:

OptionMeaning
-dLast password-change date
-EAccount expiration date
-IInactive days after password expiration
-lList aging information
-mMinimum days between password changes
-MMaximum password age
-WWarning days before expiration

Examples:

Set account expiration:

sudo chage -E 2027-01-31 student

Maximum password age of 90 days:

sudo chage -M 90 student

Remove account expiration:

sudo chage -E -1 student

Force a password change at next login:

sudo chage -d 0 student

Set a warning five days before expiration:

sudo chage -W 5 student

Account policy is an administrative and security decision. Do not apply expiration rules blindly.


Finding Files and Commands

find

General form:

find START_PATH EXPRESSIONS

Find by name:

find /home/developer -name 'test7000'

Find Go files:

find ./project -name '*.go'

Always quote wildcard patterns so that the shell does not expand them before find receives them.


Limit Search Depth

Maximum depth:

find ./project -maxdepth 2 -name '*.go'

Minimum depth:

find ./project -mindepth 2 -name '*.go'

Search Only Files

find ./project -type f -name 'abc*'

Only directories:

find ./project -type d -name 'abc*'

Search by Other Properties

Empty objects:

find ./project -empty

Files owned by a user:

find ./project -user developer

Files with exact permissions:

find ./project -perm 664

Files newer than another file:

find ./project -newer reference.file

find -exec

Run a command on results:

find ./project -type f -name '*.txt' -exec grep 'error' {} \;

Delete matching files interactively:

find ./project -type f -name 'sample.txt' -exec rm -i {} \;

Be extremely careful when combining find with deletion or permission changes.

First inspect the result:

find ./project -type f -name '*.tmp' -print

Only after confirming the target set should you consider a modifying command.


locate

locate searches an indexed database rather than walking the filesystem in real time.

Example:

locate test7000

Limit results:

locate -n 20 '*.html'

Ignore case:

locate -i 'readme.md'

Because locate depends on an index, very recent filesystem changes may not appear until the database is refreshed.


which, command -v, man, and --help

Locate a command found through $PATH:

which go

A shell-friendly alternative:

command -v go

Read manual pages:

man find

Command help:

find --help

These tools should become part of your normal workflow.

Do not memorize every option.

Learn how to discover the option you need.


Processes and Network Inspection

ps

Inspect running processes:

ps aux

Search within them:

ps aux | grep postgres

kill

Send the default termination signal:

kill PID

Example:

kill 1512

A forceful termination:

kill -9 1512

SIGKILL (-9) should not be the first choice.

It prevents the target process from performing normal cleanup.

Try normal termination first.


Listening Ports and Sockets

The original guide uses netstat.

It may still be available through the net-tools package:

sudo netstat -plntu

On modern Linux systems, ss is generally preferred:

sudo ss -plntu

Inspect port 80:

sudo ss -plnt | grep ':80'

lsof

Inspect processes using a port:

sudo lsof -i :5432

Extract a PID using awk:

sudo lsof -i :5432 | grep LISTEN | awk '{print $2}'

When scripting, prefer robust machine-readable output when a tool provides it instead of depending heavily on column formatting.


Network Interfaces

The original guide uses:

ifconfig

On modern Linux, use:

ip addr

or:

ip a

Routes:

ip route

The legacy ifconfig command may still exist when net-tools is installed.


/etc/hosts

Local hostname mappings are stored in:

/etc/hosts

Inspect:

cat /etc/hosts

Edit only when required:

sudo nano /etc/hosts

Incorrect entries can break local name resolution.


Memory Inspection

Display memory usage:

free -m

Human-readable format:

free -h

Linux intentionally uses available memory for filesystem caching.

High cache usage does not automatically mean the machine has a memory problem.

The original guide demonstrates manually dropping kernel caches through /proc/sys/vm/drop_caches.

That should not be used as routine memory optimization.

For normal development and administration, allow the Linux kernel to manage caches unless you are performing a controlled benchmark or diagnostic procedure and understand why cache dropping is necessary.


Scheduling Repeated Work With cron

Cron executes commands on recurring schedules.

Edit your user crontab:

crontab -e

General syntax:

MINUTE HOUR DAY_OF_MONTH MONTH DAY_OF_WEEK COMMAND

Visual form:

* * * * * command
│ │ │ │ │
│ │ │ │ └── day of week
│ │ │ └──── month
│ │ └────── day of month
│ └──────── hour
└────────── minute

Cron Examples

Every day at 03:00:

0 3 * * * /path/to/backup.sh

Five minutes after midnight every day:

5 0 * * * /path/to/command

At 14:15 on the first day of every month:

15 14 1 * * /path/to/script.sh

At 22:00 Monday through Friday:

0 22 * * 1-5 /path/to/script.sh

Every two hours at minute 23:

23 */2 * * * /path/to/script.sh

Sunday at 04:05:

5 4 * * 0 /path/to/command

Cron Special Strings

ExpressionMeaning
@rebootOnce at startup
@yearlyOnce per year
@annuallySame as @yearly
@monthlyOnce per month
@weeklyOnce per week
@dailyOnce per day
@midnightSame as @daily
@hourlyOnce per hour

Example:

@daily /path/to/backup.sh

Removing Cron Jobs

List jobs:

crontab -l

Edit selectively:

crontab -e

Remove all jobs for the current user:

crontab -r

Warning: crontab -r removes the entire crontab. Prefer crontab -e when you only need to remove one entry.


One-Time Scheduling With at

at schedules a command for one future execution.

Install when needed:

sudo apt update
sudo apt install at

Schedule a command for 09:00:

echo "command_to_be_run" | at 09:00

One hour from now:

at now + 1 hour

At 13:00 two days from now:

at 1pm + 2 days

Inspect available jobs:

atq

Remove a scheduled job:

atrm JOB_ID

Use:

man at

for supported time formats.


UFW - Uncomplicated Firewall

UFW provides a simpler interface for Linux firewall configuration.

Install:

sudo apt update
sudo apt install ufw

Check status:

sudo ufw status

Verbose status:

sudo ufw status verbose

Default Policy

A common server policy is:

sudo ufw default deny incoming
sudo ufw default allow outgoing

This blocks unsolicited incoming connections unless explicitly allowed.


SSH Safety

Before enabling a firewall on a remote machine, make sure your SSH access is allowed.

Standard SSH:

sudo ufw allow ssh

Equivalent default port:

sudo ufw allow 22/tcp

If SSH runs on a custom port:

sudo ufw allow 2222/tcp

Critical: Enabling a firewall on a remote server without allowing the management connection can lock you out of the machine.


Allow HTTP

sudo ufw allow 80/tcp

HTTPS:

sudo ufw allow 443/tcp

Port Ranges

TCP:

sudo ufw allow 1000:2000/tcp

UDP:

sudo ufw allow 1000:2000/udp

Allow a Specific Source Address

sudo ufw allow from 192.168.1.50

Allow a source only to SSH:

sudo ufw allow from 192.168.1.50 to any port 22 proto tcp

Allow a subnet:

sudo ufw allow from 192.168.1.0/24

Deny Traffic

sudo ufw deny 80/tcp

Use deny rules deliberately and understand rule ordering.


Delete Rules

Delete by rule expression:

sudo ufw delete allow 80/tcp

Or list numbered rules:

sudo ufw status numbered

Then remove one:

sudo ufw delete RULE_NUMBER

Enable and Disable

Enable:

sudo ufw enable

Disable:

sudo ufw disable

Reset all UFW rules:

sudo ufw reset

reset removes your existing UFW configuration. Use it only when that is intentional.


Combining Advanced Tools

The real value of terminal knowledge appears when commands are combined.

Example:

sudo lsof -i :5432 \
    | grep LISTEN \
    | awk '{print $2}'

Conceptually:

inspect sockets
      ↓
filter listening entry
      ↓
extract PID field

Another example:

find ./logs -type f -name '*.log' \
    -exec grep -H 'ERROR' {} \;

Another:

grep -E '^ERROR|^WARN' application.log \
    | awk '{print $1, $2, $0}'

This is the Unix philosophy in practice:

Build larger workflows by combining small tools.


Advanced Command Checklist

You should become comfortable with:

grep
grep -E
regular expressions
sed
awk
NR
NF
FS
OFS
chage
find
locate
which
command -v
man
--help
ps
kill
ss
netstat
lsof
ip
free
cron
crontab
at
atq
atrm
ufw

You do not need to memorize every syntax form.

You need to understand what class of problem each tool solves and how to find its documentation.


Final Perspective

Advanced terminal work is not about typing complicated commands to look experienced.

It is about understanding how Linux represents:

text
files
processes
users
time
network sockets
permissions
system state

and then using small tools to inspect and manipulate those objects precisely.

A strong engineer should eventually be able to enter a remote Linux machine with nothing more than a shell and begin answering questions such as:

What process is running?
Which port is it using?
Who owns the file?
Where is the executable?
Which lines match this pattern?
Which files changed?
What job runs at 03:00?
Which firewall rule blocks the connection?

That ability is one of the foundations of backend, infrastructure, DevOps, SysOps, and cloud engineering.

Fundamentals of Git

Git is a distributed version control system used to track changes in files and to manage the history of a software project.

In software engineering, Git allows us to:

  • create repositories;
  • track changes over time;
  • create checkpoints of development;
  • compare different states of a project;
  • create independent branches of development;
  • merge completed work;
  • undo changes;
  • inspect project history;
  • recover previous states;
  • temporarily store unfinished work;
  • mark important versions with tags.

Git is not only a tool for collaboration.

Even when you are working completely alone, Git gives you something extremely important:

A structured history of how your project changed.

That history allows you to experiment without constantly being afraid of destroying the previous working state.


Installing Git on Linux Mint

On Linux Mint, Git can be installed directly through APT:

sudo apt update
sudo apt install git

Verify the installation:

git --version

Display general Git help:

git help

Useful built-in help commands include:

git help -a
git help -g
git help init
git help commit
git help branch
git help merge

You can also use:

git <command> --help

For example:

git commit --help
git status --help
git log --help

Do not try to memorize every Git flag.

Learn the concepts first.

Then use Git’s own documentation when you need a specific option.


Initial Git Configuration

Before creating commits, configure the identity that Git will store in commit metadata.

Set your name:

git config --global user.name "Your Name"

Set your email:

git config --global user.email "you@example.com"

Inspect global configuration:

git config --global --list

Your global Git configuration is normally stored in:

~/.gitconfig

You can inspect it with:

cat ~/.gitconfig

or:

nano ~/.gitconfig

You normally do not need to create this file manually. Git creates or updates it when configuration values are written.


Creating a Working Directory

For the examples in this guide, create a directory that will contain Git repositories:

mkdir -p ~/gitLib
cd ~/gitLib

Now create the first project:

mkdir first_repo
cd first_repo

git init - Initialize a Repository

A normal directory is not automatically a Git repository.

To initialize Git inside the current directory:

git init

Example output may look similar to:

Initialized empty Git repository in /home/developer/gitLib/first_repo/.git/

Inspect hidden files:

ls -la

You will now see:

.git/

The .git directory contains Git’s repository metadata and object database.

That directory is what turns the project directory into a Git repository.

Do not manually edit or delete files inside .git unless you understand Git internals and know exactly what you are doing.


The Fundamental Git Cycle

A basic Git workflow looks like this:

Initialize repository
        ↓
Modify files
        ↓
Inspect status
        ↓
Stage selected changes
        ↓
Commit staged changes
        ↓
Inspect history
        ↓
Continue development

The most important commands at the beginning are:

git init
git status
git add
git commit
git log

Everything else builds on these concepts.


Working Tree, Staging Area, and Repository

Before going further, understand the three main states involved in normal Git work.

Working Tree
    ↓
Staging Area
    ↓
Repository History

The working tree contains the files you are currently editing.

The staging area contains the exact changes selected for the next commit.

The repository history contains commits that have already been recorded.

A useful mental model is:

edit
  ↓
git add
  ↓
stage
  ↓
git commit
  ↓
history

git status

Immediately after initialization:

git status

A new repository may report:

On branch master

No commits yet

nothing to commit

Depending on your Git configuration, the default branch may be named master or main.

This guide uses:

master

because that is the branch name used throughout the original Team413 material.

The Git concepts are identical regardless of the branch name.


Creating an Untracked File

Create a file:

touch message1.txt

Now inspect status:

git status

Git will report the file as untracked.

Conceptually:

message1.txt
    ↓
exists in working tree
    ↓
Git is not tracking it yet

git add - Add Changes to the Staging Area

Stage one file:

git add message1.txt

Inspect status:

git status

The file is now prepared for the next commit.

You can stage all current changes with:

git add .

But do not use git add . automatically without looking at what changed.

A better habit is:

git status
git diff
git add <specific-files>

when you want precise control over the next commit.


Removing a File From the Staging Area

The original guide demonstrates:

git rm --cached message1.txt

That command removes the file from Git’s index while leaving the working-tree file present.

For a file that you merely staged accidentally and still want Git to track later, modern Git also provides:

git restore --staged message1.txt

The important concept is:

Staging can be changed before committing.

A commit should contain the exact change you intend to record.


git commit - Create a Checkpoint

A commit records the staged state of the project.

Think of a commit as a checkpoint.

Stage the file:

git add message1.txt

Create the first commit:

git commit -m "Our first system message"

Git creates a commit with its own unique identifier.

Afterward:

git status

should show:

nothing to commit, working tree clean

The Three Common Change States

A tracked file may conceptually move through:

modified
    ↓
staged
    ↓
committed

Modified - The file differs from the last committed version.

Staged - The change has been selected for the next commit.

Committed - The staged change has been stored in repository history.


Short Status

Use:

git status -s

or:

git status --short

Typical status letters include:

??    untracked
M     modified
A     added
D     deleted
R     renamed

The short format is useful once you understand what the symbols represent.


git log - Commit History

Display commit history:

git log

A normal entry contains:

  • commit ID;
  • author;
  • timestamp;
  • commit message.

A compact view:

git log --oneline

Example:

0ef0242 Our first system message

The value:

0ef0242

is a shortened form of the commit ID.

Git commit IDs allow us to reference exact historical states.


Limiting Log Output

Show the last three commits:

git log -n 3 --oneline

Show commits between two revisions:

git log <older>..<newer> --oneline

Example:

git log d110129..74d91c --oneline

Visualizing History

A very useful command is:

git log --oneline --decorate --graph --all

This shows:

  • compact commit IDs;
  • branch and tag names;
  • branch relationships;
  • the commit graph.

Use this frequently while learning branches and merges.


Branches

A branch represents an independent line of development.

Branches allow us to isolate:

  • new features;
  • bug fixes;
  • experiments;
  • refactoring;
  • temporary work.

A common workflow is:

stable branch
      ↓
create feature branch
      ↓
develop and commit
      ↓
test
      ↓
merge
      ↓
remove feature branch when finished

Listing Branches

git branch

The active branch is marked with:

*

Example:

* master

Creating a Branch

git branch feature/task2

List again:

git branch

Example:

  feature/task2
* master

The branch exists, but we have not switched to it yet.


Switching Branches

The original guide uses:

git checkout feature/task2

Modern Git also provides:

git switch feature/task2

Both concepts mean:

Move HEAD to another branch and update the working tree to that branch’s state.


Create and Switch in One Command

Using checkout:

git checkout -b feature/task3

Modern equivalent:

git switch -c feature/task3

Rename a Branch

git branch -m old-name new-name

Example:

git branch -m small-feature quick-feature

Commit Changes on a Feature Branch

Switch to the feature branch:

git checkout feature/task2

Modify a file:

echo "This is the first system message!" >> message1.txt

Inspect:

git status

Stage:

git add message1.txt

Commit:

git commit -m "First system message has been modified"

Inspect history:

git log --oneline --decorate --graph --all

You should now see that the feature branch contains a commit that the original branch does not yet contain.


git diff Between Branches

Before merging, inspect the difference:

git diff master feature/task2

Git displays what changes would distinguish one branch state from the other.


Merging a Branch

To merge feature/task2 into master, first switch to the branch that should receive the changes:

git checkout master

Then:

git merge feature/task2

This direction matters.

Conceptually:

current branch
    +
incoming branch
    ↓
merged current branch

Fast-Forward Merge

If master has not changed since the feature branch was created, Git may perform a fast-forward merge.

Before:

A---B   master
     \
      C---D   feature

After fast-forward:

A---B---C---D   master, feature

Git does not need a new merge commit.

It simply moves the master branch pointer forward.


Deleting a Merged Branch

After a feature has been merged:

git branch -d feature/task2

The lowercase -d performs a safe deletion and refuses when the branch contains work that Git considers unmerged.

Force deletion:

git branch -D feature/task2

Use -D carefully. It can remove a branch that contains commits not reachable from another branch.


git commit -a

For already tracked files, Git can stage modifications and deletions as part of commit:

git commit -am "Fourth system message has been added"

This is effectively useful for tracked files only.

It does not automatically include brand-new untracked files.

For new files, use:

git add new-file
git commit -m "Add new file"

Creating Several Commits

Create another modification:

echo "Second system message: Welcome!" >> message1.txt
git add message1.txt
git commit -m "Second system message has been added"

Then another:

echo "Third system message: Analyze!" >> message1.txt
git add message1.txt
git commit -m "Third system message has been added"

And another:

echo "Fourth system message: Proceed!" >> message1.txt
git commit -am "Fourth system message has been added"

Inspect:

git log --oneline

Now the project history contains multiple checkpoints.


Checking Out a Specific Commit

Suppose the history contains:

e0c20e6 Fourth system message has been added
24af2fa Third system message has been added
aba3bd9 Second system message has been added
650bf2c First system message has been modified
0ef0242 Our first system message

To inspect the repository at the second-message commit:

git checkout aba3bd9

Git will enter a detached HEAD state.


Understanding HEAD

HEAD represents the currently checked-out position.

Normally:

HEAD
 ↓
branch
 ↓
commit

For example:

HEAD -> master -> e0c20e6

In detached HEAD state:

HEAD -> aba3bd9

HEAD points directly to a commit rather than to a normal branch.


Detached HEAD

Detached HEAD is useful for:

  • inspecting old states;
  • testing old commits;
  • temporary experiments;
  • comparing historical versions.

Example:

git status

may show:

HEAD detached at aba3bd9

The working tree now represents the selected historical commit.


Creating Commits in Detached HEAD

You can modify files and commit while detached.

For example:

echo "Experimental message" >> message1.txt
git commit -am "Experimental detached commit"

But that commit is not automatically attached to a normal branch.

If you later switch away, Git may warn that you are leaving the commit behind.

To preserve it, create a branch:

git branch experiment <commit-id>

or while currently detached:

git switch -c experiment

Creating a Branch From an Older Commit

Checkout the historical commit:

git checkout aba3bd9

Create a new branch from that point:

git checkout -b feature/task5

Now any new commits belong to feature/task5.

This is one of Git’s most powerful properties:

Any historical commit can become the starting point of a new line of development.


git revert - Safely Undo a Commit

Suppose the latest commit added something that should be removed.

Use:

git revert HEAD

Git creates a new commit that reverses the effect of the selected commit.

History remains intact.

Conceptually:

A---B---C
        ↓
   incorrect change

git revert C

A---B---C---D
            ↓
       inverse of C

Both the original commit and the revert remain visible.

This makes revert especially useful when history should remain traceable.


Revert a Specific Commit

git revert <commit-id>

Example:

git revert 140c7a6

git reset - Move Repository State

git reset is different from git revert.

Revert adds a new inverse commit.

Reset moves a branch reference and can change the staging area and working tree depending on the mode.

This makes reset powerful, but potentially destructive.


Unstage Changes With Reset

A plain:

git reset

moves staged changes out of the staging area while normally leaving the working-tree modifications present.

Conceptually:

staged
  ↓
git reset
  ↓
modified but unstaged

Modern Git also provides:

git restore --staged <file>

for explicit unstaging.


Reset to a Commit

git reset <commit-id>

By default this is a mixed reset.

The branch moves to the specified commit, while later file changes normally remain in the working tree as unstaged modifications.


git reset --hard

Example:

git reset --hard 24af2fa

This moves the branch and resets both:

  • staging area;
  • working tree.

Changes after the target state may disappear from the visible branch and working tree.

Treat git reset --hard as destructive. Always inspect git status and git log first.

Do not use it casually on work you have not protected.


Revert vs Reset

A simple comparison:

OperationHistoryWorking PrincipleTypical Use
git revertPreservedAdd inverse commitSafe history-preserving undo
git resetRepositionedMove branch/referenceLocal history manipulation
git reset --hardRepositionedMove branch and discard working/staged stateExplicit destructive reset

A useful rule:

Use revert when history should remain intact. Use reset when you intentionally want to rewrite or reposition local history.


git clean - Remove Untracked Files

git reset primarily affects tracked repository state.

git clean deals with untracked files.

Preview first:

git clean -n

or:

git clean --dry-run

This is extremely important.

It shows what Git would delete.


Remove Untracked Files

git clean -f

Remove untracked directories too:

git clean -df

Remove ignored files as well:

git clean -xf

git clean -xf is dangerous. It can delete build output, local configuration, generated data, and other ignored files.

Always run a dry-run variant first.


Merge With --no-ff

A normal fast-forward merge may not create a dedicated merge commit.

Sometimes you intentionally want the branch integration to remain visible in history.

Use:

git merge feature/task4 --no-ff

This forces a merge commit even when Git could fast-forward.

Example history:

*   Merge branch 'feature/task4'
|\
| * Feature commit 2
| * Feature commit 1
|/
* Previous master commit

This makes the existence of the feature branch explicit in the project history.


Fast-Forward vs --no-ff

Fast-forward:

A---B---C---D

Forced merge commit:

A---B-------M
     \     /
      C---D

Neither is universally correct.

The choice depends on how you want project history to communicate development structure.


Amend the Last Commit

Sometimes the last commit needs adjustment.

You may have:

  • forgotten to include a file;
  • written the wrong commit message;
  • staged one more correction immediately after committing.

Use:

git commit --amend

To replace only the message directly:

git commit --amend -m "Corrected commit message"

To add forgotten changes:

git add forgotten-file
git commit --amend

Important Property of Amend

Amending does not modify the existing commit in place.

It creates a new commit object with a new commit ID.

Conceptually:

old commit: 66ee579
        ↓ amend
new commit: 0229304

The visible history now points to the replacement commit.

This is history rewriting.

For local work, that is often fine.

Be careful when rewriting commits that other people may already depend on.


Rebase

Rebase rewrites one line of commits so that it appears to start from another base commit.

Suppose history looks like this:

A---B---C   master
     \
      D---E   feature

After rebasing feature onto master:

A---B---C---D'---E'   feature

D' and E' are new commits representing the replayed changes.

The original commit IDs change.


Why Rebase?

Rebase is often used to maintain a linear history.

Instead of creating:

A---B---C
     \   \
      D---E---M

you can replay feature work after the current base:

A---B---C---D'---E'

Basic Rebase

Switch to the feature branch:

git checkout feature/task5

Rebase onto master:

git rebase master

Git identifies commits that belong to the feature branch and replays them on top of the current master.


Rebase Can Produce Conflicts

If both histories changed overlapping parts of the same content, Git may stop with a conflict.

Example:

CONFLICT (content): Merge conflict in message1.txt

Inspect state:

git status

Open the conflicted file.

Git conflict markers may look like:

<<<<<<< HEAD
content from one side
=======
content from the other side
>>>>>>> commit

You must decide what the final content should be.


Resolve a Rebase Conflict

  1. Edit the conflicted file.
  2. Remove conflict markers.
  3. Keep the correct final content.
  4. Stage the resolved file.
git add message1.txt

Continue:

git rebase --continue

Abort a Rebase

If you want to return to the state before rebase started:

git rebase --abort

This is one of the most important rebase commands to remember.


Skip a Rebase Commit

Git also provides:

git rebase --skip

This skips the commit currently being replayed.

Use it only when you intentionally want to discard that commit’s change from the rebased history.


Rebase Conflict Principle

Editing the same file on different branches is completely normal.

A conflict does not happen merely because the same file changed.

Conflicts usually appear when Git cannot automatically reconcile overlapping changes.

For example:

branch A modifies line 20
branch B also modifies line 20

may require manual resolution.

While:

branch A modifies line 20
branch B modifies line 200

may merge or rebase automatically.

The important skill is not avoiding all concurrent edits.

The important skill is understanding how to resolve conflicting changes safely.


Rebase Changes Commit IDs

Because rebase creates new commit objects, commit IDs before and after rebase differ.

Before:

5ffabe2 Fifth system message
6224156 Sixth system message

After replay:

5e110cf Fifth system message
4aa3940 Sixth system message

The logical changes may be similar.

The commits are new objects.


Merge After Rebase

If a feature branch has been successfully rebased onto the current master, merging it may become a fast-forward:

git checkout master
git merge feature/task5

Then remove the completed branch:

git branch -d feature/task5

git reflog

Git’s normal log shows commits reachable through normal references.

reflog records how local references such as HEAD moved over time.

Run:

git reflog

Example entries may represent:

  • commits;
  • checkouts;
  • resets;
  • rebases;
  • branch switches.

Conceptually:

HEAD@{0}    current position
HEAD@{1}    previous movement
HEAD@{2}    movement before that

Why Reflog Matters

You may run:

git reset --hard <older-commit>

and think a newer local commit has disappeared.

But Git may still record the previous HEAD movement in reflog.

Inspect:

git reflog

Then inspect a previous state:

git show HEAD@{1}

or:

git checkout HEAD@{1}

A safer recovery approach is often to create a branch:

git branch recovery HEAD@{1}

Reflog Time Expressions

Git accepts several time expressions for reflog references.

Examples:

git show master@{1.hour.ago}
git show master@{1.day.ago}
git show master@{2.weeks.ago}

Display reflog-style history:

git log -g master

Reflog is local repository metadata.

It is an extremely useful recovery mechanism, but it should not be treated as a permanent backup system.


Tags

Tags mark important commits with meaningful names.

Typical uses include:

  • releases;
  • release candidates;
  • milestones;
  • stable checkpoints.

Example:

git tag v1.7-rc1

List tags:

git tag

or:

git tag --list

Show a Tag

git show v1.7-rc1

Git displays the commit associated with the tag and related information.


Lightweight Tags

A lightweight tag is essentially a name pointing directly to a commit.

Create one:

git tag v1.4-lw

Annotated Tags

Annotated tags create a tag object containing additional metadata.

Create:

git tag -a v1.4 -m "Version 1.4"

Annotated tags are useful when you want the tag itself to carry:

  • tagger information;
  • timestamp;
  • message.

Inspect Tag Object Type

Use:

git cat-file -t <tag>

Example:

git cat-file -t v1.4-lw

A lightweight tag commonly resolves directly to:

commit

An annotated tag resolves to:

tag

Find Tags

List matching tags:

git tag -l "v1.8*"

or:

git tag --list "v1.8*"

Describe Current State

git describe --all

Include tags:

git describe --all --tags

This is useful for connecting the current repository state to nearby named references.


Compare Tags

Tags can be used anywhere Git accepts revisions.

Example:

git diff v1.7-rc1 v1.8-rc2

This makes tags useful for release comparisons.


Move a Tag

Force a local tag to another commit:

git tag -f v3.1.0-beta <commit-id>

Delete a local tag:

git tag -d v3.1.0-beta

Be careful when moving meaningful release tags.

A tag is often treated as a stable reference.


Create a Branch From a Tag

git checkout -b branch-v1.8 v1.8-rc1

Modern equivalent:

git switch -c branch-v1.8 v1.8-rc1

This creates a new line of development from the tagged commit.


git stash

Sometimes you are in the middle of unfinished work and need to switch context.

You do not want to create a meaningless half-finished commit.

That is where stash is useful.

git stash temporarily stores uncommitted changes and restores the working tree toward the committed state.

Conceptually:

unfinished working changes
        ↓
git stash
        ↓
clean working tree
        ↓
do other work
        ↓
restore stash

Create a Stash

Modify a tracked file:

echo "For every connection we use SSL!" >> programming-language.txt

Then:

git stash

List stashes:

git stash list

Example:

stash@{0}: WIP on master: b854bf3 ...

Named Stash

A clear modern form is:

git stash push -m "database changes"

The original guide uses:

git stash save "database changes"

git stash save exists in older workflows, but git stash push -m is clearer for current Git usage.


Stash Indexing

Stashes are indexed:

stash@{0}
stash@{1}
stash@{2}

The newest stash is normally:

stash@{0}

If one is removed, the remaining indices may shift.

Do not assume an old index remains attached to the same stash forever.

Check:

git stash list

first.


Inspect a Stash

git stash show stash@{1}

Show a patch:

git stash show -p stash@{1}

Apply a Stash

Apply a specific stash without removing it from the stash list:

git stash apply stash@{1}

Apply the newest stash:

git stash apply

After application:

git status

will show the restored changes.


Pop a Stash

Apply and remove it:

git stash pop stash@{0}

Conceptually:

apply
+
drop

Drop a Stash

git stash drop stash@{1}

Delete all stashes:

git stash clear

git stash clear removes the entire stash stack. Check git stash list first.


Stash Untracked Files

By default, stash focuses on tracked modifications and staged changes.

Include untracked files:

git stash -u

or:

git stash push -u -m "work in progress"

Create a Branch From a Stash

Git can create a branch starting from the commit where the stash was originally created and apply the stash there:

git stash branch feature/task94 stash@{0}

This is useful when unfinished work has grown into something that deserves its own branch.


Stash Can Conflict

Applying a stash may conflict with changes already present in the current working tree.

For example:

error: Your local changes would be overwritten

At that point you may need to:

  • commit current changes;
  • stash current changes;
  • resolve overlaps manually;
  • reset only when you intentionally want to discard the current work.

Do not immediately use:

git reset --hard

unless you have confirmed that the current changes can be destroyed.


git diff

git diff compares Git data sources and displays differences.

It is commonly used together with:

git status
git log

Use git status to see what changed.

Use git diff to see how it changed.


Working-Tree Diff

Modify a file:

echo "New service for Payment!" >> programming-language.txt

Then:

git diff

Git displays the unstaged change.


List Changed Filenames

git diff --name-only

Example:

programming-language.txt

Short Statistics

git diff --shortstat

Example:

1 file changed, 1 insertion(+)

Name and Status

git diff --name-status

Possible status letters include:

LetterMeaning
AAdded
CCopied
DDeleted
MModified
RRenamed
TType changed
UUnmerged

Understanding Diff Output

A simplified Git diff may look like:

diff --git a/programming-language.txt b/programming-language.txt
--- a/programming-language.txt
+++ b/programming-language.txt
@@ -3,3 +3,4 @@
 We talk to each other via gRPC!
 We use only HTTP2 connection!
+New service for Payment!

Lines beginning with:

+

were added.

Lines beginning with:

-

were removed.


Compare Two Commits

Get commit IDs:

git log --oneline

Then:

git diff <commit-a> <commit-b>

Example:

git diff b854bf cff609

You usually do not need to type the entire commit hash as long as the shortened prefix is unique.


Compare a Commit With HEAD

git diff b854bf HEAD

HEAD represents the currently checked-out commit.


Compare Branches

git diff master feature/task94

For a specific file:

git diff master feature/task94 -- ./diff_test.txt

Using -- clearly separates revision arguments from file paths.


Two-Dot and Three-Dot Diff

Two branch tips can be compared with:

git diff branch1 branch2

or:

git diff branch1..branch2

A three-dot comparison:

git diff branch1...branch2

compares the common ancestor of the branches with the tip of the second branch.

This can be useful for asking:

What has branch2 changed since it diverged from branch1?


Compare Tags

git diff v1.7-rc1 v1.8.1

Because tags identify commits or tag objects resolving to commits, they are natural comparison points for releases.


Save a Diff to a File

Redirect output:

git diff b854bf HEAD > ~/diff-head-commit.txt

Inspect:

cat ~/diff-head-commit.txt

A diff can therefore be stored, reviewed, attached to documentation, or processed by other command-line tools.


A Complete Local Git Workflow

A practical local development cycle might look like:

git status

Modify files.

Then:

git diff

Stage selected changes:

git add file1 file2

Inspect staged state:

git status

Commit:

git commit -m "Implement feature X"

Create another line of development:

git checkout -b feature/y

Work and commit.

Then inspect:

git log --oneline --decorate --graph --all

Return:

git checkout master

Merge:

git merge feature/y

Delete completed branch:

git branch -d feature/y

This is already enough Git knowledge to work productively on a large amount of local software development.


Git Safety Rules

Git is extremely powerful because it allows history to be manipulated.

That is also why some commands require respect.

Before destructive operations, check:

git status
git log --oneline --decorate --graph --all

Before cleaning untracked files:

git clean -n

Before force-deleting a branch:

git log <branch>

Before:

git reset --hard

make sure the working-tree changes can actually be discarded.

Before:

git stash clear

inspect:

git stash list

The general rule is:

Inspect first. Modify second.


Commands You Should Know

At the end of this chapter, you should understand the purpose of:

git init
git config
git status
git add
git restore --staged
git commit
git log
git branch
git checkout
git switch
git merge
git diff
git revert
git reset
git clean
git commit --amend
git rebase
git reflog
git tag
git stash

You do not need to remember every flag.

You need to understand the state transitions they perform.


Git State Mental Model

A useful final model is:

Untracked File
      ↓ git add
Tracked / Staged
      ↓ git commit
Committed History

For an already tracked file:

Committed
    ↓ edit
Modified
    ↓ git add
Staged
    ↓ git commit
New Commit

Branches add another dimension:

                feature
               /
A---B---C------D---E
        \
         master

Merge combines development histories.

Rebase replays one history onto another base.

Revert adds a new commit that undoes an older one.

Reset moves references and can change staged or working state.

Stash temporarily stores unfinished work.

Reflog records local reference movement.

Tags give important commits meaningful names.

Diff explains the difference between states.

That is Git.


Final Perspective

Do not think of Git as a collection of commands.

Think of Git as a model of project history.

Every command changes one or more of these things:

working tree
staging area
commit history
branch references
HEAD
tags
stash

Once you understand those objects, Git stops feeling random.

You begin to understand why a command behaves the way it does.

The most important habit is not typing commands quickly.

It is being able to answer:

Where is HEAD?
Which branch am I on?
What is modified?
What is staged?
What is committed?
What history will this command change?
Can I recover if I make a mistake?

When you can answer those questions before executing a Git command, you are no longer memorizing Git.

You are using it deliberately.

Algorithms

The Algorithms section contains programming exercises focused on problem solving, data processing, algorithm design, validation, graph relationships, scheduling, optimization, string processing, and combinatorial reasoning.

The section contains:

9 groups
45 tasks

Each group contains five tasks.

The groups are organized to introduce different categories of problems rather than following a strictly linear difficulty scale.

As the section progresses, the exercises move between:

  • collection processing
  • sorting and grouping
  • pair and frequency analysis
  • combinations
  • validation
  • structured processing
  • graphs
  • scheduling
  • state machines
  • optimization
  • manual string processing
  • constraint-based combinations

The objective is not only to produce the expected result.

Solutions should also be:

  • correct
  • deterministic
  • testable
  • understandable
  • properly validated
  • reasonably efficient

General Approach

Before implementing a task:

  1. Identify the exact input.
  2. Identify the required output.
  3. Determine the validation rules.
  4. Identify important edge cases.
  5. Separate data preparation from processing.
  6. Choose an appropriate algorithm.
  7. Verify the result using the provided examples.
  8. Add additional tests.

For optimization tasks, also distinguish:

valid solution

from:

optimal solution

For stateful tasks, distinguish:

current state

from:

requested state change

For graph and dependency tasks, validate the structure before attempting to process it.

The first group introduces fundamental collection-processing problems.

Tasks include:

String Pair Conversion
Multi-Dimensional Value Search
Conditional Map Filtering
Matrix Diagonal Processing
Map Sorting

Main topics:

  • string conversion
  • numeric conversion
  • slices
  • maps
  • nested collections
  • conditional filtering
  • matrix traversal
  • deterministic ordering

This group establishes the basic processing patterns used throughout the library.

Group 2 — Sorted Data and Range Grouping

The second group focuses on maintaining order and dividing values into numeric ranges.

Tasks include:

Maximum Value per Group
Sorted Element Insertion
Fixed Range Grouping
Sorted Range Insertion
Configurable Range Grouping

Main topics:

  • maximum-value search
  • sorted collections
  • insertion
  • range boundaries
  • grouping
  • configurable segmentation

The tasks require careful handling of ordering and boundary conditions.

Group 3 — Pair Analysis and Frequency Processing

The third group introduces relationships between corresponding values and statistical properties of collections.

Tasks include:

Pair Sum Matching
Pair Sum Delta Matching
Frequency Grouping
Indexed Multiplication Limits
Pair Normalization Analysis

Main topics:

  • indexed relationships
  • pair calculations
  • tolerance ranges
  • frequency counting
  • threshold comparison
  • inward pairing
  • normalization

This group develops reasoning about relationships between values rather than isolated elements.

Group 4 — Collection Relationships and Combinations

The fourth group focuses on relationships between complete collections.

Tasks include:

Collection Equality
Three-Element Average Combinations
Common Values
Closest Values to Target
Common Adjacent Pairs

Main topics:

  • multiset equality
  • combinations
  • intersections
  • proximity
  • adjacency
  • cross-list analysis

Several tasks require comparing multiple collections while preserving clearly defined ordering or uniqueness rules.

Group 5 — Validation and Structured Processing

The fifth group introduces larger structured-processing problems.

Tasks include:

Configurable String Validation
User Login and Access Control
Sector Range Analysis
Generic Pipeline Stability Analysis
Multi-Sector Pipeline Stability Report

Main topics:

  • configurable validation
  • multiple validation errors
  • authentication
  • authorization
  • account state
  • sector segmentation
  • generic numeric processing
  • stability thresholds
  • structured reports

The tasks begin to combine several processing rules into one operation.

Instead of performing one transformation, implementations must coordinate validation, domain rules, and structured output.

Group 6 — Graphs, Scheduling, and State

The sixth group focuses on relationships that form graphs, dependency networks, schedules, and state transitions.

Tasks include:

Dependency Graph Resolution
Weighted Route Resolution
Dependency-Aware Task Scheduler
State Machine Validation
Event Stream Window Analysis

Main topics:

  • directed graphs
  • dependencies
  • cycle detection
  • topological ordering
  • weighted paths
  • scheduling
  • limited workers
  • state machines
  • transition validation
  • event windows
  • burst detection

This group introduces problems where relationships between entities determine which operations are possible.

Deterministic behavior is particularly important when several valid processing orders exist.

Group 7 — Optimization and Planning

The seventh group focuses on problems where producing any valid solution is not enough.

The implementation must find the best solution according to a defined objective.

Tasks include:

Weighted Interval Scheduling
Multi-Resource Capacity Selection
Minimum Cost Assignment
Capacity-Constrained Route Planning
Deadline and Penalty Scheduling

Main topics:

  • interval optimization
  • dynamic programming
  • multidimensional capacity
  • subset selection
  • assignment
  • route planning
  • scheduling
  • deadlines
  • penalties
  • solution reconstruction

These tasks introduce the distinction between:

feasibility

and:

optimality

A result may satisfy every constraint and still be incorrect if a better valid solution exists.

Depending on the task and input size, useful techniques may include:

dynamic programming
backtracking
memoization
branch and bound
graph algorithms
exhaustive search

Group 8 — Manual String Processing

The eighth group focuses on implementing string-processing behavior without relying on standard-library functions that directly solve the required operation.

Tasks include:

Manual Substring Search
Multiple Substring Search
Match Range Collection
Manual String Replacement
Multi-Source Search Analysis

Main topics:

  • manual substring search
  • multiple search patterns
  • range collection
  • replacement
  • token processing
  • occurrence counting
  • multi-source analysis

The purpose of the restrictions in this group is to expose the underlying processing logic.

Functions that directly perform the required operation should not be used.

For example, depending on the task, avoid helpers that directly provide:

substring search
contains
count
replace
split
regular-expression matching

Normal language features such as:

loops
indexing
length
collection creation
manual output construction

remain allowed.

Group 9 — Advanced Combinations and Constraint Processing

The final group combines reconstruction, compatibility, Cartesian products, and conditional combination generation.

Tasks include:

Unique and Common Values
Sector Range Reconstruction
System Version Compatibility
Generic Combination Generator
Conditional Combination Generator

Main topics:

  • global frequency analysis
  • missing-value reconstruction
  • sector correction
  • compatibility relationships
  • Cartesian products
  • configurable combination generation
  • target sums
  • delta ranges
  • constraint filtering

This group closes the Algorithms section with problems that combine several earlier ideas.

The tasks require reasoning about complete sets of possibilities while respecting additional constraints.

Progression

The section can be viewed conceptually as:

Group 1
Basic Data Transformation and Search

        ↓

Group 2
Sorted Data and Range Grouping

        ↓

Group 3
Pair Analysis and Frequency Processing

        ↓

Group 4
Collection Relationships and Combinations

        ↓

Group 5
Validation and Structured Processing

        ↓

Group 6
Graphs, Scheduling, and State

        ↓

Group 7
Optimization and Planning

        ↓

Group 8
Manual String Processing

        ↓

Group 9
Advanced Combinations and Constraint Processing

The progression is intentionally not a strict difficulty ladder.

Different groups emphasize different types of reasoning.

A string-processing problem may be more difficult than a graph problem for one implementation, while an optimization problem may require a completely different approach from both.

The goal is broad algorithmic practice.

Input Validation

Unless a task explicitly states otherwise, implementations should validate input that would make the requested operation undefined or structurally invalid.

Examples include:

duplicate IDs
invalid indexes
invalid ranges
negative capacities
invalid intervals
missing dependencies
cyclic dependencies
mismatched collection lengths
unsupported operators
invalid configuration
unknown references

Validation should happen before performing destructive or state-changing processing where applicable.

Deterministic Results

Some problems allow several mathematically valid answers.

When the task defines a tie-breaking rule, that rule must be followed.

When deterministic behavior is required but no natural ordering exists, a documented rule should be used.

Examples include:

ascending numeric order
lexicographical ID order
lowest worker ID
earliest start time
lowest total resource usage

Running the same task with the same input should produce the same result.

Result Models

Prefer structured results when an operation produces several related values.

For example:

type Result struct {
    Valid   bool
    Values  []int
    Errors  []string
}

is usually easier to understand and test than several unrelated return values.

Optimization tasks should normally return both:

optimal objective value
selected solution

For example:

TotalValue
SelectedJobs

or:

TotalCost
Assignments

Returning only the numeric optimum is not enough when the task requires reconstruction of the actual solution.

Testing

Every task should include tests for:

provided examples
normal valid input
boundary conditions
empty input where applicable
invalid input
deterministic behavior

Depending on the task, also test:

duplicates
equal values
missing values
zero values
negative values
overlapping intervals
cycles
unreachable nodes
multiple optimal solutions
capacity exhaustion
invalid state transitions

Optimization tasks should contain cases where an obvious greedy solution is not optimal.

This helps verify that the implementation actually solves the optimization problem rather than only producing a plausible result.

Performance

The exercises are designed primarily for correctness and reasoning.

However, implementations should still consider algorithmic complexity.

Examples include:

repeated full scans
nested loops
unnecessary allocations
repeated sorting
exponential combination growth
graph traversal complexity
dynamic-programming state size

Some tasks intentionally use small datasets because exact combinatorial optimization can grow rapidly.

Do not replace a correct exact solution with an undocumented heuristic when the task requires the optimal result.

Language Independence

The tasks are language-independent.

They may be implemented in:

Go
Rust
C++

or another suitable programming language.

The examples may use Go-style models because they provide a concise representation of the required structures.

The important part is the behavior of the solution, not the specific language syntax.

Goal

The Algorithms section is designed to develop several forms of problem-solving ability:

data transformation
search
sorting
grouping
pair analysis
collection comparison
combinatorial reasoning
validation
graph processing
dependency resolution
scheduling
state processing
optimization
manual string algorithms
constraint processing

A successful solution should not only return the expected output.

It should also make it clear:

why the result is correct
which rules were applied
which edge cases were considered
which constraints were satisfied
and, where required, why the result is optimal

Algorithm Group 1

Algorithm Group 1 contains five exercises focused on fundamental data processing operations and working with common data structures.

The tasks involve transforming input data, searching multi-dimensional collections, filtering values based on conditions, processing matrices, and sorting structured data.

Tasks

Task 1 — String Pair Conversion

Transform paired string values into a key-value structure where names are used as keys and decimal values are converted to numeric values.

The task also requires attention to input validation, value conversion, and error handling.

Search a two-dimensional collection of decimal values and find all elements greater than a specified value.

For every matched element, preserve information about:

  • the value
  • the list in which it was found
  • its position inside that list

Task 3 — Conditional Map Filtering

Process selected entries from a map containing collections of decimal values.

Values are filtered according to a comparison operator and a target value.

The supported comparison operations are:

  • less than
  • less than or equal to
  • greater than
  • greater than or equal to

Task 4 — Matrix Diagonal Processing

Work with a two-dimensional numeric matrix and calculate values based on positions relative to its diagonals.

The task focuses on understanding matrix coordinates and selecting elements according to their position.

Task 5 — Map Sorting

Process map data and produce ordered results based on its keys or values.

The task focuses on extracting structured data from a map and applying different sorting requirements.

Objectives

The exercises in this group provide practice with:

  • slices and multi-dimensional slices
  • maps
  • numeric conversion
  • searching
  • filtering
  • conditional processing
  • matrix traversal
  • sorting
  • validation and error handling

Implementation

Read each individual task specification before choosing an implementation.

The examples provided by the tasks define the expected behavior for specific inputs, but the implementation should solve the general problem rather than only reproduce the example results.

Different implementation approaches are allowed unless an individual task explicitly defines a restriction.

When evaluating a solution, consider correctness, clarity, unnecessary iterations, memory usage, and the suitability of the selected data structures.

Task 1 — String Pair Conversion

Objective

Create a function that receives a collection of string values and transforms pairs of values into a map[string]float64.

Every two consecutive elements in the input represent one pair:

  • the first element represents a name
  • the second element represents a decimal value

The name must become the map key, while the decimal value must be converted from string to float64 and stored as the value associated with that key.

Input

The input data is a collection of strings where every two consecutive elements form a pair.

Example:

inputData := []string{
    "David", "9.20",
    "Alex", "8.10",
    "Max", "6.20",
    "Ben", "7.50",
}

The pairs are therefore:

"David" -> "9.20"
"Alex"  -> "8.10"
"Max"   -> "6.20"
"Ben"   -> "7.50"

Function

Create a function named:

CreateForm(...)

The function must process the provided string values and return:

map[string]float64

The exact implementation and function signature are left to the developer, provided that the required input can be accepted and the expected output is produced.

Requirements

For every pair of input values:

  1. Take the first value as the name.
  2. Use the name as a key in the resulting map.
  3. Take the second value as the decimal value.
  4. Convert the decimal value from string to float64.
  5. Store the converted value under the corresponding name.

For example:

"David", "9.20"

must become:

"David" -> 9.20

The same operation must be performed for every pair in the input.

Example

Given:

inputData := []string{
    "David", "9.20",
    "Alex", "8.10",
    "Max", "6.20",
    "Ben", "7.50",
}

the function should transform the data into:

map[string]float64{
    "David": 9.20,
    "Alex":  8.10,
    "Max":   6.20,
    "Ben":   7.50,
}

Expected Result

map[string]float64{
    "David": 9.20,
    "Alex":  8.10,
    "Max":   6.20,
    "Ben":   7.50,
}

Validation

Pay particular attention to:

  • input validation
  • conversion of string values to float64
  • conversion errors
  • invalid input data

The implementation should not assume that every provided decimal string can always be successfully converted.

Validation and error handling are part of the task.

Implementation Notes

The objective is to design the transformation rather than simply reproduce the example output.

The implementation should work with other valid name/value pairs following the same input format.

Consider how the function should behave when the input does not contain valid pairs or when a numeric value cannot be converted.

Do not optimize specifically for the example data.

Task 2 — Multi-Dimensional Value Search

Objective

Create a function that searches a two-dimensional collection of float64 values and returns every element that is greater than a specified input value.

For every matched element, the function must also return information about:

  • the value that was found
  • the list in which the value was found
  • the position of the value inside that list

Input

The function has two input arguments.

The first argument is a two-dimensional collection of float64 values:

[][]float64

Example:

data := [][]float64{
    {3.2, 5.4},
    {6.3, 1.4},
    {2.5, 6.5},
}

The second argument is a float64 value that will be used as the comparison threshold.

For example:

4.8

The function must find all elements whose value is greater than the provided threshold.

Result Type

The function must return a collection of Message structures.

The Message structure is defined as:

type Message struct {
    Value    float64
    List     int
    Position int
}

The fields have the following meaning:

  • Value — the value that was found
  • List — the number of the list in which the value was found
  • Position — the position of the value inside that list

Function

The function is named:

FindAllGreaterThen(...)

The function receives:

two-dimensional float64 data
+
float64 threshold

and returns:

[]Message

Requirements

Iterate through all values in the provided two-dimensional collection.

For each value:

  1. Compare it with the provided threshold.
  2. If the value is greater than the threshold, create a Message.
  3. Store the matched value in Message.Value.
  4. Store the number of the containing list in Message.List.
  5. Store the position of the value inside that list in Message.Position.
  6. Add the Message to the result collection.

Only values strictly greater than the threshold should be returned.

Example Data

Given:

data := [][]float64{
    {3.2, 5.4},
    {6.3, 1.4},
    {2.5, 6.5},
}

the positions can be represented as:

List 1:
    Position 1 -> 3.2
    Position 2 -> 5.4

List 2:
    Position 1 -> 6.3
    Position 2 -> 1.4

List 3:
    Position 1 -> 2.5
    Position 2 -> 6.5

The task uses list and position numbering starting from 1.

Case 1

Call:

FindAllGreaterThen(data, 4.8)

Values greater than 4.8 are:

5.4
6.3
6.5

Their locations are:

5.4 -> List 1, Position 2
6.3 -> List 2, Position 1
6.5 -> List 3, Position 2

Expected Result

[]Message{
    {5.4, 1, 2},
    {6.3, 2, 1},
    {6.5, 3, 2},
}

Case 2

Call:

FindAllGreaterThen(data, 5.5)

Values greater than 5.5 are:

6.3
6.5

Their locations are:

6.3 -> List 2, Position 1
6.5 -> List 3, Position 2

Expected Result

[]Message{
    {6.3, 2, 1},
    {6.5, 3, 2},
}

Position Numbering

Although Go slices are indexed from 0, the expected task result uses list and position numbering starting from 1.

For example:

data[0][1]

contains:

5.4

but the expected task representation is:

List     = 1
Position = 2

The implementation must therefore preserve the numbering convention demonstrated by the expected results.

Implementation Notes

The function should work with other valid two-dimensional float64 collections and threshold values.

The implementation should not depend on the dimensions or values used in the examples.

All nested lists should be processed, and every matching value should be included in the returned collection.

The ordering of the returned messages should follow the traversal order of the input data, as demonstrated by the original examples.

Task 3 — Conditional Map Filtering

Objective

Create a function that searches selected entries in a map[int][]float64 and returns only the values that satisfy a specified comparison condition.

The function must allow the caller to:

  • select which map keys should be processed
  • choose a comparison operator
  • provide a comparison value

The result is another map[int][]float64 containing the matching values.

Input

The function has four input arguments.

First Argument

The first argument is:

map[int][]float64

Example:

m1 := map[int][]float64{
    1: {3.8, 4.6, 5.2},
    2: {2.2, 3.5, 4.9},
    3: {2.7, 3.1, 4.1},
}

Each map key is associated with a collection of float64 values.

Second Argument

The second argument is:

[]int

This slice defines which map keys should be searched.

For example:

[]int{1, 3}

means that only map entries with keys 1 and 3 should be processed.

Third Argument

The third argument is a string representing the comparison operator.

The supported values are:

L  -> Less Than
LE -> Less Than or Equal To
G  -> Greater Than
GE -> Greater Than or Equal To

Therefore, the third argument can contain one of the following values:

L
LE
G
GE

Fourth Argument

The fourth argument is a float64 comparison value.

For example:

4.0

The selected operator and this value together define the filtering condition.

Function

The function is named:

FindAll(...)

Conceptually, the function receives:

map[int][]float64
+
[]int containing selected keys
+
comparison operator
+
float64 comparison value

and returns:

map[int][]float64

Comparison Rules

The comparison operator determines which values should be returned.

L — Less Than

For:

operator = L
value    = 4.5

return values satisfying:

value < 4.5

LE — Less Than or Equal To

For:

operator = LE
value    = 4.5

return values satisfying:

value <= 4.5

G — Greater Than

For:

operator = G
value    = 4.5

return values satisfying:

value > 4.5

GE — Greater Than or Equal To

For:

operator = GE
value    = 4.5

return values satisfying:

value >= 4.5

Requirements

The function must process only the map keys specified in the second argument.

For every selected key:

  1. Find the corresponding entry in the input map.
  2. Iterate through its float64 values.
  3. Apply the comparison defined by the third and fourth arguments.
  4. Collect all values that satisfy the condition.
  5. Add the matching values to the output map under the same key.

The result must have the type:

map[int][]float64

The keys in the result correspond to the selected keys that were searched.

The values associated with each result key are the values that satisfied the selected comparison condition.

Example Data

Given:

m1 := map[int][]float64{
    1: {3.8, 4.6, 5.2},
    2: {2.2, 3.5, 4.9},
    3: {2.7, 3.1, 4.1},
}

Case 1

Call:

FindAll(m1, []int{1, 3}, "G", 4.0)

Only keys 1 and 3 are searched.

For key 1:

3.8 -> does not satisfy > 4.0
4.6 -> satisfies > 4.0
5.2 -> satisfies > 4.0

For key 3:

2.7 -> does not satisfy > 4.0
3.1 -> does not satisfy > 4.0
4.1 -> satisfies > 4.0

Expected Result

map[int][]float64{
    1: {4.6, 5.2},
    3: {4.1},
}

Case 2

Call:

FindAll(m1, []int{2, 3}, "G", 3.0)

For key 2:

2.2 -> does not satisfy > 3.0
3.5 -> satisfies > 3.0
4.9 -> satisfies > 3.0

For key 3:

2.7 -> does not satisfy > 3.0
3.1 -> satisfies > 3.0
4.1 -> satisfies > 3.0

Expected Result

map[int][]float64{
    2: {3.5, 4.9},
    3: {3.1, 4.1},
}

Case 3

Call:

FindAll(m1, []int{1, 2, 3}, "LE", 3.5)

For key 1:

3.8 -> does not satisfy <= 3.5
4.6 -> does not satisfy <= 3.5
5.2 -> does not satisfy <= 3.5

For key 2:

2.2 -> satisfies <= 3.5
3.5 -> satisfies <= 3.5
4.9 -> does not satisfy <= 3.5

For key 3:

2.7 -> satisfies <= 3.5
3.1 -> satisfies <= 3.5
4.1 -> does not satisfy <= 3.5

Expected Result from the Original Task

map[int][]float64{
    1: {},
    2: {2.2, 3.5},
    3: {2.7, 3.1},
}

Important Note About Empty Results

The original task description states that if a selected key does not contain any values satisfying the condition, that key should not be included in the output map.

However, the original third example includes:

1: {}

even though key 1 contains no values less than or equal to 3.5.

This creates a difference between the written rule and the provided example.

When implementing the task, decide which behavior will be followed and keep it consistent:

  • omit keys that have no matching values, according to the written requirement
  • or preserve selected keys with an empty slice, according to the third example

The original source does not further clarify which behavior takes priority.

Validation

Consider validation for:

  • unsupported comparison operators
  • requested keys that do not exist in the input map
  • empty key lists
  • empty value collections

The original task explicitly defines only the comparison operators:

L
LE
G
GE

Behavior for other operator values is not specified.

Implementation Notes

The implementation should work with other valid map[int][]float64 values, selected key collections, operators, and comparison values.

The function should not be implemented specifically around the example data.

Only keys listed in the second argument should be considered during filtering.

Task 4 — Matrix Diagonal Processing

Objective

Create two functions that process a two-dimensional integer matrix relative to its main diagonal.

The first function must calculate the sum of all elements above the main diagonal.

The second function must calculate the sum of all elements below the main diagonal.

Input

The input is a two-dimensional collection of integers:

[][]int

Example:

data := [][]int{
    {1, 2, 3, 4, 5},
    {1, 2, 3, 4, 5},
    {1, 2, 3, 4, 5},
    {1, 2, 3, 4, 5},
    {1, 2, 3, 4, 5},
}

This matrix contains five rows and five columns.

Main Diagonal

The main diagonal begins at:

[0][0]

and ends at:

[4][4]

For the example matrix, the diagonal positions are:

[0][0]
[1][1]
[2][2]
[3][3]
[4][4]

The values on the main diagonal are:

1
2
3
4
5

Function 1 — Sum Above the Diagonal

Create a function that calculates the sum of all elements located above the main diagonal.

An element is above the main diagonal when:

column index > row index

For example:

[0][1]
[0][2]
[0][3]
[0][4]

[1][2]
[1][3]
[1][4]

[2][3]
[2][4]

[3][4]

These positions are all above the main diagonal.

Using the example matrix, the values are:

2, 3, 4, 5,
3, 4, 5,
4, 5,
5

The function should return the sum of these values.

Function 2 — Sum Below the Diagonal

Create a second function that calculates the sum of all elements located below the main diagonal.

An element is below the main diagonal when:

row index > column index

For example:

[1][0]

[2][0]
[2][1]

[3][0]
[3][1]
[3][2]

[4][0]
[4][1]
[4][2]
[4][3]

These positions are all below the main diagonal.

Using the example matrix, the values are:

1,
1, 2,
1, 2, 3,
1, 2, 3, 4

The function should return the sum of these values.

Requirements

Create two separate functions:

  1. one function for summing elements above the main diagonal
  2. one function for summing elements below the main diagonal

The values located directly on the main diagonal must not be included in either result.

The implementation should determine the position of each element using its row and column indexes.

Matrix Representation

The matrix can be visualized as:

          Columns
          0  1  2  3  4

Row 0     1  2  3  4  5
Row 1     1  2  3  4  5
Row 2     1  2  3  4  5
Row 3     1  2  3  4  5
Row 4     1  2  3  4  5

The main diagonal is:

[0][0] = 1
[1][1] = 2
[2][2] = 3
[3][3] = 4
[4][4] = 5

Elements above the diagonal satisfy:

column > row

Elements below the diagonal satisfy:

row > column

Example Results

For the provided matrix:

Sum Above the Diagonal

2 + 3 + 4 + 5
+ 3 + 4 + 5
+ 4 + 5
+ 5
= 40

Expected result:

40

Sum Below the Diagonal

1
+ 1 + 2
+ 1 + 2 + 3
+ 1 + 2 + 3 + 4
= 20

Expected result:

20

Implementation Notes

The original task defines a square 5 x 5 matrix.

The implementation should correctly distinguish between:

  • elements above the main diagonal
  • elements on the main diagonal
  • elements below the main diagonal

Do not include diagonal elements in either sum.

The exact function names and signatures are not specified by the original task and may be chosen by the developer.

Task 5 — Map Sorting

Objective

Create two functions that sort data stored in a map[string]int.

The first function must sort the data based on the map keys.

The second function must sort the data based on the values associated with those keys.

Because a Go map does not preserve iteration order, both functions must return the sorted result as an ordered list of structures.

Input

The input map contains the following values:

data := map[string]int{
    "David": 40,
    "Paul":  20,
    "Bill":  30,
    "Fred":  50,
    "Alex":  10,
}

Each entry contains:

string key -> integer value

For example:

"David" -> 40
"Paul"  -> 20
"Bill"  -> 30
"Fred"  -> 50
"Alex"  -> 10

Result Type

Define a structure that represents one map entry:

type MapItem struct {
    Key   string
    Value int
}

Both sorting functions must return:

[]MapItem

The order of the elements inside the returned slice represents the sorted order.

Function 1 — Sort by Keys

Create a function that sorts the input map entries based on their keys.

The keys must be sorted in ascending alphabetical order.

For the provided input, the expected key order is:

Alex
Bill
David
Fred
Paul

Expected Result

[]MapItem{
    {Key: "Alex", Value: 10},
    {Key: "Bill", Value: 30},
    {Key: "David", Value: 40},
    {Key: "Fred", Value: 50},
    {Key: "Paul", Value: 20},
}

Function 2 — Sort by Values

Create a second function that sorts the input map entries based on their integer values.

The values must be sorted in ascending order.

For the provided input, the expected value order is:

10
20
30
40
50

Expected Result

[]MapItem{
    {Key: "Alex", Value: 10,},
    {Key: "Paul", Value: 20},
    {Key: "Bill", Value: 30},
    {Key: "David", Value: 40},
    {Key: "Fred", Value: 50},
}

Requirements

Create two separate functions.

The first function must:

  1. receive the input map[string]int
  2. convert the map entries into sortable data
  3. sort the entries by Key
  4. return the result as []MapItem

The second function must:

  1. receive the input map[string]int
  2. convert the map entries into sortable data
  3. sort the entries by Value
  4. return the result as []MapItem

The association between every key and its original value must be preserved.

Sorting Rules

Key Sorting

Key sorting must use ascending alphabetical order.

For example:

Alex
Bill
David
Fred
Paul

Value Sorting

Value sorting must use ascending numeric order.

For example:

10
20
30
40
50

Duplicate Values

If multiple map entries contain the same integer value, their relative order must be deterministic.

Use the key as a secondary sorting criterion.

For example, if the input contains:

map[string]int{
    "David": 20,
    "Alex":  20,
    "Paul":  10,
}

sorting by value should produce:

[]MapItem{
    {Key: "Paul", Value: 10},
    {Key: "Alex", Value: 20},
    {Key: "David", Value: 20},
}

The primary sort criterion is:

Value

and when two values are equal, the secondary criterion is:

Key

Implementation Notes

A Go map should only be used as the input data structure.

The sorted result must not rely on map iteration order.

Instead, the implementation should create an ordered slice containing the key-value pairs and sort that slice according to the required criterion.

The implementation should work with other valid map[string]int inputs and should not depend on the example values.

Algorithm Group 2

Algorithm Group 2 contains five exercises focused on searching, insertion, grouping, sorting, range classification, and transformation of integer collections.

The exercises progressively combine several common collection-processing operations.

Tasks

Task 1 — Maximum Value per Group

Process a two-dimensional integer collection and find the maximum value from each inner group.

The result contains one maximum value for every group.

Task 2 — Sorted Element Insertion

Insert a new integer into an already sorted slice while preserving its ascending order.

Task 3 — Fixed Range Grouping

Separate a collection of integers into three predefined numeric ranges and sort the values inside each resulting group.

Task 4 — Sorted Range Insertion

Insert multiple values into existing sorted groups.

Each value must be placed into the appropriate group while preserving sorting and preventing duplicate values.

Task 5 — Configurable Range Grouping

Filter a collection using lower and upper boundaries and then separate the selected values into three groups using configurable split boundaries.

Objectives

The exercises in this group provide practice with:

  • nested slices
  • maximum-value searching
  • sorted insertion
  • numeric ranges
  • grouping
  • filtering
  • duplicate detection
  • maintaining sorted collections
  • configurable boundaries

Implementation

Read each individual task before selecting an implementation strategy.

The examples define the expected behavior for the provided input values, but implementations should solve the general problem described by each task.

Unless explicitly stated otherwise, do not design the implementation around the size or exact values of the example collections.

Where the original task leaves behavior ambiguous, the individual task page provides additional clarification.

Task 1 — Maximum Value per Group

Objective

Create a function that processes a two-dimensional integer collection and finds the maximum value from each inner group.

The function must return one maximum value for every group in the input.

Input

The input is:

[][]int

Example:

data := [][]int{
    {32, 12, 24, 20},
    {18, 40, 22, 30},
    {21, 31, 42, 35},
}

The input contains three groups.

Result Type

The function must return:

[]int

Each position in the result represents the maximum value found in the corresponding input group.

Requirements

Process every inner slice independently.

For each group:

  1. inspect all values in the group
  2. determine the maximum value
  3. append that value to the result

The order of the result must correspond to the order of the input groups.

Example

Given:

data := [][]int{
    {32, 12, 24, 20},
    {18, 40, 22, 30},
    {21, 31, 42, 35},
}

the maximum values are:

Group 1 -> 32
Group 2 -> 40
Group 3 -> 42

Expected Result

[]int{32, 40, 42}

Implementation Notes

The implementation should work with other valid two-dimensional integer collections and should not depend on the example values.

The original task does not specify a function name.

Consider how the implementation should handle an empty inner group if support for such input is required.

The original specification does not define behavior for empty groups, so that behavior should be explicitly decided by the implementation rather than silently assumed.

Task 2 — Sorted Element Insertion

Objective

Create a function that inserts an integer into an already sorted slice while preserving ascending order.

The element must be placed directly into its correct sorted position.

Input

The initial sorted slice is:

list := []int{
    1, 2, 5, 7, 8, 11, 14,
}

The function receives an integer value that should be inserted into the slice.

Function

The function is named:

AddElement(...)

Conceptually, it receives:

AddElement(list, value)

and produces a sorted slice containing the new value.

Requirements

The function must:

  1. receive an already sorted []int
  2. receive an integer value
  3. determine the correct position for the new value
  4. insert the value at that position
  5. preserve ascending order

The resulting slice must contain all original elements and the newly inserted element.

Case 1

Call:

AddElement(list, 4)

The value 4 belongs between:

2 and 5

Expected Result

[]int{
    1, 2, 4, 5, 7, 8, 11, 14,
}

Case 2

Call:

AddElement(list, 9)

The value 9 belongs between:

8 and 11

Expected Result

[]int{
    1, 2, 5, 7, 8, 9, 11, 14,
}

Case 3

Call:

AddElement(list, 12)

The value 12 belongs between:

11 and 14

Expected Result

[]int{
    1, 2, 5, 7, 8, 11, 12, 14,
}

Boundary Cases

A general implementation should also support values that belong at the beginning or end of the slice.

For example:

AddElement(list, 0)

should place 0 before the current first element.

Similarly:

AddElement(list, 20)

should place 20 after the current last element.

Duplicate Values

The original task does not specify whether duplicate values are allowed.

Therefore, duplicate handling is intentionally left as an implementation decision unless additional requirements are introduced.

Implementation Notes

The input slice is already sorted.

The objective is therefore to determine the correct insertion position rather than treating the task as a general unsorted-list sorting problem.

The implementation should work with other sorted integer slices and insertion values.

Task 3 — Fixed Range Grouping

Objective

Create a function that separates a collection of integers into three groups according to predefined numeric ranges.

The values inside each resulting group must be sorted in ascending order.

Input

The input slice contains:

list := []int{
    20, 40, 10, 50, 80,
    60, 90, 70, 30, 100,
}

Result Type

The result must be:

[][]int

containing exactly three groups.

Grouping Rules

Group 1

The first group contains values:

value <= 30

Group 2

The second group contains values:

value > 30 && value <= 60

Group 3

The third group contains values:

value > 60 && value <= 100

Requirements

Process every value from the input slice.

For each value:

  1. determine which range it satisfies
  2. place it into the corresponding group
  3. sort the values inside each group in ascending order

The result must preserve the group order:

Group 1
Group 2
Group 3

Example

Given:

list := []int{
    20, 40, 10, 50, 80,
    60, 90, 70, 30, 100,
}

the values are classified as:

Group 1:
10, 20, 30

Group 2:
40, 50, 60

Group 3:
70, 80, 90, 100

Expected Result

[][]int{
    {10, 20, 30},
    {40, 50, 60},
    {70, 80, 90, 100},
}

Values Outside the Defined Range

The original task defines the third group only up to and including 100:

value > 60 && value <= 100

It does not specify how values greater than 100 should be handled.

It also does not explicitly define a lower boundary for the first group, so values below zero still satisfy:

value <= 30

An implementation should preserve these stated range rules unless additional validation requirements are introduced.

Implementation Notes

The implementation should not depend on the original ordering of the input values.

The final values inside each group must be sorted.

The task can be approached by grouping first and sorting afterward, or by maintaining sorted groups while processing the input.

The choice of algorithm is left to the developer.

Task 4 — Sorted Range Insertion

Objective

Create a function that inserts multiple integer values into an existing two-dimensional collection of sorted groups.

Each new value must be placed into the appropriate group.

The groups must remain sorted, and duplicate values must not be added.

Input

The function has two input arguments.

Existing Groups

The first argument is:

[][]int

with the following values:

groups := [][]int{
    {6, 11, 17},
    {24, 32, 38},
    {45, 50, 55},
}

Each inner slice represents an existing sorted group.

Values to Insert

The second argument is:

[]int

with the following values:

values := []int{
    15, 30, 11, 49, 50, 32,
}

Function

The function is named:

AddElements(...)

Example:

AddElements(groups, values)

Requirements

Process every value from the second argument.

For each value:

  1. determine the appropriate existing group
  2. check whether the value already exists in that group
  3. if the value already exists, do not insert it again
  4. otherwise insert it into the group
  5. preserve ascending order inside the group

Duplicate Handling

Duplicate values inside a group are not allowed.

For the provided input:

11
50
32

already exist in their corresponding groups.

They must therefore not be inserted again.

The values:

15
30
49

are new and must be inserted.

Example

Initial groups:

[][]int{
    {6, 11, 17},
    {24, 32, 38},
    {45, 50, 55},
}

Values to process:

[]int{
    15, 30, 11, 49, 50, 32,
}

The new values are placed as follows:

15 -> first group
30 -> second group
49 -> third group

Existing values are ignored:

11 -> already exists
50 -> already exists
32 -> already exists

Expected Result

[][]int{
    {6, 11, 15, 17},
    {24, 30, 32, 38},
    {45, 49, 50, 55},
}

Group Selection

The original example clearly associates:

15 with the first group
30 with the second group
49 with the third group

However, the original task does not explicitly define numeric lower and upper boundaries for these groups.

The implementation must therefore define a consistent rule for determining which existing range should receive a new value.

That rule should preserve the intended separation demonstrated by the example.

Sorting

Every group is sorted before processing begins.

After all insertions are complete, every group must still be sorted in ascending order.

The implementation may either:

  • find the correct insertion position for each new value
  • or insert values and restore sorting afterward

The final result must be equivalent.

Implementation Notes

The function should operate on other valid collections following the same grouped and sorted structure.

Duplicate detection applies within the group where a value would be inserted.

The original task does not specify behavior for a value that cannot be associated with any existing group.

If such input is supported, that behavior should be explicitly defined by the implementation.

Task 5 — Configurable Range Grouping

Objective

Create a function that first selects integer values inside a configurable search range and then separates those values into three groups using two additional split boundaries.

The resulting groups must contain only values that satisfy the search boundaries.

Values inside each group must be sorted in ascending order.

Input

The function has five input arguments.

Conceptually:

CreateGroups(
    list,
    lowerBoundary,
    higherBoundary,
    firstSplit,
    secondSplit,
)

First Argument — Input Values

The first argument is:

[]int

Example:

list := []int{
    6, 45, 17, 81, 32,
    55, 95, 50, 24, 72,
    62, 38, 28, 68, 33,
    89, 11, 49, 77, 99,
}

Second Argument — Lower Boundary

The second argument is an int representing the lower search boundary.

The original task defines the possible values as:

20
30

Third Argument — Higher Boundary

The third argument is an int representing the higher search boundary.

The original task defines the possible values as:

80
90

Together, the second and third arguments define which values from the input collection are eligible for grouping.

Fourth Argument — First Split Boundary

The fourth argument defines the upper boundary of the first result group.

The original task defines the possible values as:

40
60

Fifth Argument — Second Split Boundary

The fifth argument defines the upper boundary of the second result group.

The original task defines the possible values as:

50
70

The second split boundary also determines where the third group begins.

Grouping Rules

After applying the lower and higher search boundaries, the selected values must be divided into three groups.

Given:

firstSplit  = 40
secondSplit = 70

the groups are:

Group 1

value <= 40

Group 2

value > 40 && value <= 70

Group 3

value > 70

Only values that already satisfy the lower and higher search boundaries are considered for these groups.

Example

Given:

list := []int{
    6, 45, 17, 81, 32,
    55, 95, 50, 24, 72,
    62, 38, 28, 68, 33,
    89, 11, 49, 77, 99,
}

call:

CreateGroups(list, 30, 90, 40, 70)

The search boundaries are:

LowerBoundary  = 30
HigherBoundary = 90

Therefore, values outside the selected search range are excluded before grouping.

The selected values are:

32
33
38
45
49
50
55
62
68
72
77
81
89

They are then divided using:

firstSplit  = 40
secondSplit = 70

Group 1

Condition:

value <= 40

Result:

[]int{
    32, 33, 38,
}

Group 2

Condition:

value > 40 && value <= 70

Result:

[]int{
    45, 49, 50, 55, 62, 68,
}

Group 3

Condition:

value > 70

Result:

[]int{
    72, 77, 81, 89,
}

Expected Result

[][]int{
    {32, 33, 38},
    {45, 49, 50, 55, 62, 68},
    {72, 77, 81, 89},
}

Boundary Processing

The lower and higher boundaries define the values that are eligible for grouping.

For:

CreateGroups(list, 30, 90, 40, 70)

values below the lower boundary are excluded.

For example:

6
11
17
24
28

Values above the higher boundary are also excluded:

95
99

The remaining values are classified using the two split boundaries.

Requirements

The function must:

  1. receive the source []int
  2. apply the lower and higher search boundaries
  3. exclude values outside the selected search range
  4. divide the remaining values into three groups
  5. use the fourth and fifth arguments as split boundaries
  6. sort the values inside every resulting group
  7. return the result as [][]int

Boundary Validation

A valid configuration should maintain a logical relationship between the boundaries.

Conceptually:

lowerBoundary <= firstSplit
firstSplit < secondSplit
secondSplit <= higherBoundary

This ensures that the three grouping ranges remain inside the selected search interval.

The original task provides predefined possible values that produce valid configurations, but does not explicitly define behavior for invalid combinations.

An implementation should validate or reject invalid boundary configurations rather than silently producing ambiguous groups.

Inclusive and Exclusive Boundaries

The grouping rules explicitly define:

Group 1: value <= firstSplit
Group 2: value > firstSplit && value <= secondSplit
Group 3: value > secondSplit

The original example also demonstrates that values outside the lower and higher search boundaries are excluded.

The exact inclusive/exclusive wording for the outer search boundaries is not explicitly stated in the original text.

The implementation should therefore define this behavior consistently.

For the provided example, this distinction does not change the expected result because neither 30 nor 90 appears in the input collection.

Implementation Notes

The filtering stage and grouping stage represent two different operations.

Conceptually:

Input
  ↓
Apply search boundaries
  ↓
Eligible values
  ↓
Apply split boundaries
  ↓
Three groups
  ↓
Sort
  ↓
Result

Keeping these responsibilities logically separated can make the implementation easier to understand and test.

The implementation should work for all valid boundary combinations defined by the task and should not depend on the specific example values.

Algorithm Group 3

Algorithm Group 3 contains five exercises focused on correlated collections, pair processing, configurable numeric ranges, frequency analysis, calculated limits, and normalization.

Several tasks in this group operate on multiple collections whose elements are related by their index positions.

Tasks

Task 1 — Pair Sum Matching

Create pairs from two integer slices using matching index positions and return the indexes whose pair sums match one of the requested target values.

Task 2 — Pair Sum Delta Matching

Extend pair-sum matching with a configurable delta that defines an allowed range around or relative to a target value.

Task 3 — Frequency Grouping

Generate a collection of random integer values and classify each distinct value according to how many times it occurs.

Task 4 — Indexed Multiplication Limits

Associate each decimal value with a collection of multipliers, calculate the resulting value, and compare it against an individual limit.

Task 5 — Pair Normalization Analysis

Create pairs by combining values from opposite ends of a slice and analyze the multiplication result of every pair against a normalization value.

Objectives

The exercises in this group provide practice with:

  • correlated slices
  • index-based pairing
  • sum matching
  • configurable ranges
  • parsing control values
  • frequency counting
  • random data generation
  • floating-point calculations
  • related multi-dimensional collections
  • typed calculation results
  • normalization
  • formatted analysis output

Index Relationships

Several exercises in this group depend on the same index representing related data across multiple collections.

For example:

p1[0] is paired with p2[0]
p1[1] is paired with p2[1]
p1[2] is paired with p2[2]

When collections are related in this way, their lengths and index relationships must be considered part of the input validation.

Implementation

The examples define the expected behavior for the provided data, but implementations should solve the general problem described by each task.

Where the original specification used weakly typed output or contained an inconsistent example, the individual task page provides a clarified specification while preserving the original algorithmic objective.

Task 1 — Pair Sum Matching

Objective

Create a function that combines values from two integer slices by matching index positions.

For every resulting pair, calculate the sum of its two values.

Return the indexes of all pairs whose sum is equal to any target value provided in a third input slice.

Input

The function has three input arguments.

First Slice

p1 := []int{
    -4, 5, -1, 3, 7,
    -3, 6, -2, 4, 8,
    10, 13, 17, 9, 2,
}

Second Slice

p2 := []int{
    12, 7, 13, 8, 3,
    14, 6, 15, 5, 4,
    1, -3, -5, -2, 6,
}

Target Values

The third argument is:

[]int

and contains one or more target sums.

Example:

[]int{8, 10}

Pair Creation

Elements are paired using matching index positions.

For example:

Index 0 -> {-4, 12}
Index 1 -> {5, 7}
Index 2 -> {-1, 13}
...

For any valid index i:

pair = {p1[i], p2[i]}

and:

pairSum = p1[i] + p2[i]

Function

The function is named:

FindSum(...)

Conceptually:

FindSum(p1, p2, targets)

returns:

[]int

containing the matching pair indexes.

Requirements

For every index shared by p1 and p2:

  1. create the pair from p1[i] and p2[i]
  2. calculate the sum of the two values
  3. compare the sum with all target values
  4. if the sum matches any target, add index i to the result

Each matching index should appear only once in the result.

Case 1

Call:

FindSum(p1, p2, []int{8, 10})

The function searches for pairs whose sums are either:

8
or
10

Expected Result

[]int{
    0, 4, 11, 14,
}

Case 2

Call:

FindSum(p1, p2, []int{9, 11})

The function searches for pairs whose sums are either:

9
or
11

Expected Result

[]int{
    3, 5, 8, 10,
}

Indexing

The expected results use standard zero-based slice indexes.

For example:

p1[0] = -4
p2[0] = 12

-4 + 12 = 8

Because 8 is one of the target values in Case 1, index:

0

is included in the result.

Input Validation

The two input slices represent correlated data and should therefore contain the same number of elements.

A robust implementation should validate that:

len(p1) == len(p2)

The original task does not define behavior for slices of different lengths.

The implementation should explicitly reject or otherwise handle such input rather than accidentally ignoring unmatched elements.

Implementation Notes

The implementation should work with other integer slices and target collections.

The function should not depend on the example length or values.

Consider how target lookup can be implemented efficiently when the third argument contains many possible target sums.

Task 2 — Pair Sum Delta Matching

Objective

Create a function that combines values from two integer slices by matching index positions and returns the indexes of pairs whose sums satisfy a target value modified by a delta rule.

The delta controls the allowed range relative to the target.

Input

The function has four input arguments.

First Slice

p1 := []int{
    -4, 5, -1, 3, 7,
    -3, 6, -2, 4, 8,
    10, 13, 17, 9, 2,
}

Second Slice

p2 := []int{
    12, 2, 7, 6, 3,
    8, 0, 15, 5, -4,
    -1, -6, -5, -2, 6,
}

Target

The third argument is an integer representing the target sum.

Example:

9

Delta

The fourth argument is a string describing how the target range should be expanded.

Supported forms are:

+N
-N
*N

where N is a positive integer delta.

Examples:

+2
-2
*2

Pair Creation

Values from p1 and p2 are paired using matching indexes.

For example:

Index 0 -> {-4, 12}
Index 1 -> {5, 2}
Index 2 -> {-1, 7}

For every index i:

pairSum = p1[i] + p2[i]

Delta Rules

Positive Delta

A positive delta:

+N

defines the inclusive range:

target <= pairSum <= target + N

For example:

target = 9
delta  = +2

produces:

9 <= pairSum <= 11

Negative Delta

A negative delta:

-N

defines the inclusive range:

target - N <= pairSum <= target

For example:

target = 8
delta  = -2

produces:

6 <= pairSum <= 8

Absolute Delta

An absolute delta:

*N

defines an inclusive range on both sides of the target:

target - N <= pairSum <= target + N

For example:

target = 11
delta  = *2

produces:

9 <= pairSum <= 13

Function

The function is named:

FindSum(...)

Conceptually:

FindSum(p1, p2, target, delta)

returns:

[]int

containing the indexes of matching pairs.

Case 1 — Positive Delta

Call:

FindSum(p1, p2, 9, "+2")

Allowed sum range:

9 <= pairSum <= 11

Expected Result

[]int{
    3, 4, 7, 8, 10,
}

Case 2 — Negative Delta

Call:

FindSum(p1, p2, 8, "-2")

Allowed sum range:

6 <= pairSum <= 8

Expected Result

[]int{
    0, 1, 2, 6, 11, 13, 14,
}

Case 3 — Absolute Delta

Call:

FindSum(p1, p2, 11, "*2")

Allowed sum range:

9 <= pairSum <= 13

Expected Result

[]int{
    3, 4, 7, 8, 10, 12,
}

Delta Validation

The delta is provided as a string and must be interpreted by the function.

A valid delta consists of:

operator + numeric value

where the operator is one of:

+
-
*

and the numeric portion represents a positive integer.

Examples of valid values:

+2
-3
*5

Invalid or unsupported delta values should result in an error rather than silently using an undefined range.

Input Validation

Because p1 and p2 represent correlated values, they should contain the same number of elements.

The implementation should validate:

len(p1) == len(p2)

The delta string should also be validated before pair processing begins.

Implementation Notes

The three delta modes represent three different interval calculations.

It may be useful to convert the delta string into normalized lower and upper boundaries before processing the pairs.

Conceptually:

Target + Delta
      ↓
Resolve allowed range
      ↓
Process pairs
      ↓
Calculate pair sums
      ↓
Return matching indexes

This keeps delta parsing separate from pair matching.

Task 3 — Frequency Grouping

Objective

Generate 60 random integer values inside a defined numeric range and classify every distinct value according to how many times it occurs in the generated collection.

The result contains three groups:

  • values that occur exactly once
  • values that occur exactly twice
  • values that occur three or more times

Random Data Generation

Generate exactly:

60

integer values.

Every generated value must satisfy:

value > 20 && value <= 40

Therefore, valid generated integers are:

21 through 40

inclusive.

Store the generated values in:

[]int

Example Generated Data

A partial example could look like:

[]int{
    23, 25, 27, 40, 38,
    27, 31, 27, 35, 25,
    // ...
}

The complete generated slice must contain 60 values.

Function

Create a function that receives the generated []int and separates its distinct values into three frequency groups.

The exact function name is not specified by the original task.

Group 1 — Occurs Once

The first group contains values that appear exactly once in the input.

Condition:

count(value) == 1

For example:

23
40
38
31

may belong to this group if each appears only once in the complete generated collection.

Group 2 — Occurs Twice

The second group contains values that appear exactly twice.

Condition:

count(value) == 2

For example, if:

25

appears exactly two times, it belongs to Group 2.

Group 3 — Occurs Three or More Times

The third group contains values that appear at least three times.

Condition:

count(value) >= 3

For example, if:

27

appears three or more times, it belongs to Group 3.

Result

The function should produce three collections of distinct values.

Conceptually:

[][]int{
    uniqueOnce,
    uniqueTwice,
    uniqueThreeOrMore,
}

Each distinct numeric value should appear only once in its corresponding result group.

Its frequency determines the group, not the number of times it should be repeated in the result.

For example, if 27 occurs five times in the input, the third result group should still contain:

27

only once.

Example

Suppose part of a generated collection contains:

[]int{
    23,
    25,
    27,
    40,
    38,
    27,
    31,
    27,
    25,
}

Within this simplified example:

23 -> 1 occurrence
25 -> 2 occurrences
27 -> 3 occurrences
40 -> 1 occurrence
38 -> 1 occurrence
31 -> 1 occurrence

The corresponding groups would be:

[][]int{
    {23, 40, 38, 31},
    {25},
    {27},
}

Requirements

The task consists of two logical stages.

Stage 1 — Generate Data

Generate:

60

random integer values satisfying:

20 < value <= 40

Stage 2 — Analyze Frequencies

Determine how many times each distinct value occurs and place it into exactly one of the three frequency groups.

Validation

Before frequency analysis, the implementation may verify that:

len(values) == 60

and that every value satisfies:

20 < value <= 40

Values outside this range violate the task input requirements.

Implementation Notes

The original task included 19 inside an abbreviated example of generated values even though the defined generation range is:

value > 20 && value <= 40

The valid specification takes precedence here, so generated values must remain within 21 through 40.

The implementation should count frequencies rather than repeatedly scanning the collection unnecessarily.

The ordering of values inside each result group is not specified by the original task.

If deterministic output is desired, the groups may be sorted in ascending order.

Task 4 — Indexed Multiplication Limits

Objective

Create a function that associates each decimal value with a collection of multipliers, calculates a resulting value, and compares that result against an individual limit.

The function must return a typed result describing both the calculated value and whether it satisfies the limit condition.

Input

The function has three input arguments.

All three collections are related by index.

First Argument — Values

The first argument is:

[]float64

containing 8 decimal values.

The values should be generated in the range:

value > 1.0 && value <= 10.0

with precision to three decimal places.

Example:

list1 := []float64{
    3.213,
    2.543,
    6.435,
    // ...
}

Second Argument — Multipliers

The second argument is:

[][]float64

Each inner slice contains the multipliers associated with the value at the same index in list1.

Example:

list2 := [][]float64{
    {1.20, 1.40},
    {1.25, 1.45},
    {1.20, 1.30},
    {1.15, 1.25},
    {1.20, 1.35},
    {1.20, 1.25},
    {1.15, 1.40},
    {1.25, 1.35},
}

For example:

list1[0] = 3.213

list2[0] = {1.20, 1.40}

Therefore, both multipliers belong to the value 3.213.

Third Argument — Limits

The third argument is:

[]float64

containing the limit associated with every value.

Example:

list3 := []float64{
    5.5,
    4.5,
    6.5,
    8.5,
    14.5,
    12.5,
    9.5,
    11.5,
}

The limit at index i applies to the calculated result for list1[i].

Result Type

Define a typed result structure:

type CalculationResult struct {
    Value      float64
    BelowLimit bool
}

The function returns:

[]CalculationResult

This replaces the mixed float64 / bool result representation from the original task with an explicit typed result.

Calculation

For every index i, begin with:

list1[i]

and multiply it by every multiplier contained in:

list2[i]

Conceptually:

result =
    list1[i]
    * list2[i][0]
    * list2[i][1]
    * ...

Then compare the calculated result against:

list3[i]

Result Rules

If:

calculatedValue < limit

return:

CalculationResult{
    Value:      calculatedValue,
    BelowLimit: true,
}

If:

calculatedValue >= limit

return:

CalculationResult{
    Value:      calculatedValue,
    BelowLimit: false,
}

The calculated value is preserved in both cases.

Example 1

Given:

value       = 3.213
multipliers = {1.20, 1.40}
limit       = 5.5

calculate:

3.213 * 1.20 * 1.40 = 5.39784

Because:

5.39784 < 5.5

the result is:

CalculationResult{
    Value:      5.39784,
    BelowLimit: true,
}

If the result is presented with three decimal places:

5.398

Example 2

Given:

value       = 2.543
multipliers = {1.25, 1.45}
limit       = 4.5

calculate:

2.543 * 1.25 * 1.45 = 4.6091875

Because:

4.6091875 >= 4.5

the result is:

CalculationResult{
    Value:      4.6091875,
    BelowLimit: false,
}

Requirements

For every index:

  1. take the value from list1
  2. retrieve its multipliers from list2
  3. multiply the value by all associated multipliers
  4. retrieve the corresponding limit from list3
  5. compare the calculated result with the limit
  6. return a CalculationResult

Input Relationship

The three collections are correlated by index.

Therefore:

list1[i]
list2[i]
list3[i]

all describe the same calculation.

The outer lengths must therefore match:

len(list1) == len(list2)
len(list1) == len(list3)

A robust implementation should validate these relationships before performing calculations.

Precision

The generated source values use three decimal places.

Intermediate calculations should not be unnecessarily rounded before the comparison is performed.

If a formatted result is needed for presentation, rounding should occur only after the comparison.

Display Requirement

Before executing the calculation function, print or otherwise display the values from:

list1

and:

list2

as required by the original exercise.

Design Change from the Original Task

The original version returned a heterogeneous collection containing either:

float64

or:

false

depending on the comparison result.

This revised specification uses:

CalculationResult

instead.

The calculation itself is unchanged, but the result is now type-safe and preserves the calculated value even when the limit is exceeded.

Implementation Notes

A typed result avoids runtime type assertions and provides the same representation in languages such as Go and Rust without relying on a generic or empty-interface collection.

The algorithm should support any number of multipliers associated with a value rather than assuming exactly two.

Task 5 — Pair Normalization Analysis

Objective

Create two functions.

The first function creates pairs by combining values from opposite ends of an integer slice.

The second function analyzes each pair by multiplying its values and comparing the result with a normalization value.

For every pair, produce a textual description of the comparison result.

Input

The source slice is:

list := []int{
    39,
    50,
    64,
    81,
    72,
    31,
    43,
    48,
    29,
    99,
}

Function 1 — CreatePairs

Create a function named:

CreatePairs(...)

The function receives the source:

[]int

and returns:

[][]int

Pairing Rules

Pairs are created using values from opposite ends of the slice.

The first value is paired with the last value.

The second value is paired with the second-to-last value.

Continue toward the center until all values have been paired.

For the provided input:

39 <-> 99
50 <-> 29
64 <-> 48
81 <-> 43
72 <-> 31

Expected Pair Result

[][]int{
    {39, 99},
    {50, 29},
    {64, 48},
    {81, 43},
    {72, 31},
}

Store this result as:

pairList := CreatePairs(list)

Function 2 — CheckNormalization

Create a second function named:

CheckNormalization(...)

The function receives:

1. the [][]int produced by CreatePairs
2. an integer normalization value

and returns:

[]string

Normalization Calculation

For each pair:

{a, b}

calculate:

multiplication = a * b

Then compare the result with the normalization value.

The output message must describe:

  • which pair was analyzed
  • the multiplication result
  • whether the result is greater than, smaller than, or equal to the normalization value
  • the absolute difference between the multiplication result and the normalization value

Example

Create the pairs:

pairList := CreatePairs(list)

Then call:

infoLog := CheckNormalization(pairList, 3000)

The normalization value is:

3000

Pair 1

Pair:

{39, 99}

Calculation:

39 * 99 = 3861

Difference:

3861 - 3000 = 861

Result:

greater than normalization value by 861

Pair 2

Pair:

{50, 29}

Calculation:

50 * 29 = 1450

Difference:

3000 - 1450 = 1550

Result:

smaller than normalization value by 1550

Pair 3

Pair:

{64, 48}

Calculation:

64 * 48 = 3072

Difference:

3072 - 3000 = 72

Result:

greater than normalization value by 72

Pair 4

Pair:

{81, 43}

Calculation:

81 * 43 = 3483

Difference:

3483 - 3000 = 483

Result:

greater than normalization value by 483

Pair 5

Pair:

{72, 31}

Calculation:

72 * 31 = 2232

Difference:

3000 - 2232 = 768

Result:

smaller than normalization value by 768

Expected Output

The returned []string should contain descriptions equivalent to:

Analyzed pair has values [39, 99]. Multiplication value 3861 is greater than normalization value by 861.

Analyzed pair has values [50, 29]. Multiplication value 1450 is smaller than normalization value by 1550.

Analyzed pair has values [64, 48]. Multiplication value 3072 is greater than normalization value by 72.

Analyzed pair has values [81, 43]. Multiplication value 3483 is greater than normalization value by 483.

Analyzed pair has values [72, 31]. Multiplication value 2232 is smaller than normalization value by 768.

Equality Case

A complete implementation should also handle a multiplication result that is exactly equal to the normalization value.

In that case:

multiplication == normalization

and the difference is:

0

The message should clearly report that the multiplication value is equal to the normalization value.

Odd-Length Input

The original example contains an even number of values, so every element can be paired.

The original specification does not define behavior for a slice with an odd number of elements.

A robust implementation should explicitly choose one of the following behaviors:

reject odd-length input

or define how the single center element should be represented.

For this task, rejecting odd-length input is the simplest unambiguous behavior because every result entry is required to be a pair.

Requirements

CreatePairs must:

  1. process values from both ends of the source slice
  2. create two-element pairs
  3. move toward the center
  4. return the pairs as [][]int

CheckNormalization must:

  1. process every pair
  2. multiply the two values
  3. compare the product with the normalization value
  4. calculate the difference
  5. return one description for each analyzed pair

Implementation Notes

The pair order must follow the source positions demonstrated by the example.

For a slice of length n, the conceptual pairing is:

index 0     with index n-1
index 1     with index n-2
index 2     with index n-3
...

The normalization comparison should use the actual multiplication result, and the reported difference should always be non-negative.

Group 4

Algorithm Group 4

Algorithm Group 4 contains five exercises focused on comparing multiple collections, generating combinations, finding shared values, selecting values by distance, and detecting common adjacent pairs.

Several tasks in this group operate on multiple slices representing independent datasets that must be compared with one another.

Tasks

Task 1 — Collection Equality

Determine whether three integer slices contain the same values even when those values appear in different orders.

Task 2 — Three-Element Average Combinations

Generate every unique combination of three different elements from an integer slice and count how many combinations have an average greater than a specified threshold.

Task 3 — Common Values

Find all integer values that appear in each of three input slices.

Task 4 — Closest Values to Target

For each input slice, identify the two values that are closest to a specified target value.

Task 5 — Common Adjacent Pairs

Find adjacent value pairs that appear in more than one input slice and report which slices contain each pair.

Objectives

The exercises in this group provide practice with:

  • collection comparison
  • ordering independence
  • frequency and membership analysis
  • combinations
  • arithmetic averages
  • intersection of multiple collections
  • distance calculations
  • nearest-value selection
  • adjacent element processing
  • pair comparison
  • result deduplication
  • deterministic output

Collection Relationships

Unlike some earlier groups, the slices in this group generally represent independent collections.

Their values may appear:

  • in different orders
  • only in some collections
  • multiple times
  • next to different neighboring values

The implementation should therefore distinguish between value membership, position, and adjacency depending on the task.

Implementation

The examples define the intended behavior for the provided data, but each implementation should solve the general problem.

Where the original specification contains an ambiguity or an inconsistent example, the individual task page provides a clarified requirement while preserving the intended exercise.

Task 1 — Collection Equality

Objective

Create a function that determines whether three integer slices contain the same values regardless of their ordering.

The function must return a boolean result.

Input

Create three integer slices.

List 1

list1 := []int{
    41, 26, 74, 25, 85, 36,
    93, 47, 56, 76, 20, 39,
}

List 2

list2 := []int{
    39, 25, 47, 76, 56, 85,
    93, 26, 36, 41, 74, 20,
}

List 3

list3 := []int{
    25, 39, 47, 85, 56, 76,
    41, 74, 20, 93, 36, 26,
}

Function

Create a function named:

CheckEquality(...)

The function receives all three slices and returns:

bool

Requirements

The function must determine whether all three collections contain the same elements.

The order of the values must not affect the result.

For example:

{1, 2, 3}

and:

{3, 1, 2}

contain the same values even though their ordering is different.

Expected Result

For the provided input:

CheckEquality(list1, list2, list3)

the result should be:

true

because all three slices contain the same integer values.

Duplicate Values

A complete implementation should compare the actual contents of the collections, including duplicate counts.

For example:

{1, 1, 2}

should not be considered equal to:

{1, 2, 2}

even though both collections contain the distinct values 1 and 2.

Therefore, equality should mean that every value occurs the same number of times in every input collection.

Length Validation

If the slices have different lengths, they cannot contain exactly the same collection of elements.

The function may therefore immediately return:

false

when:

len(list1) != len(list2)

or:

len(list1) != len(list3)

Implementation Notes

The implementation should not depend on the original ordering of the slices.

Possible strategies include:

  • sorting copies of the collections and comparing them
  • counting occurrences of each value
  • using another structure that preserves occurrence counts

The original input slices should not need to be permanently reordered unless the implementation explicitly allows mutation.

Task 2 — Three-Element Average Combinations

Objective

Create a function that generates every unique combination of three different elements from an integer slice.

For each combination, calculate its average value.

Return the number of combinations whose average is greater than a specified threshold.

Input

The source slice is:

list := []int{
    41, 19, 25, 74, 85, 36,
    93, 47, 56, 76, 20, 39,
    34, 66, 60, 82, 88, 91,
    17, 44, 28, 31, 95, 51,
    40, 14,
}

The second input argument is an integer threshold.

For the provided example:

threshold := 70

Function

Create a function named:

CheckAverageOfThreeElements(...)

Conceptually:

CheckAverageOfThreeElements(list, threshold)

returns the number of valid three-element combinations.

Combination Rules

Each group must contain exactly three different elements from different index positions.

For indexes:

i < j < k

a combination is:

{list[i], list[j], list[k]}

Using increasing indexes ensures that the same combination of positions is not generated multiple times in different orders.

For example:

{41, 19, 25}

is the same positional combination as:

{25, 41, 19}

and should only be processed once.

Average Calculation

For every three-element combination:

{a, b, c}

calculate:

average = (a + b + c) / 3

The combination satisfies the condition when:

average > threshold

For the provided task:

average > 70

Equivalent Sum Comparison

Because every combination always contains exactly three elements, the condition:

(a + b + c) / 3 > threshold

can also be evaluated as:

a + b + c > threshold * 3

This avoids unnecessary floating-point arithmetic.

For:

threshold = 70

the condition becomes:

a + b + c > 210

Total Number of Combinations

The input contains:

26

elements.

The number of unique three-element combinations is:

C(26, 3) = 2600

Every one of these combinations should be evaluated exactly once.

Expected Result

For:

CheckAverageOfThreeElements(list, 70)

the number of combinations whose average is greater than 70 is:

288

Therefore, the expected result is:

288

Requirements

The function must:

  1. generate every unique combination of three different index positions
  2. calculate or evaluate the average of each combination
  3. compare the average with the threshold
  4. count only combinations where the average is strictly greater than the threshold
  5. return the final count

Strict Comparison

The condition is:

average > threshold

not:

average >= threshold

Therefore, a combination whose average is exactly equal to the threshold must not be counted.

Duplicate Values

The phrase “three different elements” refers to different elements or index positions in the input collection.

If the input contains equal numeric values at different indexes, they are still separate elements and may participate in a combination unless the implementation introduces an additional distinct-value restriction.

The original task does not require numeric values inside a combination to be unique.

Implementation Notes

The implementation does not need to allocate and store all 2600 combinations.

It may generate each combination, evaluate it immediately, and increment a counter when the condition is satisfied.

This keeps memory usage constant apart from the input collection.

Task 3 — Common Values

Objective

Create a function that finds all integer values that exist in each of three input slices.

The function must return the values common to all three collections.

Input

List 1

list1 := []int{
    41, 26, 74, 25, 85, 36,
    93, 47, 56, 76, 20, 39,
}

List 2

list2 := []int{
    39, 43, 51, 26, 73, 46,
    58, 93, 75, 68, 38, 85,
}

List 3

list3 := []int{
    93, 26, 70, 29, 85, 36,
    80, 79, 50, 42, 20, 39,
}

Function

Create a function named:

TakeCommonValue(...)

The function receives:

[]int
[]int
[]int

and returns:

[]int

Requirements

A value belongs in the result only if it appears in:

list1
AND
list2
AND
list3

Values appearing in only one or two collections must not be returned.

Common Values

For the provided input, the values common to all three slices are:

26
85
93
39

Expected Result

[]int{
    26,
    85,
    93,
    39,
}

Duplicate Handling

The result should contain each common numeric value only once.

If a value occurs multiple times inside one or more input slices, that should not cause the value to be repeated in the result.

For example:

list1 = {5, 5, 5}
list2 = {5, 5}
list3 = {5}

should produce:

[]int{5}

Result Ordering

The original task defines the expected result as:

[]int{26, 85, 93, 39}

but does not explicitly define a general ordering rule.

A deterministic implementation should choose and document an ordering rule.

One reasonable option is to preserve the order in which common values appear in the first input slice.

Alternatively, the result may be sorted if the specification is intentionally extended to require sorted output.

Implementation Notes

This task represents the intersection of three integer collections.

The implementation should avoid unnecessary repeated scans when working with larger inputs.

The exact algorithm is left to the developer.

Task 4 — Closest Values to Target

Objective

Create a function that receives three integer slices and a target value.

For each input slice, find the two values that are closest to the target.

Return one pair for each input slice.

Input

List 1

list1 := []int{
    41, 19, 25, 74, 85, 36,
    93, 47, 56, 76, 20, 39,
}

List 2

list2 := []int{
    39, 43, 56, 66, 32, 46,
    58, 93, 74, 22, 81, 29,
}

List 3

list3 := []int{
    93, 47, 74, 29, 85, 36,
    80, 27, 56, 66, 20, 31,
}

The fourth argument is the target value:

target := 30

Function

Create a function named:

FindClosestPair(...)

Conceptually:

FindClosestPair(
    list1,
    list2,
    list3,
    target,
)

returns:

[][]int

Distance

For every value:

value

calculate its absolute distance from the target:

distance = abs(value - target)

The two values with the smallest distances form the result pair for that list.

List 1

Target:

30

The closest relevant values are:

25 -> distance 5
36 -> distance 6

Therefore:

[]int{25, 36}

List 2

The closest values are:

29 -> distance 1
32 -> distance 2

The expected pair follows the ordering shown in the original task:

[]int{32, 29}

List 3

The closest values are:

31 -> distance 1
27 -> distance 3

The expected pair is:

[]int{27, 31}

Expected Result

[][]int{
    {25, 36},
    {32, 29},
    {27, 31},
}

Pair Ordering

The original expected result does not order each pair by distance from the target.

For example:

{32, 29}

places 32 before 29 even though 29 is closer to 30.

Similarly:

{27, 31}

places 27 before 31.

This suggests that the pair should preserve the relative order in which the selected values appeared in the original input slice.

Therefore, after selecting the two closest values, return them in their original slice order.

Tie Handling

A general implementation must define behavior when more than two values have the same distance from the target.

For deterministic behavior, use the original index as the secondary criterion.

Conceptually, candidates are ranked by:

1. absolute distance from target
2. original index

After selecting the two closest elements, return them in their original source order.

Requirements

For each input slice:

  1. calculate the distance of every element from the target
  2. identify the two elements with the smallest distances
  3. preserve their original relative order
  4. return them as a two-element slice

The complete result contains one pair for each input collection.

Input Validation

Each input slice must contain at least two elements.

If a slice contains fewer than two elements, a valid pair cannot be produced.

The implementation should explicitly handle or reject such input.

Implementation Notes

The selected elements do not need to be adjacent.

The term “pair” in this task means the two values closest to the target, not two neighboring elements in the source slice.

Task 5 — Common Adjacent Pairs

Objective

Create a function that compares three integer slices and finds adjacent value pairs that appear in more than one slice.

For every matching pair, report:

  • the two values in the pair
  • which input slices contain that pair

Each distinct pair must appear only once in the final result.

Input

List 1

list1 := []int{
    41, 19, 25, 74, 85, 36,
    93, 47, 56, 76, 20, 39,
}

List 2

list2 := []int{
    39, 43, 56, 66, 73, 46,
    58, 93, 74, 29, 85, 36,
}

List 3

list3 := []int{
    93, 47, 74, 29, 85, 36,
    80, 25, 56, 66, 20, 39,
}

Function

Create a function named:

TakeCommonPairs(...)

The function receives three:

[]int

values and returns:

[]string

containing one description for every adjacent pair that appears in at least two input slices.

Adjacent Pair

A pair consists of two neighboring elements.

For a slice:

[]int{
    41, 19, 25, 74,
}

the adjacent pairs are:

[41,19]
[19,25]
[25,74]

A pair preserves its order.

Therefore:

[41,19]

is different from:

[19,41]

unless an implementation explicitly changes the task semantics.

Pair Generation

For a slice with length n, generate pairs using:

{list[0], list[1]}
{list[1], list[2]}
{list[2], list[3]}
...
{list[n-2], list[n-1]}

Common Pairs

A pair should be reported only when it exists in at least two different input slices.

For the provided data, the common adjacent pairs are:

[85,36]
[93,47]
[20,39]
[56,66]
[74,29]
[29,85]

Pair [85,36]

This pair exists in:

list1
list2
list3

Result description:

Pair of values [85,36] exists in lists [1,2,3].

Pair [93,47]

This pair exists in:

list1
list3

Result description:

Pair of values [93,47] exists in lists [1,3].

Pair [20,39]

This pair exists in:

list1
list3

Result description:

Pair of values [20,39] exists in lists [1,3].

Pair [56,66]

This pair exists in:

list2
list3

Result description:

Pair of values [56,66] exists in lists [2,3].

Pair [74,29]

This pair exists in:

list2
list3

Result description:

Pair of values [74,29] exists in lists [2,3].

Pair [29,85]

This pair exists in:

list2
list3

Result description:

Pair of values [29,85] exists in lists [2,3].

Expected Result

The function should return descriptions equivalent to:

Pair of values [85,36] exists in lists [1,2,3].

Pair of values [93,47] exists in lists [1,3].

Pair of values [20,39] exists in lists [1,3].

Pair of values [56,66] exists in lists [2,3].

Pair of values [74,29] exists in lists [2,3].

Pair of values [29,85] exists in lists [2,3].

Deduplication

A common pair must appear only once in the output.

For example:

[85,36]

exists in all three lists.

It must therefore produce one result:

Pair of values [85,36] exists in lists [1,2,3].

and not separate duplicate records such as:

[85,36] exists in [1,2]
[85,36] exists in [1,3]
[85,36] exists in [2,3]

Pair Identity

Pair order is significant.

For example:

[85,36]

does not match:

[36,85]

because adjacency includes the direction in which the two values appear in the slice.

Multiple Occurrences in One List

If the same adjacent pair appears multiple times inside one input slice, that slice should still be listed only once for that pair.

For example, if a pair appears twice in list1 and once in list2, its report should still contain:

lists [1,2]

rather than repeating list identifiers.

Requirements

The function must:

  1. generate all adjacent pairs from each input slice
  2. identify equivalent ordered pairs
  3. track which input slices contain every pair
  4. keep only pairs appearing in at least two different slices
  5. produce one result per distinct pair
  6. avoid duplicate reports

Result Ordering

The original task provides a specific example order but does not define a general ordering rule.

A deterministic implementation should preserve a documented ordering strategy.

One reasonable approach is to order matching pairs by the first time they are encountered while scanning:

list1
then list2
then list3

Implementation Notes

It may be useful to represent an adjacent pair internally using a comparable type such as:

type Pair struct {
    First  int
    Second int
}

This allows a pair to be used as a key while collecting the set of input lists in which it appears.

The public output can still remain:

[]string

as required by the task.

Algorithm Group 5

Algorithm Group 5 contains five larger engineering-oriented exercises focused on validation, authentication, authorization, data segmentation, generic numeric processing, and pipeline stability analysis.

Compared with earlier algorithm groups, these tasks require more than a single isolated calculation.

They combine:

  • input validation
  • structured models
  • configurable rules
  • state changes
  • authorization
  • grouping
  • numeric analysis
  • reporting
  • testing

Tasks

Task 1 — Configurable String Validation

Create a reusable validation system supporting seven different validation types.

Each validation request must also support configurable length limits, whitespace rules, descriptions, and multiple validation errors.

Task 2 — User Login and Access Control

Model users, validate their data, store multiple users, and implement login processing with authentication, authorization, password expiration, IP restrictions, login modes, and suspension rules.

Task 3 — Sector Range Analysis

Divide an integer collection into a configurable number of equal sectors.

Each sector has its own accepted numeric range.

Report which values inside every sector satisfy its range and which do not.

Task 4 — Generic Pipeline Stability Analysis

Analyze multiple numeric pipelines using either integer or floating-point values.

Each pipeline has its own stability threshold.

Report every value below that threshold together with its deficit and source pipeline.

Task 5 — Multi-Sector Pipeline Stability Report

Extend pipeline analysis by dividing each source pipeline into multiple sectors.

Each sector has its own stability threshold.

The report must identify unstable values, fully stable sectors, sectors with multiple failures, and critically unstable values.

Objectives

The exercises in this group provide practice with:

  • reusable validation
  • configurable validation rules
  • multiple-error reporting
  • structured application models
  • authentication
  • authorization
  • account state
  • time-based validation
  • IP address validation
  • collection segmentation
  • range parsing
  • generic numeric processing
  • stability thresholds
  • nested collection processing
  • report generation
  • input relationship validation
  • test design

Structured Results

Several tasks in this group require multiple pieces of related output.

Prefer explicit result structures rather than loosely typed values or formatted strings when the result is intended for further processing.

For example:

type ValidationError struct {
    Field   string
    Rule    string
    Message string
}

is generally more useful than returning only:

[]string

because presentation can be added afterward without losing structured information.

Validation

Input relationships are particularly important in this group.

Examples include:

number of sector ranges == number of sectors
number of stability values == number of pipelines
number of sector stability values == number of sectors in that pipeline

These relationships should be validated before processing begins.

Testing

The original tasks explicitly require multiple tests in several places.

Tests should cover both:

  • valid behavior
  • invalid or boundary behavior

Examples include:

  • valid and invalid strings
  • successful and failed logins
  • expired passwords
  • invalid sector counts
  • malformed ranges
  • mismatched stability configuration
  • fully stable pipelines
  • partially unstable pipelines
  • critical instability

Implementation

The examples provide concrete scenarios, but the implementation should solve the general problem.

Avoid hard-coding behavior specifically for the supplied values.

Where the original task contains an ambiguous type, inconsistent argument count, or underspecified result format, the individual task page provides a more explicit model while preserving the original objective.

Task 1 — Configurable String Validation

Objective

Create a reusable validation function that validates a collection of strings according to configurable validation rules.

The system must support seven different validation types.

A validation failure may contain multiple errors for the same input value.

Validation Types

Define the following validation types.

Type 1 — Printable Characters

All printable characters are allowed.

The value must still satisfy configured:

  • length limits
  • whitespace rules

Type 2 — ID

Allowed characters are:

A-Z
a-z
0-9
-
_

No other characters are allowed.

Type 3 — Phone Number

The value must represent a valid phone-number format.

The exact accepted phone syntax should be explicitly defined by the implementation.

At minimum, the validator should distinguish phone numbers from arbitrary text.

Type 4 — Digits Only

Only decimal digits are allowed:

0-9

Type 5 — Letters and Selected Special Characters

Allowed characters are:

A-Z
a-z
-
_
$
!
#

Digits are not allowed.

Type 6 — UTC Timeline

The value must represent a valid UTC timestamp.

For example:

2019-11-27T18:00:00Z

A practical implementation may use an RFC 3339 compatible UTC representation.

Type 7 — IP Address

The value must represent either:

IPv4

or:

IPv6

Validation Configuration

Every validation request must support additional configuration.

A possible model is:

type ValidationType int

type ValidationRule struct {
    Type              ValidationType
    Description       string
    MinLength         int
    MaxLength         int
    AllowWhitespace   bool
    MaxWhitespace     int
}

The exact structure may be changed, but equivalent information must be supported.

Description

Every value should have a description explaining what is being validated.

For example:

ValidationRule{
    Description: "User identifier",
}

This description should also be available in validation errors.

Length Validation

Each validation rule must support:

minimum number of characters
maximum number of characters

For example:

MinLength = 3
MaxLength = 32

means:

3 <= length <= 32

Whitespace Validation

Every rule must define whether whitespace is allowed.

When whitespace is allowed, the configuration must also specify the maximum number of whitespace characters.

For example:

AllowWhitespace: true,
MaxWhitespace:   2,

allows at most two whitespace characters.

If:

AllowWhitespace: false,

any whitespace occurrence is a validation error.

Input

The validator must be able to process a slice of strings.

A useful request model is:

type ValidationInput struct {
    Value string
    Rule  ValidationRule
}

Multiple values can then be validated using:

[]ValidationInput

Result Model

Use structured validation errors.

For example:

type ValidationError struct {
    Description string
    Rule        string
    Message     string
}

A complete result may be:

type ValidationResult struct {
    Valid  bool
    Errors []ValidationError
}

Multiple Errors

The validator must not stop after the first failed rule.

For example, suppose an ID:

"ab @"

must satisfy:

Type = ID
MinLength = 8
Whitespace = forbidden

The result may contain several errors:

minimum length not satisfied
invalid character '@'
whitespace is not allowed

All applicable validation errors should be returned.

Example — Valid ID

Configuration:

ValidationRule{
    Type:            ValidationTypeID,
    Description:     "User ID",
    MinLength:       3,
    MaxLength:       20,
    AllowWhitespace: false,
}

Value:

dev_user-01

Expected result:

valid

Example — Invalid ID

Value:

dev user@01

Possible errors include:

whitespace is not allowed
character '@' is not allowed

UTC Validation

A UTC timeline should be parsed and verified rather than accepted only because it superficially resembles a timestamp.

For example:

2019-11-27T18:00:00Z

is valid.

A malformed date such as:

2019-99-77T81:00:00Z

must not pass validation.

IPv4 and IPv6

Type 7 should validate actual address structure.

Examples:

192.168.1.10

and:

2001:db8::1

represent different supported address families.

Phone Validation

The original task requires a phone-number validation type but does not define one exact international phone-number grammar.

Therefore, the accepted syntax must be documented by the implementation.

Do not silently invent several incompatible phone formats.

One reasonable implementation may support a normalized international representation such as:

+381641234567

while separately deciding whether spaces or separators are allowed through the configured rules.

Configuration Validation

The validation configuration itself must also be valid.

Examples of invalid configuration include:

MinLength < 0
MaxLength < MinLength
MaxWhitespace < 0

These should be rejected before validating values.

Requirements

The validator must:

  1. support all seven validation types
  2. support minimum and maximum length
  3. support configurable whitespace behavior
  4. include a description for every analyzed value
  5. return multiple errors when multiple rules fail
  6. process multiple input values
  7. distinguish configuration errors from value-validation errors

Testing

Create multiple tests.

At minimum, test:

  • valid Type 1 value
  • invalid ID
  • valid and invalid phone numbers
  • digits-only success and failure
  • Type 5 allowed and forbidden characters
  • valid and malformed UTC values
  • valid IPv4
  • invalid IPv4
  • valid IPv6
  • invalid IPv6
  • minimum-length failure
  • maximum-length failure
  • forbidden whitespace
  • excessive whitespace
  • multiple simultaneous errors

Implementation Notes

The validator should be reusable by later tasks.

In particular, Task 2 requires validation of user attributes when users are inserted into the store.

Avoid writing a validator that only works with the example values from this task.

Task 2 — User Login and Access Control

Objective

Create a small user-access system.

The implementation must support:

  • user storage
  • user validation
  • authentication
  • password expiration
  • regular and streaming login modes
  • sector authorization
  • request authorization
  • IP-address authorization
  • failed-login tracking
  • automatic suspension

The validation system created in Task 1 must be used when new users are added.

User Model

Create a User structure containing:

Name
ID
Username
Password
PasswordValidUntil
PhoneNumber
Team
RegularLoginAllowed
StreamingLoginAllowed
AllowedSectors
AllowedRequests
AllowedIPAddresses
FailedLoginAttempts
Suspended

A possible Go representation is:

type User struct {
    Name                  string
    ID                    string
    Username              string
    Password              string
    PasswordValidUntil    time.Time
    PhoneNumber           string
    Team                  string
    RegularLoginAllowed   bool
    StreamingLoginAllowed bool
    AllowedSectors        []int
    AllowedRequests       []int
    AllowedIPAddresses    []string
    FailedLoginAttempts   int
    Suspended             bool
}

The exact field types may be adapted to the implementation.

Sector Request IDs

The system contains four sectors.

Sector 1

[]int{
    1, 2, 3,
    10, 11, 12, 13,
    20, 21, 22,
    30, 35,
    45, 46,
    50,
}

Sector 2

[]int{
    100, 101, 102, 103, 104, 105, 106,
    110, 111, 112, 113,
    115, 116, 117,
    120,
}

Sector 3

[]int{
    200, 202, 204, 206, 208,
    210, 212, 214, 216,
    220, 240,
}

Sector 4

[]int{
    302, 304, 306, 308,
    320, 325,
    350, 360, 370, 390, 399,
}

Allowed Sectors

A user may be configured for:

all
1
2
3
4

A typed representation is preferred over mixing strings and integers.

For example, an implementation may use:

type SectorAccess struct {
    All     bool
    Sectors []int
}

User Store

Create a custom user store containing three users.

You may choose the actual attribute values.

For example:

type UserStore struct {
    Users []User
}

When a new user is added:

  1. validate all required string attributes using Task 1
  2. validate the sector configuration
  3. validate request IDs
  4. validate allowed IP addresses
  5. validate password-expiration data
  6. reject the user when validation fails

LoginData

Create a structure named:

LoginData

with the following attributes:

LoginType
ReqID
Username
UserPass
AllowedAddress

For example:

type LoginData struct {
    LoginType      string
    ReqID          int
    Username       string
    UserPass       string
    AllowedAddress string
}

Login Types

The user model distinguishes:

regular login
streaming login

A typed representation is recommended.

For example:

type LoginType string

const (
    LoginTypeRegular   LoginType = "regular"
    LoginTypeStreaming LoginType = "streaming"
)

Login Processing

Create a function that receives:

LoginData

and verifies the login attempt against the user store.

Conceptually:

CheckLogin(store, loginData)

Login Validation Order

A login attempt should verify relevant conditions such as:

  1. user exists
  2. user is not suspended
  3. password is correct
  4. password has not expired
  5. requested login type is allowed
  6. source IP address is allowed
  7. request ID is allowed
  8. the request belongs to an allowed sector

All applicable authorization rules must be satisfied.

Successful Login

On successful login, return information containing:

Name
Username
Team
AllowedSectors
AllowedRequests

A structured result can be used:

type LoginSuccess struct {
    Name            string
    Username        string
    Team            string
    AllowedSectors  []int
    AllowedRequests []int
}

Failed Login

A failed login must return a list of errors.

For example:

type LoginError struct {
    Code    string
    Message string
}

The result may contain:

[]LoginError

rather than only one error.

Required Failure Scenarios

Create examples for the following failures.

Bad Username or Password

The submitted credentials do not match a valid user.

Expired Password

The current UTC time is later than:

PasswordValidUntil

Request ID Not Allowed

The requested:

ReqID

is not included in the user’s allowed request IDs.

IP Address Not Allowed

The submitted address is not contained in:

AllowedIPAddresses

Streaming Login Not Allowed

A streaming login was requested but:

StreamingLoginAllowed == false

Suspended User

The account is already suspended.

Failed Login Counter

Every failed login attempt must update:

FailedLoginAttempts

according to the suspension rule.

The original task states:

USER is suspended on second failed login attempt.

Therefore, once the failed-login count reaches:

2

set:

Suspended = true

Suspension Rule

Conceptually:

first failed login
    ↓
FailedLoginAttempts = 1
Suspended = false

second failed login
    ↓
FailedLoginAttempts = 2
Suspended = true

Further login attempts must fail because the user is suspended.

Important Security Behavior

A failed login caused by an unknown username cannot update the state of a user that does not exist.

For an existing username with invalid authentication or authorization data, the implementation must define which failure types increment the failed-login counter.

The original task does not distinguish authentication failures from authorization failures for this purpose.

Therefore, the implementation must document its chosen policy.

One reasonable interpretation is:

credential failures increment FailedLoginAttempts
authorization failures return errors without incrementing credential-failure state

but this behavior is an implementation decision beyond what the original source explicitly specifies.

Successful Login and Counter Reset

The original task does not state whether a successful login resets:

FailedLoginAttempts

Do not silently assume a reset rule.

If the implementation adds one, document it explicitly.

Request-to-Sector Relationship

A request ID belongs to one of the four defined sectors.

A login request should satisfy both:

the user is allowed to access the request ID

and:

the user is allowed to access the sector containing that request ID

Validation Integration

When users are created, use Task 1 to validate attributes such as:

  • Name
  • ID
  • Username
  • Password
  • PasswordValidUntil
  • PhoneNumber
  • AllowedIPAddresses

The appropriate validation type should be selected for each field.

Testing

Create at least:

  • one successful login
  • bad username or password
  • expired password
  • unauthorized request ID
  • unauthorized IP address
  • forbidden streaming login
  • suspended-user login
  • first failed login
  • second failed login causing suspension

Implementation Notes

Keep the responsibilities separated.

A useful design is:

User validation
      ↓
User store
      ↓
Authentication
      ↓
Authorization
      ↓
Account-state update
      ↓
Structured response

Avoid implementing every rule inside one large conditional block.

Task 3 — Sector Range Analysis

Objective

Create a function that divides an integer slice into a configurable number of equal sectors.

Every sector has its own accepted numeric range.

For each sector, return:

  • values that satisfy the sector range
  • values that do not satisfy the sector range

Input

The function has three input arguments.

The original task refers to two inputs but subsequently defines three separate arguments.

This specification uses the three arguments actually required by the task.

Argument 1 — Values

The first argument is a slice containing exactly 30 integers:

values := []int{
    82, 45, 67, 12, 94,
    38, 71, 56, 53, 29,
    39, 87, 21, 64, 54,
    32, 79, 14, 92, 68,
    53, 27, 81, 60, 49,
    73, 11, 93, 37, 65,
}

Argument 2 — Sector Count

The second argument determines how many equal sectors the input should be divided into.

Allowed values are:

2
3
5
6
10

Because the input contains 30 elements, these values divide the input evenly.

Sector Sizes

The resulting sector sizes are:

2 sectors  -> 15 elements per sector
3 sectors  -> 10 elements per sector
5 sectors  -> 6 elements per sector
6 sectors  -> 5 elements per sector
10 sectors -> 3 elements per sector

Sector Creation

Sectors are created using the original order of the input slice.

For example, with:

sectorCount = 5

the 30 values are divided into five consecutive groups containing six values each.

Conceptually:

Sector 1 -> values[0:6]
Sector 2 -> values[6:12]
Sector 3 -> values[12:18]
Sector 4 -> values[18:24]
Sector 5 -> values[24:30]

Argument 3 — Sector Ranges

The third argument is:

[]string

containing one accepted range for every sector.

A range is represented as:

min-max

For example:

50-60

means:

value >= 50 && value <= 60

Both boundaries are inclusive.

Example Range Configuration

If:

sectorCount = 2

a valid range configuration could be:

[]string{
    "25-35",
    "60-75",
}

The number of ranges must equal the number of sectors.

Result Model

Define a result for each sector.

For example:

type SectorResult struct {
    SectorID      int
    Range         string
    Matching      []int
    NotMatching   []int
}

The complete function may return:

type SectorAnalysis struct {
    SectorCount int
    Sectors     []SectorResult
}

Processing

For every sector:

  1. parse its range
  2. inspect each value belonging to that sector
  3. compare the value with the inclusive range
  4. place matching values into Matching
  5. place non-matching values into NotMatching

Example Condition

For:

range = "50-60"

the condition is:

50 <= value <= 60

Examples:

49 -> not matching
50 -> matching
55 -> matching
60 -> matching
61 -> not matching

Range Parsing

A range must contain two valid integer boundaries.

For:

25-35

parse:

minimum = 25
maximum = 35

A valid range requires:

minimum <= maximum

Validation

The function must validate:

len(values) == 30

for the original exercise.

It must also validate:

sectorCount ∈ {2, 3, 5, 6, 10}

and:

len(ranges) == sectorCount

Every range must be syntactically valid.

Invalid Range Examples

Examples of invalid ranges include:

"abc"
"50"
"60-50"
"10-x"

These should produce a validation error.

Requirements

The function must:

  1. receive the 30-element source slice
  2. validate the requested sector count
  3. divide the source into equal consecutive sectors
  4. validate one range per sector
  5. parse every range
  6. classify each value
  7. return both matching and non-matching values for every sector
  8. report the total number of sectors

Testing

Create multiple tests.

Recommended cases include:

  • 2 sectors
  • 3 sectors
  • 5 sectors
  • 6 sectors
  • 10 sectors
  • invalid sector count
  • incorrect number of ranges
  • malformed range
  • reversed range boundaries
  • sector where all values match
  • sector where no values match

Implementation Notes

The source order determines sector membership.

The task does not ask for values to be sorted before division.

Therefore:

divide first
then analyze

rather than:

sort first
then divide

unless an extended version of the task explicitly introduces sorting.

Task 4 — Generic Pipeline Stability Analysis

Objective

Create a function named:

ReportForInvalidPipelines(...)

that analyzes multiple numeric pipelines.

The function must support two numeric forms:

integer
float32

Every pipeline has its own stability level.

For every value below the stability level, report:

  • the value
  • the required stability level
  • how far below the stability level the value is
  • the pipeline from which the value originated

Integer Form

Example:

pipelines := [][]int{
    {
        72, 55, 63, 48, 91,
        34, 25, 81, 60, 19,
        42, 88, 77, 93, 69,
        18, 57, 36, 41, 84,
    },
    {
        54, 73, 29, 92, 68,
        15, 84, 59, 67, 21,
        39, 64, 98, 41, 26,
        18, 33, 51, 72, 80,
    },
}

Stability levels:

stability := []int{
    50,
    65,
}

The relationship is:

pipelines[0] -> stability[0] = 50
pipelines[1] -> stability[1] = 65

Float Form

Example:

pipelines := [][]float32{
    {
        10.51, 98.74, 56.35, 81.26, 67.88,
        49.57, 26.14, 12.77, 94.31, 38.90,
    },
    {
        53.14, 29.82, 61.47, 74.56, 85.73,
        10.12, 77.64, 58.92, 34.91, 69.78,
    },
}

Stability levels:

stability := []float32{
    50.50,
    65.75,
}

Stability Rule

For pipeline i:

threshold = stability[i]

A value is invalid when:

value < threshold

A value equal to the threshold is stable.

Difference

For every invalid value:

difference = threshold - value

The result must therefore always be positive.

Suggested Result Model

Conceptually:

type InvalidPipelineValue[T Number] struct {
    PipelineIndex int
    Value         T
    Stability     T
    Difference    T
}

and:

type PipelineReport[T Number] struct {
    AnalysisType string
    Invalid      []InvalidPipelineValue[T]
}

The exact generic syntax depends on the implementation language.

Go Implementation Options

In Go, this may be solved using:

  • generics
  • two typed wrappers around shared logic
  • another explicitly typed abstraction

The implementation should avoid an unnecessarily weak interface{} result when a type-safe solution is possible.

Integer Example

For the first integer pipeline:

stability = 50

values such as:

48
34
25
19
42
18
36
41

are below the stability level.

For:

value = 48

the deficit is:

50 - 48 = 2

For:

value = 25

the deficit is:

50 - 25 = 25

Float Example

For the first float pipeline:

stability = 50.50

a value such as:

49.57

has deficit:

50.50 - 49.57 = 0.93

Floating-point presentation may require formatting, but comparisons should use the numeric values rather than formatted strings.

Input Relationship

There must be exactly one stability value for every pipeline.

Therefore:

len(pipelines) == len(stability)

must hold.

Otherwise the input configuration is invalid.

Analysis Type

The original task requires information about the “type of analysis” and mentions distinguishing whether a 1D or 2D slice is being analyzed.

However, both provided function forms use a two-dimensional first argument:

[][]int
[][]float32

Therefore, the original source does not fully define what “1D or 2D analysis” means in this context.

Do not invent additional 1D behavior without extending the specification.

At minimum, the report should identify the numeric form being analyzed, for example:

integer pipelines

or:

float32 pipelines

If support for true 1D input is later added, it should be documented as a separate extension.

Requirements

The function must:

  1. support integer pipeline analysis
  2. support float32 pipeline analysis
  3. associate each pipeline with its stability level
  4. find every value below the threshold
  5. calculate the deficit
  6. report the source pipeline
  7. identify the analysis form
  8. validate input relationships

Testing

Create multiple tests.

At minimum:

  • integer example
  • float32 example
  • all values stable
  • all values unstable
  • value exactly equal to stability
  • mismatched number of pipelines and stability values
  • empty input
  • floating-point boundary case

Implementation Notes

The core algorithm is identical for both numeric forms:

for each pipeline
    ↓
resolve its threshold
    ↓
for each value
    ↓
if value < threshold
    ↓
calculate deficit
    ↓
add report entry

The numeric type changes, but the processing model does not.

Task 5 — Multi-Sector Pipeline Stability Report

Objective

Create an advanced pipeline-analysis function named:

ReportForInvalidPipelines(...)

Each source pipeline must first be divided into a configurable number of equal sectors.

Every sector has its own stability level.

The function must then produce a detailed report describing:

  • every value below its sector stability level
  • how far each invalid value is below that level
  • the source pipeline
  • the source sector
  • fully stable sectors
  • sectors containing two or more unstable values
  • values whose stability deficit is greater than a global critical threshold

Input

The function has four input arguments:

1. pipelines
2. sector counts
3. sector stability levels
4. critical deficit

Argument 1 — Pipelines

The first argument is:

[][]int

The provided data contains six pipelines.

Pipeline 1

[]int{
    37, 92, 61, 28,
    55, 78, 10, 91,
    64, 84, 56, 73,
    29, 19, 49, 88,
}

Length:

16

Pipeline 2

[]int{
    64, 35, 46, 78,
    52, 23, 97, 12,
    61, 41, 85, 70,
    99, 54, 16, 68,
}

Length:

16

Pipeline 3

[]int{
    15, 58, 91, 37,
    67, 74, 29, 50,
    84, 63, 95, 12,
    39, 68, 27, 72,
    44, 10, 81, 90,
    80, 36, 79, 43,
}

Length:

24

Pipeline 4

[]int{
    25, 61, 92, 34,
    47, 83, 52, 64,
    11, 70, 86, 59,
    28, 65, 49, 40,
    99, 74, 53, 80,
    63, 82, 30, 72,
}

Length:

24

Pipeline 5

[]int{
    58, 33, 47, 91,
    29, 72, 56, 39,
    61, 87, 64, 49,
    88, 72, 44, 61,
    57, 55, 69, 95,
}

Length:

20

Pipeline 6

[]int{
    48, 92, 65, 44,
    41, 59, 40, 90,
    77, 55, 33, 68,
    19, 84, 61, 43,
    12, 81, 74, 56,
}

Length:

20

Argument 2 — Sector Counts

The second argument is:

[]int

There must be exactly one sector count for every source pipeline.

Allowed sector counts depend on pipeline length.

16-Element Pipeline

Allowed:

2
4

20-Element Pipeline

Allowed:

4
5

24-Element Pipeline

Allowed:

3
4
6

These configurations guarantee equal-size sectors.

Sector Size

For a pipeline with:

N elements

divided into:

S sectors

each sector contains:

N / S

values.

For example:

16 elements / 4 sectors = 4 values per sector

Argument 3 — Stability Configuration

The third argument is:

[][]int

There must be one inner stability slice for every pipeline.

The number of stability values inside that slice must equal the number of sectors selected for that pipeline.

For example:

sectorCounts[0] = 2

requires:

len(stability[0]) == 2

If:

stability[0] = []int{50, 70}

then:

Sector 1 stability = 50
Sector 2 stability = 70

Argument 4 — Critical Deficit

The fourth argument is a single integer.

It defines when an unstable value should also be classified as critically below its required stability level.

For example:

criticalDeficit = 15

means a value is critical when:

stability - value > 15

Case 1

Sector counts:

[]int{
    2,
    4,
    3,
    6,
    4,
    5,
}

Stability configuration:

[][]int{
    {50, 70},
    {60, 70, 40, 65},
    {40, 60, 50},
    {60, 40, 50, 70, 60, 70},
    {50, 40, 40, 50},
    {40, 30, 50, 70, 60},
}

Critical deficit:

15

Case 1 Sector Mapping

Pipeline 1

Length:

16

Sector count:

2

Each sector contains:

8 values

Stability levels:

Sector 1 -> 50
Sector 2 -> 70

Pipeline 2

Length:

16

Sector count:

4

Each sector contains:

4 values

Stability levels:

Sector 1 -> 60
Sector 2 -> 70
Sector 3 -> 40
Sector 4 -> 65

Pipeline 3

Length:

24

Sector count:

3

Each sector contains:

8 values

Stability levels:

40
60
50

Pipeline 4

Length:

24

Sector count:

6

Each sector contains:

4 values

Stability levels:

60
40
50
70
60
70

Pipeline 5

Length:

20

Sector count:

4

Each sector contains:

5 values

Stability levels:

50
40
40
50

Pipeline 6

Length:

20

Sector count:

5

Each sector contains:

4 values

Stability levels:

40
30
50
70
60

Case 2

Sector counts:

[]int{
    4,
    2,
    6,
    3,
    5,
    4,
}

Stability configuration:

[][]int{
    {60, 70, 40, 65},
    {50, 70},
    {60, 40, 50, 70, 60, 70},
    {40, 60, 50},
    {50, 40, 40, 50, 70},
    {40, 30, 50, 70},
}

Critical deficit:

20

Sector Processing

For every pipeline:

  1. determine its sector count
  2. calculate sector size
  3. divide the pipeline into consecutive sectors
  4. retrieve the stability level for each sector
  5. analyze every value

Sector membership is based on original position.

Do not sort the pipeline before dividing it.

Stability Rule

A value is stable when:

value >= stability

A value is unstable when:

value < stability

Stability Deficit

For an unstable value:

deficit = stability - value

For example:

stability = 60
value     = 37

produces:

deficit = 23

Critical Instability

An unstable value is also critical when:

deficit > criticalDeficit

For:

criticalDeficit = 15

a deficit of:

16

is critical.

A deficit of exactly:

15

is not critical because the original task requires values below the stability level by more than the configured amount.

Suggested Result Model

Use structured output.

For example:

type InvalidValue struct {
    Value       int
    Stability   int
    Deficit     int
    Critical    bool
    SourceIndex int
}

A sector result can be:

type SectorReport struct {
    SectorIndex        int
    Stability          int
    InvalidValues      []InvalidValue
    FullyStable        bool
    MultipleFailures   bool
}

A pipeline result can be:

type PipelineReport struct {
    PipelineIndex int
    SectorCount   int
    SectorSize    int
    Sectors       []SectorReport
}

The complete result can be:

type StabilityReport struct {
    CriticalDeficit int
    Pipelines       []PipelineReport
}

The exact naming may be changed.

Fully Stable Sector

A sector is fully stable when every value satisfies:

value >= stability

Therefore:

FullyStable = true

only when:

len(InvalidValues) == 0

Sector with Multiple Failures

The task requires reporting sectors containing:

2 or more values below stability

Therefore:

MultipleFailures = len(InvalidValues) >= 2

Invalid Value Report

Every unstable value must preserve enough information to identify:

  • its numeric value
  • required stability
  • deficit
  • pipeline
  • sector
  • optionally its original source index

For example:

InvalidValue{
    Value:       37,
    Stability:   60,
    Deficit:     23,
    Critical:    true,
    SourceIndex: 0,
}

Validation

The following relationships must be validated.

Pipeline Count

len(pipelines) == len(sectorCounts)

Stability Configuration Count

len(pipelines) == len(stability)

Valid Sector Count

The sector count must be allowed for the corresponding pipeline length.

Equal Sector Division

The pipeline length must be evenly divisible by the requested sector count.

Stability Count Per Pipeline

For every pipeline i:

len(stability[i]) == sectorCounts[i]

Critical Deficit

The critical deficit should not be negative.

Invalid Configuration Example

A 16-element pipeline cannot be configured with:

3 sectors

under the original task rules.

Likewise:

sectorCount = 4
stability values = {50, 60}

is invalid because four sectors require four stability levels.

Requirements

The function must report:

  1. every value below its stability level
  2. the deficit for every unstable value
  3. the source pipeline
  4. the source sector
  5. every fully stable sector
  6. every sector with two or more unstable values
  7. every value whose deficit exceeds the critical threshold

Testing

The original task requires:

  1. testing Case 1
  2. testing Case 2
  3. creating one additional example using different values

Additional useful tests include:

  • all sectors stable
  • all sectors unstable
  • one invalid value in a sector
  • exactly two invalid values
  • deficit exactly equal to critical threshold
  • deficit one greater than critical threshold
  • invalid sector configuration
  • mismatched stability configuration
  • negative critical deficit

Source Data Difference

The original task initially lists one set of values for pipelines 5 and 6, then its Case 1 and Case 2 examples use slightly different values in those pipelines.

For example, the initial pipeline data includes values such as:

Pipeline 5: ... 27, 55 ...
Pipeline 6: ... 34, 11 ...

while the later executable cases use:

Pipeline 5: ... 57, 55 ...
Pipeline 6: ... 44, 41 ...

The two case definitions should therefore be treated as their own explicit test inputs rather than assumed to be identical to the earlier introductory dataset.

Implementation Notes

This task extends Task 4.

Task 4 uses:

one threshold per pipeline

Task 5 uses:

one or more sectors per pipeline
+
one threshold per sector
+
a global critical-deficit threshold

A clean processing model is:

Validate configuration
        ↓
For each pipeline
        ↓
Divide into sectors
        ↓
Resolve sector stability
        ↓
Analyze values
        ↓
Build sector report
        ↓
Build pipeline report
        ↓
Return complete report

Avoid creating separate processing code for 16-element, 20-element, and 24-element pipelines.

Their allowed sector counts differ, but the analysis algorithm itself should remain generic.

Algorithm Group 6

Algorithm Group 6 focuses on graph relationships, dependency resolution, scheduling, state transitions, and event-stream analysis.

The exercises in this group are designed to model problems that commonly appear in backend services, build systems, deployment tooling, distributed systems, workflow engines, and infrastructure orchestration.

Compared with earlier groups, the input data is less often a simple list.

Instead, the implementation must reason about relationships between entities.

Tasks

Task 1 — Dependency Graph Resolution

Analyze a set of services and their dependencies.

Determine a valid startup order, detect missing dependencies, and identify dependency cycles.

Task 2 — Weighted Route Resolution

Given a weighted graph, find the lowest-cost route between two nodes and return both the selected path and its total cost.

Task 3 — Dependency-Aware Task Scheduler

Schedule tasks that have execution durations and dependencies across a limited number of workers.

Determine when every task starts and finishes and calculate total execution time.

Task 4 — State Machine Validation

Validate a sequence of state transitions against a defined state machine.

Report invalid transitions and determine the final valid state.

Task 5 — Event Stream Window Analysis

Process timestamped events from multiple sources.

Group events into time windows and report source activity, missing intervals, and burst conditions.

Objectives

The exercises in this group provide practice with:

  • directed graphs
  • dependency traversal
  • topological ordering
  • cycle detection
  • graph validation
  • weighted graphs
  • shortest-path reasoning
  • scheduling
  • worker allocation
  • dependency completion
  • state machines
  • transition validation
  • event streams
  • timestamp ordering
  • time-window aggregation
  • deterministic reporting

Graph Terminology

Several tasks use graph-like relationships.

A node represents an entity.

Examples include:

service
task
location
state

An edge represents a relationship.

Examples include:

service A depends on service B
location A connects to location B
state A can transition to state B

The direction of an edge is important.

Deterministic Output

Some graph problems may have multiple valid answers.

When several valid results exist, implementations should use deterministic tie-breaking.

Unless a task specifies another rule, prefer:

lexicographical ID order

for equivalent candidates.

This keeps tests reproducible.

Validation

Inputs should be validated before processing.

Examples include:

  • duplicate node IDs
  • references to missing nodes
  • negative route costs
  • self-dependencies
  • malformed timestamps
  • duplicate state transitions
  • invalid worker counts

Invalid input should be distinguished from a valid problem that simply has no solution.

Implementation

The tasks are language-independent.

They may be implemented in Go, Rust, or another language.

The exact internal data structures are left to the developer.

The important requirement is that the algorithm solves the general problem rather than only the provided examples.

Task 1 — Dependency Graph Resolution

Objective

Create a function that analyzes dependencies between services.

The function must determine:

  • whether the dependency graph is valid
  • whether every referenced dependency exists
  • whether dependency cycles exist
  • a valid startup order when the graph is valid

A service may start only after all of its dependencies have started.

Input Model

Each service has:

ID
Dependencies

A possible representation is:

type Service struct {
    ID           string
    Dependencies []string
}

Input

Use the following services:

services := []Service{
    {
        ID:           "gateway",
        Dependencies: []string{"api"},
    },
    {
        ID:           "api",
        Dependencies: []string{"auth", "database"},
    },
    {
        ID:           "auth",
        Dependencies: []string{"database"},
    },
    {
        ID:           "database",
        Dependencies: []string{},
    },
    {
        ID:           "metrics",
        Dependencies: []string{"api"},
    },
}

Dependency Meaning

For:

gateway -> api

the meaning is:

gateway depends on api

Therefore:

api must start before gateway

For:

api -> database

the database must start before the API.

Expected Startup Relationships

The following relationships must be satisfied:

database before auth
database before api
auth before api
api before gateway
api before metrics

Possible Startup Order

One valid startup order is:

[]string{
    "database",
    "auth",
    "api",
    "gateway",
    "metrics",
}

Another valid order could be:

[]string{
    "database",
    "auth",
    "api",
    "metrics",
    "gateway",
}

Both satisfy the dependency graph.

Deterministic Result

When multiple nodes are available at the same time, use lexicographical ID ordering.

With that rule, the expected result is:

[]string{
    "database",
    "auth",
    "api",
    "gateway",
    "metrics",
}

because:

gateway < metrics

lexicographically.

Result Model

A structured result is recommended:

type DependencyResult struct {
    Valid               bool
    StartupOrder        []string
    MissingDependencies []MissingDependency
    Cycles              [][]string
}

For example:

type MissingDependency struct {
    ServiceID    string
    DependencyID string
}

Missing Dependency Case

Consider:

services := []Service{
    {
        ID:           "api",
        Dependencies: []string{"database"},
    },
}

but no service with:

ID = database

exists.

The resolver must report:

api references missing dependency database

The graph is invalid.

Cycle Case

Consider:

service-a depends on service-b
service-b depends on service-c
service-c depends on service-a

This creates the cycle:

service-a
    ↓
service-b
    ↓
service-c
    ↓
service-a

No valid startup order exists.

The resolver must report the cycle.

Self Dependency

A service must not depend on itself.

Invalid example:

Service{
    ID:           "api",
    Dependencies: []string{"api"},
}

This should be treated as a dependency cycle or explicit validation error.

Duplicate Services

Service IDs must be unique.

For example:

Service{ID: "api"}
Service{ID: "api"}

is invalid input.

Duplicate Dependencies

A service should not need to declare the same dependency multiple times.

For example:

Dependencies: []string{
    "database",
    "database",
}

should either be rejected or normalized.

The implementation must use one consistent rule.

Requirements

The resolver must:

  1. validate unique service IDs
  2. validate all dependency references
  3. detect self-dependencies
  4. detect dependency cycles
  5. produce a valid startup order
  6. use deterministic ordering when multiple choices exist

Additional Test — Independent Services

Input:

[]Service{
    {ID: "database"},
    {ID: "cache"},
    {ID: "worker"},
}

No service depends on another.

With lexicographical ordering, the result is:

[]string{
    "cache",
    "database",
    "worker",
}

Additional Test — Multiple Dependency Levels

Input:

A depends on B and C
B depends on D
C depends on D
D has no dependencies

A valid execution relationship is:

D
↓
B and C
↓
A

Implementation Notes

This problem can be solved using a topological ordering algorithm.

Possible approaches include:

  • dependency-count processing
  • depth-first traversal
  • another correct graph-based solution

The implementation should not rely on repeatedly hard-coding known service names.

The same resolver must work for arbitrary service graphs.

Task 2 — Weighted Route Resolution

Objective

Create a function that finds the lowest-cost route between two nodes in a weighted graph.

The function must return:

  • the selected path
  • the total route cost

The graph represents bidirectional connections.

Node Model

Each location is identified by a string ID.

Example:

A
B
C
D
E
F

Edge Model

Each connection contains:

From
To
Cost

A possible model is:

type Edge struct {
    From string
    To   string
    Cost int
}

Input Graph

Use the following connections:

edges := []Edge{
    {From: "A", To: "B", Cost: 4},
    {From: "A", To: "C", Cost: 2},
    {From: "B", To: "C", Cost: 1},
    {From: "B", To: "D", Cost: 5},
    {From: "C", To: "D", Cost: 8},
    {From: "C", To: "E", Cost: 10},
    {From: "D", To: "E", Cost: 2},
    {From: "D", To: "F", Cost: 6},
    {From: "E", To: "F", Cost: 3},
}

All edges are bidirectional.

Therefore:

A -> B

also allows:

B -> A

with the same cost.

Function

Conceptually:

FindLowestCostRoute(
    edges,
    source,
    target,
)

Example Request

Find the lowest-cost route from:

A

to:

F

Candidate Routes

One possible route is:

A -> C -> E -> F

Cost:

2 + 10 + 3 = 15

Another route is:

A -> B -> D -> F

Cost:

4 + 5 + 6 = 15

Another route is:

A -> C -> B -> D -> E -> F

Cost:

2 + 1 + 5 + 2 + 3 = 13

Therefore the lowest-cost route is:

A -> C -> B -> D -> E -> F

with total cost:

13

Expected Result

A possible result model is:

type RouteResult struct {
    Found bool
    Path  []string
    Cost  int
}

Expected result:

RouteResult{
    Found: true,
    Path: []string{
        "A",
        "C",
        "B",
        "D",
        "E",
        "F",
    },
    Cost: 13,
}

No Route

If the target cannot be reached from the source:

RouteResult{
    Found: false,
}

should be returned.

Do not return an arbitrary partially completed path.

Source Equals Target

For:

source = A
target = A

the route is valid immediately.

Expected result:

RouteResult{
    Found: true,
    Path:  []string{"A"},
    Cost:  0,
}

Cost Validation

Route costs must not be negative.

This task assumes:

cost >= 0

for every edge.

Negative costs should be rejected as invalid input.

Missing Node

If either:

source

or:

target

does not exist in the graph, return a validation error.

Duplicate Connections

If multiple edges connect the same two nodes, the implementation must define how they are handled.

One reasonable rule is to preserve all valid edges and allow the route algorithm to naturally choose the cheapest one.

Equal-Cost Routes

Multiple routes may have the same total cost.

When several routes have equal minimum cost, use a deterministic tie-breaking rule.

One possible rule is:

select the lexicographically smallest complete path

The implementation must document the chosen rule.

Requirements

The function must:

  1. validate the graph
  2. verify that source and target exist
  3. find the lowest total route cost
  4. reconstruct the complete selected path
  5. return the path and total cost
  6. report when no route exists
  7. avoid infinite traversal through cycles

Additional Test

Find the lowest-cost route from:

A

to:

D

Possible routes include:

A -> B -> D
cost = 9

and:

A -> C -> B -> D
cost = 8

Therefore the expected lowest cost is:

8

with path:

A -> C -> B -> D

Implementation Notes

This is a weighted shortest-path problem.

An efficient implementation should avoid enumerating every possible route when unnecessary.

The algorithm should work with arbitrary graph sizes and must not depend on the supplied node names.

Task 3 — Dependency-Aware Task Scheduler

Objective

Create a scheduler that executes tasks across a limited number of workers.

Each task has:

  • an ID
  • execution duration
  • zero or more dependencies

A task may begin only after all of its dependencies have completed.

The scheduler must determine:

  • worker assignment
  • start time
  • finish time
  • final execution order
  • total workflow duration

Task Model

A possible model is:

type Task struct {
    ID           string
    Duration     int
    Dependencies []string
}

Duration is expressed in seconds.

Input

Use the following tasks:

tasks := []Task{
    {
        ID:       "fetch-source",
        Duration: 4,
    },
    {
        ID:           "build-api",
        Duration:     8,
        Dependencies: []string{"fetch-source"},
    },
    {
        ID:           "build-worker",
        Duration:     6,
        Dependencies: []string{"fetch-source"},
    },
    {
        ID:           "test-api",
        Duration:     5,
        Dependencies: []string{"build-api"},
    },
    {
        ID:           "test-worker",
        Duration:     3,
        Dependencies: []string{"build-worker"},
    },
    {
        ID:           "package",
        Duration:     4,
        Dependencies: []string{"test-api", "test-worker"},
    },
    {
        ID:           "deploy",
        Duration:     2,
        Dependencies: []string{"package"},
    },
}

Number of workers:

2

Scheduling Rules

Time begins at:

0

A worker may execute only one task at a time.

A task becomes ready when:

all dependencies are complete

When multiple tasks are ready at the same time, select them using lexicographical task ID order.

When multiple workers are available, use the worker with the lowest worker ID.

Workers are identified as:

worker-1
worker-2
...

Initial State

At time:

0

only:

fetch-source

has no dependencies.

Therefore:

worker-1:
fetch-source
start = 0
finish = 4

Worker 2 remains idle.

Time 4

When fetch-source finishes, two tasks become ready:

build-api
build-worker

With two workers:

worker-1 -> build-api
worker-2 -> build-worker

Their execution is:

build-api:
start = 4
finish = 12

build-worker:
start = 4
finish = 10

Time 10

build-worker completes.

Therefore:

test-worker

becomes ready.

Worker 2 executes:

test-worker:
start = 10
finish = 13

Time 12

build-api completes.

Therefore:

test-api

becomes ready.

Worker 1 executes:

test-api:
start = 12
finish = 17

Time 13

test-worker is complete.

However:

package

cannot begin because:

test-api

has not completed yet.

Worker 2 remains idle.

Time 17

Both package dependencies are complete.

Start:

package:
start = 17
finish = 21

Time 21

Start:

deploy:
start = 21
finish = 23

Expected Total Duration

The complete workflow finishes at:

23 seconds

Suggested Result Model

type ScheduledTask struct {
    TaskID    string
    WorkerID  int
    StartTime int
    FinishTime int
}

type ScheduleResult struct {
    Valid         bool
    Tasks         []ScheduledTask
    TotalDuration int
}

Expected Schedule

Conceptually:

fetch-source
worker 1
0 -> 4

build-api
worker 1
4 -> 12

build-worker
worker 2
4 -> 10

test-worker
worker 2
10 -> 13

test-api
worker 1
12 -> 17

package
worker 1
17 -> 21

deploy
worker 1
21 -> 23

Worker assignment for later tasks may depend on the exact deterministic worker-allocation rule, but total scheduling behavior must satisfy dependencies.

Dependency Validation

The scheduler must reject:

  • missing dependencies
  • duplicate task IDs
  • self-dependencies
  • dependency cycles

A cyclic dependency graph cannot be scheduled.

Duration Validation

Task duration must satisfy:

Duration > 0

Zero or negative durations are invalid for this exercise.

Worker Validation

Worker count must satisfy:

workers >= 1

More Ready Tasks Than Workers

If five tasks are ready but only two workers are available, execute only two.

The remaining ready tasks must wait.

Use the deterministic task-ordering rule to decide which tasks are selected first.

Worker Idle Time

Workers are allowed to remain idle when:

  • no task is ready
  • all remaining tasks are waiting for dependencies

The scheduler must not violate dependency rules merely to keep workers busy.

Requirements

The scheduler must:

  1. validate the task graph
  2. track completed dependencies
  3. track worker availability
  4. identify ready tasks
  5. assign tasks deterministically
  6. calculate start and finish times
  7. preserve dependency constraints
  8. calculate total workflow duration

Important Distinction

A valid topological task order is not enough.

This task also requires calculating actual execution timing with limited workers.

For example:

A
↓
B

and:

A
↓
C

may allow B and C to run in parallel when multiple workers are available.

Extension

An advanced implementation may also report:

worker utilization
worker idle time
critical execution path

These are optional extensions and are not required by the base task.

Implementation Notes

A useful implementation strategy is event-based scheduling.

The scheduler can repeatedly process the next moment when one or more running tasks finish.

At each scheduling point:

mark completed tasks
      ↓
resolve newly ready tasks
      ↓
find available workers
      ↓
assign work
      ↓
advance to next completion time

The algorithm must remain generic for arbitrary tasks, dependencies, durations, and worker counts.

Task 4 — State Machine Validation

Objective

Create a function that validates a sequence of state transitions against a predefined state machine.

The function must:

  • verify every requested transition
  • report invalid transitions
  • preserve the last valid state
  • return the final state after processing

Service States

Use the following states:

created
starting
running
stopping
stopped
failed

Allowed Transitions

The valid transitions are:

created  -> starting

starting -> running
starting -> failed

running  -> stopping
running  -> failed

stopping -> stopped
stopping -> failed

failed   -> starting

stopped  -> starting

Any transition not listed above is invalid.

Transition Model

A transition request contains:

From
To

A possible model is:

type Transition struct {
    From string
    To   string
}

However, a safer API may accept only requested target states and derive From from the current state.

For example:

ProcessTransitions(
    initialState,
    requestedStates,
)

Input

Initial state:

created

Requested states:

[]string{
    "starting",
    "running",
    "stopping",
    "stopped",
    "starting",
    "running",
}

Expected Processing

Start:

created

Transition:

created -> starting

valid.

Next:

starting -> running

valid.

Next:

running -> stopping

valid.

Next:

stopping -> stopped

valid.

Next:

stopped -> starting

valid.

Next:

starting -> running

valid.

Expected Final State

running

Invalid Transition Example

Initial state:

created

Requested sequence:

[]string{
    "running",
    "stopping",
}

The first transition would be:

created -> running

which is invalid.

The implementation must report the failure.

Processing Policy

When a requested transition is invalid:

  1. report the invalid transition
  2. do not update the current state

The next request is evaluated against the last valid state.

For example:

Current state = created

Request:
running

invalid.

Current state remains:

created

If the next request is:

starting

then:

created -> starting

is valid.

Suggested Result Model

type TransitionResult struct {
    Index     int
    From      string
    To        string
    Valid     bool
    Message   string
}

type StateMachineResult struct {
    InitialState string
    FinalState   string
    Transitions  []TransitionResult
}

Unknown State

A state not defined by the state machine is invalid.

For example:

paused

must not be silently accepted.

Same-State Transition

Transitions such as:

running -> running

are invalid unless explicitly listed.

The provided state machine does not define self-transitions.

Failed State

The failed state may recover through:

failed -> starting

This models a restart attempt.

Direct transition:

failed -> running

is not allowed.

Stopped State

A stopped service may restart through:

stopped -> starting

It may not directly transition to:

running

Requirements

The function must:

  1. validate the initial state
  2. validate every target state
  3. validate transitions against the state graph
  4. record every attempted transition
  5. preserve the current state after invalid transitions
  6. return the final valid state
  7. distinguish valid and invalid requests

Additional Test

Initial:

created

Requests:

starting
failed
running
starting
running

Processing:

created -> starting
valid

starting -> failed
valid

failed -> running
invalid

failed -> starting
valid

starting -> running
valid

Final state:

running

Configuration Extension

An advanced implementation may represent allowed transitions as data rather than hard-coded conditions.

For example:

created:
  starting

starting:
  running
  failed

running:
  stopping
  failed

This makes the state machine easier to extend.

Implementation Notes

This task can be modeled as a directed graph where:

states = nodes
allowed transitions = directed edges

The processing algorithm is simple, but correctness depends on maintaining the current state consistently after failures.

Task 5 — Event Stream Window Analysis

Objective

Create a function that analyzes timestamped events from multiple sources.

The function must group events into fixed-duration time windows and produce activity information for every window.

The analysis must identify:

  • number of events per source
  • total number of events
  • burst conditions
  • windows with no events
  • globally missing source activity

Event Model

Each event contains:

Timestamp
Source
Type
Value

A possible model is:

type Event struct {
    Timestamp time.Time
    Source    string
    Type      string
    Value     int
}

Input

Use the following events:

10:00:05  api-1     request   1
10:00:20  api-1     request   1
10:00:45  worker-1  job       1

10:01:10  api-2     request   1
10:01:12  api-2     request   1
10:01:14  api-2     request   1
10:01:16  api-2     request   1
10:01:18  worker-1  job       1

10:03:05  api-1     request   1
10:03:40  worker-2  job       1

Assume all events belong to the same UTC date.

Window Duration

Use:

1 minute

windows.

The analysis starts at:

10:00:00

and ends after the window containing the final event.

Therefore the windows are:

10:00:00 - 10:00:59
10:01:00 - 10:01:59
10:02:00 - 10:02:59
10:03:00 - 10:03:59

Window Membership

A window uses:

[start, end)

semantics.

For example:

10:00:00 <= timestamp < 10:01:00

belongs to the first window.

An event exactly at:

10:01:00

belongs to the second window.

Window 1

Events:

10:00:05 api-1
10:00:20 api-1
10:00:45 worker-1

Source counts:

api-1    = 2
worker-1 = 1

Total:

3

Window 2

Events:

10:01:10 api-2
10:01:12 api-2
10:01:14 api-2
10:01:16 api-2
10:01:18 worker-1

Source counts:

api-2    = 4
worker-1 = 1

Total:

5

Window 3

No events exist between:

10:02:00

and:

10:03:00

Therefore this is an empty window.

Window 4

Events:

10:03:05 api-1
10:03:40 worker-2

Source counts:

api-1    = 1
worker-2 = 1

Total:

2

Burst Rule

The function receives a configurable burst threshold.

For this example:

burstThreshold = 4

A source is considered burst-active inside a window when:

sourceEventCount >= burstThreshold

Therefore:

api-2

is burst-active in Window 2 because it has:

4 events

Suggested Models

type SourceActivity struct {
    Source string
    Count  int
    Burst  bool
}

type EventWindow struct {
    Start          time.Time
    End            time.Time
    TotalEvents    int
    SourceActivity []SourceActivity
    Empty          bool
}

type EventAnalysis struct {
    Windows []EventWindow
}

Expected Window Summary

Conceptually:

Window 10:00
Total = 3
api-1 = 2
worker-1 = 1
Burst = none

Window 10:01
Total = 5
api-2 = 4
worker-1 = 1
Burst = api-2

Window 10:02
Total = 0
Empty = true

Window 10:03
Total = 2
api-1 = 1
worker-2 = 1
Burst = none

Missing Windows

The implementation must create windows even when no events exist inside them.

This is important because otherwise the missing interval:

10:02

would disappear from the report.

Input Ordering

Events may be provided out of chronological order.

The implementation must either:

  • sort them before analysis
  • or otherwise process them correctly

The final report must always use chronological window order.

Sources

The function should derive source IDs from the event stream unless a predefined source registry is provided.

Missing Source Activity

An optional extension is to provide a known source list.

For example:

knownSources := []string{
    "api-1",
    "api-2",
    "worker-1",
    "worker-2",
}

The report can then identify sources that produced no events inside a given window.

For Window 1:

missing:
api-2
worker-2

For Window 3:

missing:
api-1
api-2
worker-1
worker-2

Timestamp Validation

All timestamps must be valid.

If the analysis is configured for UTC, timestamps using another zone should either:

  • be converted to UTC
  • or rejected according to the chosen API contract

The behavior must be documented.

Burst Threshold Validation

The burst threshold must satisfy:

burstThreshold > 0

Window Duration Validation

Window duration must satisfy:

windowDuration > 0

Empty Input

For an empty event collection, return an empty analysis unless an explicit analysis time range is provided.

If the caller supplies:

start time
end time

then empty windows may still be generated for that requested period.

Requirements

The function must:

  1. validate event timestamps
  2. process events in chronological order
  3. divide time into fixed windows
  4. preserve empty windows
  5. count events per source
  6. calculate total window activity
  7. detect source bursts
  8. return windows in chronological order

Additional Analysis

An advanced implementation may also calculate:

  • event count per type
  • sum of event values
  • average value per window
  • most active source
  • longest empty interval
  • consecutive burst windows

These are optional extensions.

Implementation Notes

This task represents a simplified time-window aggregation problem.

The general processing flow is:

Normalize timestamps
       ↓
Sort events
       ↓
Determine analysis range
       ↓
Create windows
       ↓
Assign events to windows
       ↓
Aggregate source activity
       ↓
Detect bursts
       ↓
Return report

The same algorithm should work for different:

window durations
event counts
source counts
burst thresholds

Group 7 — Optimization and Planning

This group focuses on optimization problems where several valid solutions may exist, but the goal is to find the best one according to a defined objective.

Earlier algorithm groups mainly focus on:

  • searching
  • filtering
  • grouping
  • combinations
  • graph traversal
  • validation
  • state transitions
  • scheduling simulation

This group introduces a different question:

Out of all valid solutions, which one is best?

The tasks cover several common optimization patterns:

  • selecting non-overlapping intervals
  • selecting requests under capacity constraints
  • assigning jobs to workers
  • planning routes under capacity limits
  • scheduling jobs with deadlines and penalties

The main challenge is not only producing a valid result.

The implementation must also evaluate competing solutions and select the one that optimizes the required metric.

Task 1 — Weighted Interval Scheduling

Given a list of jobs with:

start time
end time
value

select a set of non-overlapping jobs whose total value is maximal.

The task introduces:

  • interval conflicts
  • sorting by time
  • compatibility relationships
  • dynamic programming
  • reconstruction of the selected solution

Task 2 — Multi-Resource Capacity Selection

Given a set of requests that consume several limited resources such as:

CPU
memory
storage

select the combination of requests that produces the maximum total value without exceeding capacity.

The task introduces:

  • multidimensional capacity constraints
  • subset optimization
  • exact versus heuristic reasoning
  • deterministic tie-breaking

Task 3 — Minimum Cost Assignment

Given a set of workers and jobs where each worker has a different cost for every job, assign jobs to workers so that:

each job is assigned exactly once
each worker receives at most one job

and total assignment cost is minimal.

The task introduces:

  • assignment optimization
  • cost matrices
  • one-to-one constraints
  • solution reconstruction

Task 4 — Capacity-Constrained Route Planning

Given delivery locations with demands and vehicles with limited capacity, create valid delivery routes.

The objective is to minimize:

total route cost

while satisfying all capacity constraints.

The task introduces:

  • route planning
  • capacity constraints
  • multiple routes
  • graph costs
  • optimization under combinatorial growth

Task 5 — Deadline and Penalty Scheduling

Given jobs with:

duration
deadline
late penalty

schedule the jobs on a limited number of workers or machines.

The goal is to minimize total penalty caused by late jobs.

The task introduces:

  • sequencing
  • finite resources
  • completion times
  • deadlines
  • penalty-based optimization
  • deterministic scheduling

General Requirements

Solutions should validate input before processing.

Common invalid cases include:

negative duration
invalid interval
negative cost
negative capacity
request exceeding all available capacity
duplicate IDs
unknown references
invalid worker count
invalid route data

When multiple solutions have the same optimal objective value, the implementation must apply deterministic tie-breaking.

Unless a task defines a different rule, use:

lexicographical order of IDs

when equivalent solutions exist.

Algorithm Choice

The tasks are designed so that several approaches may be possible.

Depending on the input size, an implementation may use:

dynamic programming
backtracking
branch and bound
memoization
graph algorithms
exhaustive search for small inputs

The exercise should not use a library function that directly solves the complete optimization problem.

The objective is to implement the decision logic.

Result Reconstruction

For optimization tasks, returning only the optimal numeric value is not enough.

For example:

Maximum Value = 124

does not explain which jobs produced the result.

Solutions should return both:

objective value
selected solution

Examples:

selected job IDs
selected request IDs
worker-to-job assignments
vehicle routes
scheduled jobs

Goal

The goal of this group is to practice problems where correctness has two levels:

Is the solution valid?

Is the solution optimal?

A valid but non-optimal solution should not be considered correct when the task explicitly requires the best result.

Task 1 — Weighted Interval Scheduling

Objective

Given a collection of jobs, select a subset of non-overlapping jobs whose total value is maximal.

Each job has:

ID
Start
End
Value

Two jobs are compatible when their time intervals do not overlap.

Job Model

A possible model is:

type Job struct {
    ID    string
    Start int
    End   int
    Value int
}

The interval uses:

[start, end)

semantics.

This means:

Job A: [2, 5)
Job B: [5, 8)

do not overlap.

Input

Use the following jobs:

J01: Start=1,  End=4,  Value=20
J02: Start=3,  End=5,  Value=25
J03: Start=0,  End=6,  Value=40
J04: Start=4,  End=7,  Value=30
J05: Start=3,  End=9,  Value=45
J06: Start=5,  End=9,  Value=50
J07: Start=6,  End=10, Value=35
J08: Start=8,  End=11, Value=40
J09: Start=8,  End=12, Value=60
J10: Start=11, End=14, Value=30
J11: Start=12, End=15, Value=50
J12: Start=13, End=16, Value=55

Required Function

Create a function conceptually equivalent to:

func FindBestSchedule(jobs []Job) ScheduleResult

A possible result model is:

type ScheduleResult struct {
    Jobs       []Job
    TotalValue int
}

Rules

The selected jobs must satisfy:

no selected jobs overlap

and:

sum of selected job values is maximal

The result should return the selected jobs ordered by start time.

Example Reasoning

Consider:

J01: [1,4) Value=20
J04: [4,7) Value=30
J09: [8,12) Value=60
J12: [13,16) Value=55

These jobs do not overlap.

Their total value is:

20 + 30 + 60 + 55 = 165

However, the implementation must evaluate all relevant alternatives and determine whether a better valid schedule exists.

Do not assume that this example is optimal.

Compatibility

For every job, determine which earlier job is the latest compatible job.

Conceptually:

PreviousCompatible[i]

contains the index of the latest job whose:

End <= Current.Start

This relationship may be useful for dynamic programming.

Dynamic Programming Interpretation

For a job i, the optimal solution may either:

exclude job i

or:

include job i

If included, its value is combined with the best compatible solution before it.

Conceptually:

best[i] =
    max(
        best[i-1],
        jobs[i].Value + best[previousCompatible[i]]
    )

The exact implementation is your choice.

Tie-Breaking

If several schedules have the same maximum total value:

  1. prefer the schedule with fewer jobs
  2. if still equal, compare selected job IDs lexicographically

Example:

[J01, J05]

is preferred over:

[J02, J04, J06]

when both have the same total value and the first uses fewer jobs.

Validation

Reject or report invalid jobs where:

ID is empty
Start < 0
End <= Start
Value < 0
duplicate ID exists

Edge Cases

Test:

empty input
single job
all jobs overlap
no jobs overlap
zero-value jobs
multiple optimal solutions
jobs touching at interval boundaries

Additional Test Case

Input:

A: [1,3) Value=10
B: [3,5) Value=15
C: [1,5) Value=24
D: [5,7) Value=8

Possible schedules include:

A + B + D = 33
C + D     = 32

Expected result:

A
B
D

with:

TotalValue = 33

Restrictions

Do not use a third-party library that directly solves weighted interval scheduling.

Sorting helpers are allowed.

Goal

This task demonstrates that choosing the individually highest-value job does not necessarily produce the optimal global schedule.

The implementation should separate:

interval compatibility
optimization
solution reconstruction

Task 2 — Multi-Resource Capacity Selection

Objective

Select the best combination of requests that can fit inside a machine with limited resources.

Each request consumes:

CPU
Memory
Storage

and provides a numeric value.

The selected combination must not exceed any capacity.

The goal is to maximize:

TotalValue

Resource Model

A possible model is:

type Resources struct {
    CPU     int
    Memory  int
    Storage int
}

Request Model

type ResourceRequest struct {
    ID        string
    Resources Resources
    Value     int
}

Capacity

Use:

CPU Capacity     = 16
Memory Capacity  = 32
Storage Capacity = 500

Input Requests

R01: CPU=4,  Memory=8,  Storage=100, Value=35
R02: CPU=6,  Memory=12, Storage=150, Value=55
R03: CPU=2,  Memory=4,  Storage=80,  Value=20
R04: CPU=8,  Memory=16, Storage=220, Value=70
R05: CPU=4,  Memory=6,  Storage=120, Value=40
R06: CPU=3,  Memory=10, Storage=90,  Value=30
R07: CPU=5,  Memory=8,  Storage=160, Value=48
R08: CPU=1,  Memory=2,  Storage=40,  Value=12
R09: CPU=7,  Memory=14, Storage=200, Value=65
R10: CPU=2,  Memory=6,  Storage=60,  Value=25

Required Function

Create a function conceptually equivalent to:

func SelectBestRequests(
    requests []ResourceRequest,
    capacity Resources,
) SelectionResult

Possible result:

type SelectionResult struct {
    Selected      []ResourceRequest
    UsedResources Resources
    TotalValue    int
}

Capacity Rules

For the selected requests:

sum CPU     <= CPU Capacity
sum Memory  <= Memory Capacity
sum Storage <= Storage Capacity

Every constraint must be satisfied simultaneously.

A solution that satisfies CPU but exceeds memory is invalid.

Example

Consider:

R01 + R02 + R05

Resources:

CPU:
4 + 6 + 4 = 14

Memory:
8 + 12 + 6 = 26

Storage:
100 + 150 + 120 = 370

Value:
35 + 55 + 40 = 130

This combination is valid.

The implementation must determine whether another valid combination produces a higher total value.

Important Property

The highest-value requests cannot simply be selected greedily.

For example, one large request may consume enough resources to prevent several smaller requests whose combined value is better.

The solution must evaluate resource trade-offs.

Deterministic Tie-Breaking

If several selections have the same maximum value:

  1. prefer lower total CPU usage
  2. then lower total memory usage
  3. then lower total storage usage
  4. then lexicographically smaller ordered request IDs

Validation

Reject invalid input where:

request ID is empty
duplicate request ID exists
resource value < 0
request Value < 0
capacity < 0

A request may individually exceed capacity.

Such a request is valid input but can never be selected.

Example:

CPU=32
Memory=4
Storage=20

when CPU capacity is 16.

Empty Selection

The empty selection is valid and has:

TotalValue = 0

This is important if all requests have zero value or none fit.

Additional Test Case

Capacity:

CPU=10
Memory=16
Storage=200

Requests:

A: CPU=6, Memory=8, Storage=100, Value=50
B: CPU=4, Memory=8, Storage=100, Value=45
C: CPU=10, Memory=16, Storage=200, Value=90

Possible results:

A + B = 95
C     = 90

Expected selection:

A
B

with:

TotalValue = 95

Optional Extension — Required Requests

Add:

Required bool

to a request.

All required requests must be selected.

If the required set itself exceeds capacity, return an invalid result.

Optional Extension — Priority

Add:

Priority int

and change optimization order to:

maximize total priority
then maximize total value

Goal

This task models a multidimensional capacity-selection problem.

The key challenge is that each request consumes several resources at the same time.

The implementation should distinguish:

resource feasibility

from:

solution quality

Task 3 — Minimum Cost Assignment

Objective

Assign jobs to workers while minimizing the total assignment cost.

Each worker has a different cost for each job.

Each job must be assigned exactly once.

Each worker may receive at most one job.

Workers

Use:

W01
W02
W03
W04
W05

Jobs

Use:

J01
J02
J03
J04
J05

Cost Matrix

The cost of assigning each worker to each job is:

        J01  J02  J03  J04  J05

W01      9    2    7    8    6
W02      6    4    3    7    5
W03      5    8    1    8    4
W04      7    6    9    4    2
W05      8    5    6    3    7

Model

One possible representation is:

type AssignmentCost struct {
    WorkerID string
    JobID    string
    Cost     int
}

or:

type CostMatrix struct {
    Workers []string
    Jobs    []string
    Costs   [][]int
}

Result

A possible result model is:

type Assignment struct {
    WorkerID string
    JobID    string
    Cost     int
}

type AssignmentResult struct {
    Assignments []Assignment
    TotalCost   int
}

Required Function

Create a function conceptually equivalent to:

func FindMinimumCostAssignment(
    workers []string,
    jobs []string,
    costs [][]int,
) AssignmentResult

Rules

Every job must be assigned exactly once.

Every worker may be used at most once.

With equal worker and job counts:

every worker is used exactly once

Optimization Goal

Minimize:

sum of all assignment costs

Example Assignment

For example:

W01 -> J02 = 2
W02 -> J01 = 6
W03 -> J03 = 1
W04 -> J05 = 2
W05 -> J04 = 3

Total:

2 + 6 + 1 + 2 + 3 = 14

The implementation must determine whether this is optimal.

Do not assume that the example is the minimum.

Why Greedy Selection Fails

A simple rule such as:

for each worker, choose its cheapest available job

does not always produce the globally optimal assignment.

A locally cheap decision can force another worker into a very expensive assignment.

The complete assignment must be optimized as one problem.

Unequal Counts

The function should also support:

workers >= jobs

Unused workers are allowed.

If:

jobs > workers

the assignment is impossible.

Return an invalid result or explicit error.

Unsupported Assignment

Optionally support forbidden worker-job combinations using:

Allowed bool

or a special representation.

For example:

W02 cannot perform J04

Forbidden assignments must never appear in the result.

Tie-Breaking

If several assignments have the same minimum cost:

  1. order assignments by JobID
  2. compare WorkerID sequence lexicographically

Example:

J01 -> W01
J02 -> W03

is preferred over:

J01 -> W02
J02 -> W01

if both have the same total cost and the first sequence is lexicographically smaller.

Validation

Reject:

duplicate worker IDs
duplicate job IDs
negative cost
missing matrix rows
rows with incorrect length
empty worker ID
empty job ID
jobs > workers

Additional Test Case

Workers:

A
B
C

Jobs:

X
Y
Z

Costs:

      X   Y   Z

A     10   2   8
B      9   7   5
C      6   4   3

One possible assignment:

A -> Y = 2
B -> X = 9
C -> Z = 3

Total:

14

The implementation must verify whether a cheaper assignment exists.

Optional Extension — Skills

Add required job skills and worker skills.

An assignment is allowed only when:

worker satisfies all required job skills

Optimization is then performed only over valid combinations.

Optional Extension — Worker Capacity

Allow workers to receive more than one job.

Each job has:

Duration

and each worker has:

AvailableHours

The problem then becomes a more general capacity-constrained assignment problem.

Goal

This task introduces optimization over one-to-one relationships.

The implementation must reason about the complete assignment rather than evaluating each worker or job independently.

Task 4 — Capacity-Constrained Route Planning

Objective

Create delivery routes for vehicles with limited capacity.

Each delivery location has:

ID
Demand

Vehicles start and finish at a common depot.

Every delivery location must be visited exactly once.

A vehicle must never carry more demand than its capacity.

The objective is to minimize:

TotalRouteCost

Locations

Use:

DEPOT
L01
L02
L03
L04
L05
L06

Demand

L01 = 4
L02 = 6
L03 = 3
L04 = 7
L05 = 5
L06 = 2

Total demand:

27

Vehicles

Use three identical vehicles:

V01 Capacity=10
V02 Capacity=10
V03 Capacity=10

Total available capacity:

30

Route Cost Matrix

Use the following symmetric costs:

       DEPOT  L01  L02  L03  L04  L05  L06

DEPOT     0     4    6    3    8    7    5
L01       4     0    5    3    7    6    4
L02       6     5    0    4    3    5    6
L03       3     3    4    0    6    4    2
L04       8     7    3    6    0    4    7
L05       7     6    5    4    4    0    3
L06       5     4    6    2    7    3    0

Models

type DeliveryLocation struct {
    ID     string
    Demand int
}

type Vehicle struct {
    ID       string
    Capacity int
}

A route may be represented as:

type Route struct {
    VehicleID  string
    Locations  []string
    TotalDemand int
    Cost       int
}

Overall result:

type RoutePlan struct {
    Routes    []Route
    TotalCost int
    Valid     bool
}

Route Rules

Every used route must:

start at DEPOT
visit one or more delivery locations
return to DEPOT

For example:

DEPOT -> L01 -> L03 -> L06 -> DEPOT

Capacity

For every vehicle:

sum of demands on route <= vehicle capacity

Example:

L01 = 4
L03 = 3
L06 = 2

Total = 9

which fits capacity 10.

Complete Coverage

Every location:

L01 through L06

must appear exactly once across all routes.

The solution is invalid if a location is:

missing
visited twice

Route Cost

Route cost is the sum of every traversed edge.

Example:

DEPOT -> L03 -> L06 -> DEPOT

Cost:

DEPOT -> L03 = 3
L03 -> L06    = 2
L06 -> DEPOT  = 5

Total = 10

Optimization Goal

Find a valid plan with minimum:

TotalCost

Number of Vehicles

The implementation does not need to use every available vehicle.

Example:

three vehicles exist

but if two vehicles can legally serve all locations and produce a lower total cost, using two is allowed.

However, capacity still applies.

In the provided base dataset:

Total Demand = 27
Vehicle Capacity = 10

therefore at least three vehicles are required.

Deterministic Tie-Breaking

If several plans have the same minimum cost:

  1. prefer fewer used vehicles
  2. sort routes by VehicleID
  3. compare route location sequences lexicographically

Validation

Reject invalid input where:

duplicate location ID
duplicate vehicle ID
negative demand
vehicle capacity <= 0
unknown matrix location
negative route cost
non-zero diagonal cost if your representation requires zero
missing cost between required locations

Impossible Case

The plan is impossible if any single location has demand larger than every vehicle capacity.

Example:

L07 Demand=14

maximum vehicle capacity = 10

No route can legally contain L07.

Additional Small Test Case

Locations:

A Demand=4
B Demand=4
C Demand=2

Vehicle capacity:

6

Vehicles:

V1
V2

A valid split is:

V1: A + C = 6
V2: B     = 4

while:

A + B = 8

is invalid.

Important Note

This task intentionally uses a small dataset.

Route planning grows combinatorially.

The goal is to implement exact optimization for manageable inputs rather than build a production vehicle-routing engine.

Optional Extension — Vehicle-Specific Cost

Different vehicles may have different travel costs.

For example:

large truck fuel multiplier = 1.25
small van fuel multiplier   = 1.00

Route cost then depends on both:

path
vehicle

Optional Extension — Time Windows

Each location may define:

EarliestArrival
LatestArrival
ServiceDuration

A route becomes valid only if every delivery occurs within its allowed time window.

Goal

This task combines:

partitioning
capacity constraints
graph cost
route ordering
global optimization

The implementation must optimize both:

which locations belong together

and:

in which order they are visited

Task 5 — Deadline and Penalty Scheduling

Objective

Schedule a set of jobs on a limited number of workers.

Each job has:

Duration
Deadline
LatePenalty

A job produces its penalty if it completes after its deadline.

The objective is to minimize:

TotalPenalty

Job Model

type ScheduledJobInput struct {
    ID          string
    Duration    int
    Deadline    int
    LatePenalty int
}

Workers

Use:

2 workers

Workers are identical.

They are identified as:

W01
W02

Input

J01: Duration=4, Deadline=4,  Penalty=30
J02: Duration=3, Deadline=7,  Penalty=20
J03: Duration=6, Deadline=8,  Penalty=50
J04: Duration=2, Deadline=6,  Penalty=15
J05: Duration=5, Deadline=10, Penalty=40
J06: Duration=3, Deadline=12, Penalty=25
J07: Duration=7, Deadline=9,  Penalty=60
J08: Duration=2, Deadline=5,  Penalty=18

Scheduling Rules

All workers start at:

time = 0

A worker may execute only one job at a time.

Jobs are non-preemptive.

Once a job starts, it runs continuously until completion.

Completion Time

For a job:

FinishTime = StartTime + Duration

A job is on time when:

FinishTime <= Deadline

A job is late when:

FinishTime > Deadline

Penalty

A late job contributes its entire:

LatePenalty

The amount of lateness does not change the penalty in the base task.

Example:

Deadline = 10
Finish   = 11
Penalty  = 40

and:

Deadline = 10
Finish   = 20
Penalty  = 40

both contribute:

40

Result Model

A possible model is:

type ScheduledJob struct {
    JobID      string
    WorkerID   string
    StartTime  int
    FinishTime int
    Deadline   int
    Late       bool
    Penalty    int
}

type PenaltyScheduleResult struct {
    Jobs         []ScheduledJob
    TotalPenalty int
    TotalDuration int
}

Optimization Goal

Find a valid schedule with minimum:

TotalPenalty

The implementation must decide:

which worker executes each job

and:

in which order

Example

Suppose:

W01:
J01 -> J03

W02:
J04 -> J02

The implementation must calculate every completion time and determine which jobs miss their deadlines.

This is only an example of schedule structure.

It is not necessarily optimal.

Deterministic Tie-Breaking

If several schedules have the same minimum penalty:

  1. prefer smaller overall completion time
  2. then fewer late jobs
  3. then compare jobs ordered by start time, worker ID, and JobID lexicographically

Total Duration

Define:

TotalDuration

as the time when the last worker finishes its last job.

Equivalent terminology:

makespan

Example:

W01 finishes at 14
W02 finishes at 11

TotalDuration = 14

Validation

Reject:

duplicate job ID
Duration <= 0
Deadline < 0
LatePenalty < 0
worker count <= 0

All Jobs Must Run

Every job must be scheduled exactly once.

The implementation may not drop a job merely to avoid its penalty.

Additional Test Case

One worker:

A: Duration=4, Deadline=4, Penalty=100
B: Duration=2, Deadline=2, Penalty=20

Schedule:

A -> B

results:

A finishes 4  -> on time
B finishes 6  -> late

TotalPenalty = 20

Schedule:

B -> A

results:

B finishes 2  -> on time
A finishes 6  -> late

TotalPenalty = 100

Expected optimal schedule:

A -> B

with:

TotalPenalty = 20

This demonstrates that:

earliest deadline first

does not necessarily minimize weighted penalties.

Zero-Penalty Job

A job may have:

LatePenalty = 0

It still must be scheduled.

It simply contributes no penalty if late.

Optional Extension — Penalty Per Time Unit

Instead of a fixed penalty, define:

PenaltyPerLateUnit

Then:

Lateness = max(0, FinishTime - Deadline)

Penalty =
    Lateness * PenaltyPerLateUnit

This changes the optimization objective significantly.

Optional Extension — Different Worker Speeds

A worker may execute different jobs at different speeds.

For example:

W01 executes J03 in 4 units
W02 executes J03 in 7 units

Duration becomes worker-dependent.

Optional Extension — Job Dependencies

Add:

Dependencies []string

A job may start only after all dependencies complete.

This combines the task with dependency-aware scheduling from Group 8.

Goal

This task demonstrates scheduling where the objective is not simply:

finish as early as possible

Instead, the scheduler must decide which deadlines are most valuable to protect.

The implementation must reason about:

job order
worker assignment
completion times
deadlines
penalties

as one optimization problem.

Algorithm Group 8

Algorithm Group 8 contains five exercises focused on multi-dimensional data analysis, range correction, dependency compatibility, generic combination generation, and condition-based Cartesian products.

Compared with earlier groups, these tasks introduce broader algorithmic problems where the implementation must often model relationships between multiple collections rather than process one collection independently.

Tasks

Task 1 — Unique and Common Values

Analyze multiple integer collections and identify:

  • values that occur only once across the complete multi-dimensional collection
  • values that are present in every input dimension

Task 2 — Sector Range Reconstruction

Process several integer collections assigned to numeric sectors.

Detect values placed in the wrong sector, move them to the correct sector, and determine which values are missing from each sector.

Task 3 — System Version Compatibility

Model three dependent systems and determine valid update, upgrade, and installation paths according to version-generation compatibility and dependency rules.

Task 4 — Generic Combination Generator

Create one generic function that produces the Cartesian product of an arbitrary number of input collections.

The function must work regardless of:

  • the number of input lists
  • the number of values inside each list

Task 5 — Conditional Combination Generator

Extend generic combination generation by:

  • selecting specific source lists
  • calculating the sum of each generated combination
  • filtering combinations using a target value
  • applying configurable delta rules

Objectives

The exercises in this group provide practice with:

  • multi-dimensional collections
  • global frequency analysis
  • collection intersection
  • numeric range validation
  • data correction
  • missing-value detection
  • dependency graphs
  • compatibility rules
  • version relationships
  • request and response modeling
  • Cartesian products
  • recursion or iterative combination generation
  • generic algorithms
  • configurable filtering
  • delta parsing
  • validation

General Design Considerations

Several exercises in this group benefit from separating the problem into stages.

For example:

Input
  ↓
Validation
  ↓
Normalization
  ↓
Processing
  ↓
Result Modeling

Trying to perform every operation inside one large loop may make the implementation harder to verify and reuse.

Generic Algorithms

Tasks 4 and 5 explicitly require a single abstract solution.

The implementation must not depend on a fixed number of source lists.

For example, the same generator should support:

2 lists
3 lists
5 lists
7 lists

without requiring a separate implementation for each case.

Implementation

The examples define the intended behavior for the provided inputs.

Where the original task contains an ambiguity or an inconsistent example, the individual task page documents the issue and defines a deterministic interpretation without changing the main objective of the exercise.

Task 1 — Unique and Common Values

Objective

Create a function that analyzes a multi-dimensional integer collection.

The function must identify two different categories of values:

  1. values that occur only once across the complete input
  2. values that are present in every input dimension

Input

The input consists of four integer slices.

List 1

list1 := []int{
    7, 9, 40, 85, 18, 8, 99, 31, 14,
    105, 48, 22, 10, 38, 12, 60, 41, 21,
    115, 15, 6, 33, 20, 17, 13, 35, 75,
}

List 2

list2 := []int{
    70, 100, 31, 60, 90, 41, 55, 33,
    115, 99, 50, 75, 40, 65, 38, 59,
    35, 45, 58,
}

List 3

list3 := []int{
    15, 45, 60, 18, 99, 55, 72, 50,
    38, 7, 65, 119, 70, 95, 6, 115,
    48, 58, 75, 10,
}

List 4

list4 := []int{
    85, 90, 105, 95, 38, 115,
    60, 100, 99, 125, 75, 119,
}

Represent the complete input as:

data := [][]int{
    list1,
    list2,
    list3,
    list4,
}

Result Model

A structured result can be used:

type AnalysisResult struct {
    UniqueValues []int
    CommonValues []int
}

The exact public type may be chosen by the developer, but both result categories must be available independently.

Unique Values

A unique value is a value that occurs exactly once across the entire multi-dimensional collection.

Conceptually:

globalOccurrenceCount(value) == 1

The count is calculated across all input dimensions.

For the provided data, the globally unique values are:

[]int{
    8,
    9,
    12,
    13,
    14,
    17,
    20,
    21,
    22,
    59,
    72,
    125,
}

Common Values

A common value is a value that appears in every input dimension.

For four lists, the condition is:

exists in list1
AND
exists in list2
AND
exists in list3
AND
exists in list4

For the provided input, the common values are:

[]int{
    38,
    60,
    75,
    99,
    115,
}

Expected Result

Conceptually:

AnalysisResult{
    UniqueValues: []int{
        8,
        9,
        12,
        13,
        14,
        17,
        20,
        21,
        22,
        59,
        72,
        125,
    },
    CommonValues: []int{
        38,
        60,
        75,
        99,
        115,
    },
}

Duplicate Values Inside One Dimension

When determining globally unique values, every occurrence matters.

For example:

list1 = {5, 5}
list2 = {}

means that 5 occurs twice globally and is therefore not unique.

When determining common values, the number of occurrences inside one dimension is irrelevant.

The value only needs to exist at least once in every dimension.

Requirements

The function must:

  1. accept a multi-dimensional integer collection
  2. count occurrences across all dimensions
  3. identify values whose global count is exactly one
  4. determine which values exist in every dimension
  5. return both result categories

Result Ordering

The original task does not define an ordering requirement.

For deterministic output, this specification uses ascending numeric order.

Therefore both:

UniqueValues

and:

CommonValues

should be sorted in ascending order.

Empty Input

If the outer collection is empty:

[][]int{}

both result collections should be empty.

If one of several dimensions is empty, no value can be common to every dimension.

Implementation Notes

The two result categories represent different operations:

UniqueValues
    -> global frequency analysis

CommonValues
    -> intersection of all dimensions

They may be calculated independently.

Avoid repeatedly scanning the complete data for every individual value when a frequency or membership structure can provide the same result more efficiently.

Task 2 — Sector Range Reconstruction

Objective

Create a function that analyzes three integer collections assigned to predefined numeric sectors.

The function must:

  1. detect values placed in the wrong sector
  2. report those values
  3. move them to the correct sector
  4. determine which values are missing from every sector
  5. produce complete sorted sector information

Sector Definitions

There are three sectors.

Sector 1

Valid values:

1 through 50

inclusive.

Sector 2

Valid values:

51 through 100

inclusive.

Sector 3

Valid values:

101 through 150

inclusive.

A complete sector would therefore contain every integer inside its assigned range exactly as required by the task.

Input

Sector 1 Input

list1 := []int{
    7, 9, 31, 40, 18, 8, 14, 61,
    48, 104, 22, 44, 10, 38, 50,
    12, 41, 21, 15, 110, 6, 33,
    74, 20, 17, 13, 35, 39, 19,
    14, 29,
}

Sector 2 Input

list2 := []int{
    70, 100, 60, 90, 4, 55, 99,
    50, 75, 132, 65, 59, 81, 62,
    72, 92, 51, 66, 16, 58, 142,
    94, 77, 63, 88, 68, 83, 96,
}

Sector 3 Input

list3 := []int{
    135, 102, 131, 108, 141, 114,
    101, 28, 139, 144, 128, 119,
    87, 133, 122, 107, 147, 105,
    150, 124, 109, 121, 34, 136,
    103, 123, 95, 143, 106, 127,
    117, 125, 112, 120, 130,
}

Misplaced Values

A misplaced value is valid globally but stored in the wrong sector.

For the provided input, the misplaced values are:

61   : Sector 1 -> Sector 2
104  : Sector 1 -> Sector 3
110  : Sector 1 -> Sector 3
74   : Sector 1 -> Sector 2

4    : Sector 2 -> Sector 1
50   : Sector 2 -> Sector 1
132  : Sector 2 -> Sector 3
16   : Sector 2 -> Sector 1
142  : Sector 2 -> Sector 3

28   : Sector 3 -> Sector 1
87   : Sector 3 -> Sector 2
34   : Sector 3 -> Sector 1
95   : Sector 3 -> Sector 2

There are therefore:

13

misplaced values.

Correction to the Original Example

The original task lists the following misplaced values:

4, 16, 28, 34, 61, 74, 87, 95, 104, 110, 132, 142

However, the source data also contains:

50

inside Sector 2.

Since Sector 2 is defined as:

51 through 100

the value 50 belongs to Sector 1.

It is therefore also a misplaced value and must be corrected.

Suggested Result Model

A structured result can be used:

type MisplacedValue struct {
    Value      int
    FromSector int
    ToSector   int
}

type SectorResult struct {
    SectorID int
    Values   []int
    Missing  []int
}

type ReconstructionResult struct {
    Misplaced []MisplacedValue
    Sectors   []SectorResult
}

The exact naming may be changed, but the report must preserve equivalent information.

Missing Values

After all misplaced values have been moved to their correct sectors, determine which values are absent.

Missing Values — Sector 1

[]int{
    1, 2, 3, 5, 11,
    23, 24, 25, 26, 27,
    30, 32, 36, 37,
    42, 43, 45, 46, 47, 49,
}

Missing Values — Sector 2

[]int{
    52, 53, 54, 56, 57,
    64, 67, 69, 71, 73,
    76, 78, 79, 80,
    82, 84, 85, 86,
    89, 91, 93, 97, 98,
}

Missing Values — Sector 3

[]int{
    111, 113, 115, 116, 118,
    126, 129, 134, 137, 138,
    140, 145, 146, 148, 149,
}

Duplicate Values

The input may contain duplicate values.

For example, Sector 1 contains:

14

more than once.

A complete sector represents numeric membership rather than occurrence count.

Therefore duplicates should not cause a value to appear multiple times in the reconstructed sorted sector.

Requirements

The function must:

  1. know the valid range of every sector
  2. inspect every input value
  3. determine the sector where the value currently exists
  4. determine the sector where it belongs
  5. report every misplaced value
  6. move misplaced values to their correct sectors
  7. normalize duplicate membership where appropriate
  8. sort each corrected sector
  9. determine all missing values
  10. return a structured report

Values Outside All Sectors

A value outside:

1 through 150

does not belong to any defined sector.

Such a value should be reported as invalid rather than silently inserted into a sector.

Implementation Notes

A useful approach is to separate:

classification

from:

missing-value detection

First normalize every value into its correct sector.

Then compare each normalized sector with its complete expected range.

This prevents misplaced values from being incorrectly reported as missing in one sector while still being stored in another.

Task 3 — System Version Compatibility

Objective

Design a compatibility resolver for three dependent software systems.

The systems are:

S1
S2
S3

Their dependency chain is:

S1
 ↓
S2
 ↓
S3

In other words:

S2 depends on S1
S3 depends on S2

The resolver must determine valid installation, update, and upgrade paths while preserving compatibility and dependency requirements.

Published Version Data

The available production versions are represented in three columns:

S1      | S2       | S3
--------------------------------
1.0.0   | 1.0.0    | 1.0.0
1.1.0   | 1.1.0    | -----
1.1.1   | -----    | 1.1.0
-----   | 1.2.0    | 1.1.1
1.2.0   | 1.3.0    | -----
1.3.0   | -----    | 1.2.0
1.3.1   | 1.4.0    | 1.2.1
-----   | 1.4.1    | 1.2.2
1.4.0   | 1.5.0    | -----
-----   | -----    | 1.3.0
1.5.0   | 1.6.0    | 1.3.1
1.5.1   | 1.6.1    | -----
1.5.2   | -----    | 1.4.0
-----   | 1.7.0    | 1.4.1
1.6.0   | 1.7.1    | -----
1.6.1   | 1.7.2    | 1.5.0
-----   | -----    | -----
1.7.0   | 1.8.0    | 1.6.0
1.7.1   | 1.8.1    | 1.6.1
1.7.2   | -----    | 1.6.2
-----   | 1.9.0    | 1.6.3
1.8.0   | 1.9.1    | -----
1.8.1   | 1.9.2    | 1.7.0
-----   | 1.9.3    | 1.7.1
1.9.0   | -----    | 1.7.2
1.9.1   | 1.10.0   | 1.7.3

A missing version is represented by:

-----

Compatibility Rule

Versions are compatible when they belong to the corresponding update generation.

The original task provides the following explicit example.

The second generation of S1 contains:

1.2.0
1.3.0
1.3.1

The compatible second-generation S2 versions are:

1.2.0
1.3.0

The compatible second-generation S3 versions are:

1.1.0
1.1.1

Therefore compatibility must be modeled by generation rather than by requiring identical semantic-version numbers across systems.

Important Source Limitation

The original task explicitly describes the second generation as an example but does not enumerate the complete generation boundaries for every version in the table.

The implementation must therefore model those generation relationships explicitly as part of the exercise.

Do not assume that versions are compatible merely because:

major versions match

or because:

minor version numbers are numerically similar

Compatibility is defined by the generation mapping.

Dependencies

The dependency chain must never be broken.

Installing S2

S2 cannot be installed unless a compatible S1 is already installed or included in the requested operation.

Invalid example:

Installed:
S1 = none

Request:
Install S2

Result:

dependency conflict

Installing S3

S3 requires a compatible S2.

Since S2 itself depends on S1, a valid S3 installation ultimately requires the full compatible dependency chain.

Update and Upgrade

The resolver must distinguish between operations requested against the client’s current infrastructure.

A client may request:

  • installation
  • update
  • upgrade
  • latest compatible version
  • latest compatible dependency chain

The exact internal representation is left to the developer.

Suggested Models

A version can be represented as:

type SystemVersion struct {
    System     string
    Version    string
    Generation int
}

An installed infrastructure state can be represented as:

type Infrastructure struct {
    S1 string
    S2 string
    S3 string
}

A request can be modeled as:

type CompatibilityRequest struct {
    ClientID string
    Current  Infrastructure
    Action   string
    Target   Infrastructure
}

A response can be modeled as:

type CompatibilityResponse struct {
    Valid       bool
    Current     Infrastructure
    Recommended Infrastructure
    Conflicts   []string
    Actions     []string
}

These types are suggestions.

The original task explicitly requires creating a custom client-side request and server-side response, so the exact representation is part of the exercise.

Client Scenario 1 — Alex

Alex currently has:

S1 = 1.5.1

He wants to install:

the newest S2 version compatible with his S1

The resolver must determine the newest compatible S2 while preserving the dependency relationship.

Client Scenario 2 — Ben

Ben currently has:

S1 = 1.7.2

He wants to install:

the newest compatible S2
the newest compatible S3

The resolver must determine a compatible chain:

S1 -> S2 -> S3

Client Scenario 3 — Lana

Lana currently has:

S1 = 1.5.1
S2 = 1.7.0
S3 = 1.4.0

She wants to update her current:

S2
S3

The resolver must first validate that the current configuration is compatible and then determine valid updates.

Client Scenario 4 — David

David currently has:

S1 = 1.8.1
S2 = 1.9.3
S3 = 1.6.1

He wants to update:

S2
S3

The resolver must detect any existing compatibility conflict before recommending changes.

Client Scenario 5 — Naomi

Naomi currently has:

S1 = 1.8.0
S2 = 1.9.1
S3 = 1.6.1

She wants updates for all three systems.

The resolver must determine the newest valid compatible infrastructure according to the generation rules.

Client Scenario 6 — Emma

Emma currently has:

S1 = 1.6.0

She wants to:

  1. upgrade S1 to the newest version of the next S1 generation
  2. install the newest compatible S2
  3. install the newest compatible S3

The complete recommended chain must remain compatible.

Client Scenario 7 — Helen

Helen has none of the systems installed.

She wants:

the latest compatible versions of all three systems

The resolver must select a complete valid dependency chain.

Client Scenario 8 — Nolan

Nolan is a new user.

He specifically wants:

S3 = 1.6.3

The resolver must work backward through the dependency chain and determine:

latest compatible S2
latest compatible S1

for that requested S3 version.

Conflict Detection

The resolver must report a conflict when:

  • an installed combination is incompatible
  • a requested version is incompatible with an existing dependency
  • a dependency required by a requested system is missing
  • no compatible version exists
  • an operation would break the dependency chain

A conflict response should explain the reason rather than return only:

false

Request and Response Requirement

The original task explicitly requires:

Create custom client side request and server side response.

Therefore the solution should expose a clear request model and a structured result model.

Avoid returning unrelated values through loosely typed structures.

Validation

At minimum, validate:

  • system identifiers
  • version existence
  • requested action
  • dependency availability
  • generation compatibility
  • current infrastructure consistency

Implementation Notes

This task is primarily about modeling compatibility relationships.

A useful separation is:

version registry
      ↓
generation mapping
      ↓
dependency graph
      ↓
current-state validation
      ↓
request resolution
      ↓
recommended state

Do not hard-code separate logic for Alex, Ben, Lana, David, Naomi, Emma, Helen, and Nolan.

All scenarios should be resolved by the same generic compatibility engine.

Task 4 — Generic Combination Generator

Objective

Create a generic function that generates every possible combination by selecting exactly one value from each provided list.

The function must work with an arbitrary number of input lists and an arbitrary number of values inside each list.

This operation is the Cartesian product of the input collections.

Input

Represent the source lists as:

[][]int

For example:

lists := [][]int{
    {1, 2, 3},
    {3, 5, 7},
}

Result

For the provided example, the function must generate:

[][]int{
    {1, 3},
    {1, 5},
    {1, 7},
    {2, 3},
    {2, 5},
    {2, 7},
    {3, 3},
    {3, 5},
    {3, 7},
}

Combination Rule

Every result contains exactly one value from every input list.

For:

List 1 = {a, b}
List 2 = {c, d}

the result is:

{a, c}
{a, d}
{b, c}
{b, d}

Example 1

Input:

[][]int{
    {1, 2, 3},
    {3, 5, 7},
}

Expected result:

[][]int{
    {1, 3},
    {1, 5},
    {1, 7},
    {2, 3},
    {2, 5},
    {2, 7},
    {3, 3},
    {3, 5},
    {3, 7},
}

The number of combinations is:

3 * 3 = 9

Example 2

Input:

[][]int{
    {1, 2},
    {3, 5},
    {8, 9},
}

Expected result:

[][]int{
    {1, 3, 8},
    {1, 3, 9},
    {1, 5, 8},
    {1, 5, 9},
    {2, 3, 8},
    {2, 3, 9},
    {2, 5, 8},
    {2, 5, 9},
}

The number of combinations is:

2 * 2 * 2 = 8

Additional Input Shapes

The same function must also support structures such as:

Example 3

List 1: 1|2
List 2: 3|5|7
List 3: 4|6|8|9
List 4: 10|11
List 5: 14|15

and:

Example 4

List 1: 1|2|3|4
List 2: 5|6|7
List 3: 8|9
List 4: 10|11|12|13

as well as any other valid number of input lists.

Generic Requirement

The implementation must not contain fixed logic such as:

if two lists -> implementation A
if three lists -> implementation B
if four lists -> implementation C

One algorithm must solve all cases.

Conceptually:

func CreateCombinations(lists [][]int) [][]int

is sufficient as an API shape.

Number of Results

If the input contains lists with lengths:

L1, L2, L3, ..., Ln

the number of generated combinations is:

L1 * L2 * L3 * ... * Ln

For example:

2 * 3 * 4 * 2 * 2 = 96

possible combinations.

Empty Inner List

If any selected input list is empty:

[][]int{
    {1, 2},
    {},
    {8, 9},
}

there are no valid combinations.

The result should therefore be:

[][]int{}

Empty Outer List

An empty outer collection does not provide any values from which a combination can be created.

For this exercise, it should return:

[][]int{}

Result Ordering

For deterministic output, combinations should follow input order.

The first list changes slowest.

The last list changes fastest.

For example:

{1,3,8}
{1,3,9}
{1,5,8}
{1,5,9}
{2,3,8}
...

Requirements

The function must:

  1. accept any number of integer lists
  2. accept different list lengths
  3. select exactly one value from every list
  4. generate every possible combination
  5. avoid generating duplicate combinations caused by the algorithm itself
  6. preserve deterministic traversal order
  7. use one generic implementation

Duplicate Source Values

If an input list itself contains duplicate values, duplicate result values may naturally be generated.

For example:

[][]int{
    {1, 1},
    {2},
}

logically contains two source positions and therefore produces:

[][]int{
    {1, 2},
    {1, 2},
}

unless the implementation explicitly normalizes duplicate source values.

The original task does not require source deduplication.

Implementation Notes

This problem can be solved using:

  • recursion
  • backtracking
  • iterative Cartesian-product expansion

The important requirement is that the solution remains generic.

Conceptually:

choose one value from list 0
    ↓
choose one value from list 1
    ↓
...
    ↓
choose one value from final list
    ↓
emit combination

The implementation should not depend on the examples shown in the task.

Task 5 — Conditional Combination Generator

Objective

Create a generic combination generator that selects specified source lists, generates their Cartesian product, calculates the sum of every combination, and returns only combinations whose sums satisfy a configurable target condition.

The function must remain generic regardless of:

  • how many source lists exist
  • which source lists are selected
  • how many values each selected list contains

Source Data

List 1

list1 := []int{
    7, 9, 18, 8, 14, 10, 12,
    21, 15, 6, 20, 17, 13,
}

List 2

list2 := []int{
    31, 41, 33, 40, 38, 35,
}

List 3

list3 := []int{
    45, 60, 55, 50, 65,
    70, 48, 58, 75,
}

List 4

list4 := []int{
    85, 90, 105, 95, 115,
    100, 99, 125, 119,
}

List 5

list5 := []int{
    195, 205, 215, 275, 230,
    240, 220, 250, 290, 305,
}

Represent them as:

data := [][]int{
    list1,
    list2,
    list3,
    list4,
    list5,
}

Function

Create one generic function:

CreateCombinations(...)

Conceptually:

CreateCombinations(
    data,
    selectedLists,
    target,
    delta,
)

Argument 1 — Source Lists

The first argument is:

[][]int

containing all available source lists.

Argument 2 — Selected Lists

The second argument is:

[]int

containing indexes of the source lists that should participate in combination generation.

Indexes are zero-based.

For example:

[]int{0, 1, 3}

selects:

List 1
List 2
List 4

A generated combination will therefore contain exactly three values:

one from List 1
one from List 2
one from List 4

Argument 3 — Target

The third argument is an integer target.

The sum of every generated combination is compared against this target.

Argument 4 — Delta

The fourth argument is a string defining the allowed deviation from the target.

Supported forms are:

+N
-N
*N
0

Positive Delta

For:

target = T
delta  = +N

the allowed range is:

T <= sum <= T + N

Example:

target = 50
delta  = +10

means:

50 <= sum <= 60

Negative Delta

For:

target = T
delta  = -N

the allowed range is:

T - N <= sum <= T

For example:

target = 50
delta  = -5

means:

45 <= sum <= 50

Absolute Delta

For:

target = T
delta  = *N

the allowed range is:

T - N <= sum <= T + N

Example:

target = 100
delta  = *10

means:

90 <= sum <= 110

Zero Delta

For:

delta = 0

no range is used.

A combination is accepted only when:

sum == target

Example:

target = 120
delta  = 0

means:

sum == 120

Combination Generation

The generator must first select the requested source lists.

Then it must generate every possible combination containing one value from every selected list.

For example:

selectedLists := []int{
    0,
    1,
}

means that the combinations are generated from:

List 1 × List 2

For every generated combination:

  1. calculate its sum
  2. compare the sum with the resolved target range
  3. return the combination only when it satisfies the condition

Suggested Result Model

Instead of returning only the raw values, a structured result is useful:

type CombinationResult struct {
    Values []int
    Sum    int
}

The function can then return:

[]CombinationResult

This preserves both:

the generated values

and:

the sum used to accept the combination

Original Example 1

Call:

CreateCombinations(
    data,
    []int{0, 1},
    50,
    "-5",
)

Selected lists:

List 1
List 2

Allowed sum range:

45 <= sum <= 50

Original Example 2

Call:

CreateCombinations(
    data,
    []int{0, 1, 2},
    100,
    "*10",
)

Selected lists:

List 1
List 2
List 3

Allowed range:

90 <= sum <= 110

Original Example 3

Call:

CreateCombinations(
    data,
    []int{0, 1, 3},
    150,
    "+10",
)

Selected lists:

List 1
List 2
List 4

Allowed range:

150 <= sum <= 160

Original Example 4

Call:

CreateCombinations(
    data,
    []int{0, 1, 3, 4},
    350,
    "+30",
)

Selected lists:

List 1
List 2
List 4
List 5

Allowed range:

350 <= sum <= 380

Original Example 5

Call:

CreateCombinations(
    data,
    []int{0, 1, 3, 4},
    355,
    "0",
)

The combination sum must satisfy:

sum == 355

Selection Validation

Every index in:

selectedLists

must reference an existing source list.

For example, with five source lists, valid indexes are:

0
1
2
3
4

An index such as:

5

is invalid.

Duplicate Selected Indexes

A selected list should appear only once.

For example:

[]int{
    0,
    1,
    1,
}

is ambiguous because it requests the same source dimension twice.

A robust implementation should reject duplicate selected indexes.

Empty Selection

If:

selectedLists

is empty, no combination can be formed for this exercise.

Return an empty result or an explicit validation error.

Delta Validation

Valid delta forms are:

0
+N
-N
*N

where N is a positive integer.

Examples:

0
+10
-5
*20

Invalid examples include:

++
abc
*-
10+

Invalid delta input should produce an error rather than silently using a default interpretation.

Generic Requirement

The function must not contain separate implementations for:

2 selected lists
3 selected lists
4 selected lists

The same combination generator must support every valid number of selected dimensions.

Processing Flow

A clean implementation can separate the task into stages:

Validate input
      ↓
Resolve selected lists
      ↓
Parse delta
      ↓
Resolve allowed sum range
      ↓
Generate Cartesian product
      ↓
Calculate combination sum
      ↓
Filter
      ↓
Return matches

Optimization

A straightforward implementation may generate the complete Cartesian product and filter afterward.

However, for larger datasets the number of combinations grows multiplicatively.

For source sizes:

L1, L2, ..., Ln

the number of candidate combinations is:

L1 * L2 * ... * Ln

An advanced implementation may prune partial combinations when the input characteristics and target range make it safe to do so.

Correctness should be established before introducing such optimization.

Relationship to Task 4

Task 4 generates:

all combinations

Task 5 extends that operation with:

list selection
+
sum calculation
+
target filtering
+
delta rules

The Cartesian-product implementation from Task 4 should therefore be reusable rather than reimplemented specifically for this task.

Algorithm Group 9

Algorithm Group 9 contains five exercises focused on implementing string-searching, indexing, replacement, and occurrence-analysis algorithms manually.

The main objective of this group is to practice the underlying algorithms instead of delegating the work to existing string-processing helpers.

Important Restriction

For this group, built-in or standard-library functions that directly perform the required string-processing operation must not be used.

This includes helpers that directly perform operations such as:

  • substring search
  • substring containment checks
  • occurrence counting
  • string replacement
  • string splitting for the purpose of solving the search
  • regular-expression matching

Examples of functions that should not be used to directly solve these tasks include APIs equivalent to:

Index
Contains
Count
Replace
ReplaceAll
Split
Find
Match

The exact names depend on the programming language.

Basic language operations required to implement the algorithm are allowed.

These include operations such as:

  • loops
  • conditions
  • indexing
  • character or byte comparison
  • collection creation
  • collection insertion
  • reading the length of a collection
  • manually building an output string

The purpose of the restriction is to implement the search and replacement logic yourself, not to make basic language usage impossible.

Tasks

Find every starting index where one search string occurs inside another string.

Search the same source string for multiple target strings and return the indexes associated with each target.

Task 3 — Match Range Collection

Search for multiple values and return the start and end indexes of every match using a structured result.

Task 4 — Manual String Replacement

Receive replacement pairs and manually replace all matching values inside a source string.

Task 5 — Multi-Source Search Analysis

Search multiple source strings for multiple values and produce detailed occurrence information for each search request.

Objectives

The exercises in this group provide practice with:

  • manual substring matching
  • string indexing
  • sequential scanning
  • multiple search patterns
  • match-range calculation
  • structured search results
  • manual replacement
  • output construction
  • occurrence counting
  • searching across multiple sources
  • deterministic reporting

Indexing Convention

Unless an individual task states otherwise, indexes in this group use:

zero-based indexing

For ranges, this documentation uses:

[start, end)

where:

start

is the index of the first matched character and:

end

is the index immediately after the matched value.

For example, a three-character match beginning at index 8 has the range:

[8, 11)

This convention makes the match length directly calculable as:

end - start

String Representation

The provided examples contain ASCII characters only.

When implementing the tasks in a language where strings are encoded as UTF-8, consider the distinction between:

  • byte indexes
  • character indexes
  • Unicode code-point indexes

For the exact ASCII examples in this group, byte indexes and character indexes are identical.

An implementation intended to support arbitrary Unicode text should explicitly define which indexing model it uses.

Implementation

The examples define the expected behavior for the provided data.

The implementation should solve the general problem manually without relying on string-processing helpers that directly perform the required operation.

Where the original task contains an incorrect type or inconsistent index example, the individual task page defines the corrected behavior.

Task 1 — Manual Substring Search

Objective

Create a function that manually finds every occurrence of one string inside another string.

The function must return the starting index of every match.

Built-in or standard-library substring-search functions must not be used.

Input

The source string is:

s1 := "abc bca cba acb abc bca cba acb"

The search value is:

s2 := "cba"

Function

Create a function named:

FindMatch(...)

Conceptually:

FindMatch(s1, s2 string)

returns:

[]int

Search Rules

The function must scan s1 and determine every position where the complete value of s2 begins.

A match is valid only when all characters from s2 match consecutive characters in s1.

Example

The source contains:

abc bca cba acb abc bca cba acb
        ^               ^

The value:

cba

starts at indexes:

8
24

Expected Result

[]int{8, 24}

Zero-Based Indexing

Indexes are zero-based.

The first character in the source is:

index 0

Therefore, the first "cba" begins at:

index 8

Overlapping Matches

The implementation should support overlapping matches.

For example:

source = "aaaa"
search = "aa"

contains matches beginning at:

0
1
2

Therefore, after finding a match, the search should not automatically skip the entire matched substring unless the task explicitly requires non-overlapping matching.

Empty Search Value

An empty search string creates an ambiguous matching rule.

For this task, an empty search value should be rejected or return an error rather than being treated as matching every position.

Requirements

The function must:

  1. scan the source string manually
  2. compare the search value character by character
  3. identify every complete match
  4. store the starting index of every match
  5. return the indexes as []int

Restricted Operations

Do not use string-search helpers equivalent to:

Index
Contains
Find
Match
regular expressions

The matching algorithm must be implemented manually.

Implementation Notes

A straightforward implementation may treat every valid source position as a possible match start.

For each candidate position:

compare source[i + j] with search[j]

until either:

  • all search characters match
  • or a mismatch is found

The implementation should work for source and search strings other than the provided example.

Task 2 — Multiple Substring Search

Objective

Create a function that searches one source string for multiple target strings.

For every target value, return all starting indexes where that value occurs.

The search algorithm must be implemented manually.

Input

The source string is:

s1 := "abc bca cba acb abc bca cba acb"

The first search value is:

s2 := "cba"

The second search value is:

s3 := "acb"

Function

Create a function named:

FindMatch(...)

Conceptually:

FindMatch(s1, s2, s3 string)

returns:

map[string][]int

Result Type

Each map key represents a searched string.

The value associated with that key contains all starting indexes where the string was found.

For example:

map[string][]int{
    "cba": {8, 24},
    "acb": {12, 28},
}

Search for cba

The value:

cba

occurs at:

8
24

Therefore:

"cba": {8, 24}

Search for acb

The value:

acb

occurs at:

12
28

Therefore:

"acb": {12, 28}

Expected Result

map[string][]int{
    "cba": {8, 24},
    "acb": {12, 28},
}

Requirements

For every search value:

  1. manually scan the source string
  2. locate every complete occurrence
  3. collect the starting indexes
  4. associate those indexes with the searched value

The indexes must use zero-based indexing.

Missing Values

If a search value does not occur in the source, preserve the searched value in the result with an empty index collection.

For example:

map[string][]int{
    "xyz": {},
}

This makes it possible to distinguish between:

a search that produced no matches

and:

a search that was never requested

Overlapping Matches

Overlapping matches should be detected.

For example:

source = "aaaa"
search = "aa"

should produce:

[]int{0, 1, 2}

Corrected Result Type

The original version of the task described the result as:

map[string]int

while simultaneously assigning multiple indexes to each key.

Since one searched value may occur multiple times, the corrected type is:

map[string][]int

This preserves the original task behavior while making the type consistent with its expected result.

Restricted Operations

Do not use helpers that directly perform substring searching, such as APIs equivalent to:

Index
Contains
Count
Find
regular expressions

The search logic must be implemented manually.

Implementation Notes

The search operation from Task 1 can be generalized and reused internally for each requested search value.

The result should not depend on map iteration order.

Task 3 — Match Range Collection

Objective

Create a function that searches one source string for multiple string values.

For every searched value, return all ranges where that value occurs.

The result must use a structured representation.

Input

The source string is:

s1 := "acb abc bcacb cba acba abc bcacb cba acba abc bcacb qwe acb abc bcacb qwe acba"

The search values are:

s2 := []string{
    "cba",
    "acba",
    "bcacb",
    "acb",
}

Result Structure

Define:

type Chain struct {
    ID   string
    List [][]int
}

ID

ID contains the searched string.

For example:

ID: "cba"

List

List contains every match range for that search value.

Every range is represented as:

[]int{
    start,
    end,
}

using the convention:

[start, end)

The start index is inclusive.

The end index is exclusive.

Function

Create:

FindMatch(s1 string, s2 []string)

returning:

[]Chain

Match Range Example

If:

"cba"

begins at index:

14

then its range is:

[14, 17)

because the matched substring contains the source positions:

14
15
16

and index 17 is the first position after the match.

Substring Matching

Search values are treated as substrings.

This means that a value may match both:

a standalone token

and:

part of a larger token

For example:

cba

also occurs inside:

acba

because the characters cba appear consecutively within that value.

Expected Result

For the provided source, the result is:

[]Chain{
    {
        ID: "cba",
        List: [][]int{
            {14, 17},
            {19, 22},
            {33, 36},
            {38, 41},
            {75, 78},
        },
    },
    {
        ID: "acba",
        List: [][]int{
            {18, 22},
            {37, 41},
            {74, 78},
        },
    },
    {
        ID: "bcacb",
        List: [][]int{
            {8, 13},
            {27, 32},
            {46, 51},
            {64, 69},
        },
    },
    {
        ID: "acb",
        List: [][]int{
            {0, 3},
            {10, 13},
            {18, 21},
            {29, 32},
            {37, 40},
            {48, 51},
            {56, 59},
            {66, 69},
            {74, 77},
        },
    },
}

Result Ordering

The returned []Chain should preserve the order of the requested search values.

Given:

[]string{
    "cba",
    "acba",
    "bcacb",
    "acb",
}

the returned chains should appear in the same order.

Within each Chain, match ranges must appear in ascending source-index order.

No Match

If a requested value does not occur, still return a Chain for that search ID.

For example:

Chain{
    ID:   "xyz",
    List: [][]int{},
}

Overlapping Matches

Overlapping matches must be supported.

The scanner should evaluate every valid source position as a possible beginning of a match.

Input Validation

Empty search values should be rejected because their matching behavior is ambiguous.

Corrected Indexing

The original version of this exercise contained example ranges for "cba" that are not consistent with standard indexing of the provided source string.

This revised specification defines one explicit convention:

zero-based indexes
end-exclusive ranges

All expected ranges in this documentation follow that convention.

Restricted Operations

Do not use built-in or standard-library helpers that directly locate substrings.

The matching algorithm must be implemented manually.

Implementation Notes

The search algorithm can be implemented once and reused for every value in s2.

For a match starting at:

start

the end position can be calculated as:

end = start + matchLength

Task 4 — Manual String Replacement

Objective

Create a function that manually replaces multiple string values inside a source string.

Replacement rules are provided as pairs.

The first value in each pair defines what should be searched for.

The second value defines the replacement.

Built-in string-replacement helpers must not be used.

Input

The source string is:

s1 := "abc bcacb cba acba abc bcacb cba acba abc bcacb qwe acb abc bcacb qwe acba"

The second argument is:

[]string

containing replacement pairs.

Conceptually:

search value
replacement value
search value
replacement value
...

Function

Create:

ReplaceValue(s1, replacements)

returning the modified:

string

Replacement Pair Format

For:

[]string{
    "cba", "Alex",
    "acba", "Ben",
    "bcacb", "David",
}

the replacement rules are:

cba   -> Alex
acba  -> Ben
bcacb -> David

The number of elements in the replacement slice must therefore be even.

Matching Rule

Replacement is performed against complete space-separated values in the provided source.

This avoids ambiguous nested replacement behavior such as replacing "cba" inside "acba" before the explicit "acba" rule has a chance to match.

For this task, each whitespace-separated source value is treated as one token.

If the complete token matches a replacement key, replace that token.

Otherwise preserve it unchanged.

Case 1

Replacement definitions:

replacements := []string{
    "cba", "Alex",
    "acba", "Ben",
    "bcacb", "David",
}

Call:

ReplaceValue(s1, replacements)

The rules are:

cba   -> Alex
acba  -> Ben
bcacb -> David

Expected Result

abc David Alex Ben abc David Alex Ben abc David qwe acb abc David qwe Ben

Case 2

Replacement definitions:

replacements := []string{
    "abc", "Alex",
    "qwe", "Ben",
    "cba", "David",
}

Call:

ReplaceValue(s1, replacements)

The rules are:

abc -> Alex
qwe -> Ben
cba -> David

Expected Result

Alex bcacb David acba Alex bcacb David acba Alex bcacb Ben acb Alex bcacb Ben acba

Requirements

The function must:

  1. validate the replacement-pair definition
  2. process the source manually
  3. identify complete source tokens
  4. compare each token with the replacement keys
  5. append either the replacement or original token to the output
  6. preserve the original token order
  7. return the final string

Replacement Validation

The replacement collection must contain an even number of values.

For example:

[]string{
    "abc", "Alex",
    "cba",
}

is invalid because the final search value does not have a replacement partner.

Duplicate Search Keys

A replacement configuration should not contain the same search key more than once.

For example:

[]string{
    "abc", "Alex",
    "abc", "David",
}

is ambiguous.

A robust implementation should reject such configuration.

Manual Processing Restriction

Do not use APIs equivalent to:

Replace
ReplaceAll
Split
Fields
regular-expression replacement

when those APIs directly perform the operation required by the exercise.

Token boundaries and output construction should be implemented manually.

Why Token Matching Is Explicit

The original task provides several replacement keys where one value may be contained inside another.

For example:

cba

is contained inside:

acba

Treating the task as unrestricted substring replacement would therefore make replacement order affect the result.

This revised specification removes that ambiguity by defining replacement against complete whitespace-separated source values.

Implementation Notes

The algorithm may scan the input character by character and build each token manually.

When a token boundary is reached:

compare token with replacement keys

then append the appropriate value to the output.

The final result should not depend on the order in which replacement rules are stored.

Task 5 — Multi-Source Search Analysis

Objective

Create a search-analysis function that processes multiple source strings and multiple requested search values.

For every requested value, determine:

  • how many times it occurs in each source
  • how many times it occurs in total
  • whether it occurs more than three times
  • whether it does not occur at all

Each search operation must also have an ID so that its result can be associated with the values requested by that search.

The searching algorithm must be implemented manually.

Sources

The three source strings are:

s1 := "zxc ert acba abcf dty ert acba abcd zxc qwe acb abcd nmv qwe acba"

s2 := "acba ghk bcacb ert acba abc sdf ert nmv bcacb dty qwe acb abcf ghk qwe"

s3 := "abcd bcacb ghk sdf nmv abc zxc sdf acba abcd sdf qwe acb abc dty qwe ghk bcacb"

Source Collection

Represent the sources as one collection.

For example:

source := []string{
    s1,
    s2,
    s3,
}

The source index corresponds to:

0 -> s1
1 -> s2
2 -> s3

Search Request

Define a structure describing one search request:

type SearchRequest struct {
    ID     string
    Values []string
}

Example:

SearchRequest{
    ID: "search-1",
    Values: []string{
        "dty",
        "vbn",
        "nmv",
        "ert",
    },
}

Search Result

Define the occurrence information for one searched value:

type ValueSearchResult struct {
    Value         string
    SourceCounts  []int
    TotalCount    int
    MoreThanThree bool
    NotFound      bool
}

Define the complete result for one request:

type SearchResult struct {
    ID      string
    Results []ValueSearchResult
}

Function

Conceptually:

FindValues(
    source []string,
    request SearchRequest,
) SearchResult

Source Counts

For each requested value, SourceCounts contains one count for each source.

For example:

SourceCounts: []int{
    2,
    2,
    0,
}

means:

s1 -> 2 occurrences
s2 -> 2 occurrences
s3 -> 0 occurrences

Total Count

The total count is:

sum(SourceCounts)

For:

[]int{
    2,
    2,
    0,
}

the total is:

4

More Than Three

Set:

MoreThanThree: true

when:

TotalCount > 3

Otherwise:

MoreThanThree: false

Not Found

Set:

NotFound: true

when:

TotalCount == 0

Otherwise:

NotFound: false

Search Data 1

The original first search set is:

[]string{
    "dty",
    "vbn",
    "nmv",
    "ert",
}

Represent it as:

request := SearchRequest{
    ID: "search-1",
    Values: []string{
        "dty",
        "vbn",
        "nmv",
        "ert",
    },
}

Call:

FindValues(source, request)

Search Data 2

The second search set is:

request := SearchRequest{
    ID: "search-2",
    Values: []string{
        "zxc",
        "df",
        "ghk",
        "sdf",
        "gbn",
        "nmv",
        "hk",
    },
}

Call:

FindValues(source, request)

Search Data 3

The third search set is:

request := SearchRequest{
    ID: "search-3",
    Values: []string{
        "abc",
        "abcf",
        "abcd",
        "acb",
        "acba",
        "bcacb",
        "cf",
        "cd",
    },
}

Call:

FindValues(source, request)

Matching Rule

For this task, search values are matched as complete whitespace-separated values.

For example, searching for:

abc

matches the token:

abc

but does not automatically match the first three characters of:

abcd

Similarly:

acb

and:

acba

are treated as different values.

This makes occurrence counts deterministic and reflects the value-oriented structure of the provided source strings.

Requirements

For every requested value:

  1. manually inspect every source
  2. count complete-token matches in each source
  3. store one count for each source
  4. calculate the total count
  5. determine whether the total is greater than three
  6. determine whether the value was not found at all
  7. store the result under the corresponding search ID

Search ID

Every request must have a unique identifier.

The search ID makes it possible to relate the returned analysis to the collection of values that produced it.

For example:

search-1

identifies the analysis of:

dty
vbn
nmv
ert

Missing Values

Values that are never found must still appear in the result.

For example, if:

vbn

does not occur in any source:

ValueSearchResult{
    Value:         "vbn",
    SourceCounts:  []int{0, 0, 0},
    TotalCount:    0,
    MoreThanThree: false,
    NotFound:      true,
}

The same applies to other requested values that are absent.

Values Found More Than Three Times

If a searched value has:

TotalCount > 3

the result must explicitly identify that condition:

MoreThanThree: true

This is separate from the individual source counts.

Input Validation

The implementation should reject:

  • empty search IDs
  • empty search values

Duplicate values inside the same request should either be rejected or normalized so that one value is analyzed only once.

A search request with an empty Values collection may return an empty result.

Restricted Operations

Do not use helper functions that directly solve token searching or counting, including APIs equivalent to:

Split
Fields
Count
Contains
Index
regular expressions

Tokenization, comparison, and counting should be implemented manually.

Implementation Notes

This task combines several operations from the previous exercises:

manual scanning
      ↓
token identification
      ↓
value matching
      ↓
per-source counting
      ↓
total aggregation
      ↓
classification
      ↓
structured result

Keeping the scanning logic separate from the reporting model can make the implementation easier to test and reuse.

The result model intentionally preserves exact counts instead of returning only human-readable text, allowing presentation to be added separately.

Modeling

The Modeling section focuses on transforming real-world requirements into structured software systems.

Unlike the Algorithm section, these exercises are not primarily about finding one optimal algorithm.

The objective is to identify and model:

  • entities
  • relationships
  • validation rules
  • data sources
  • queries
  • commands
  • state transitions
  • inventories
  • reservations
  • schedules
  • resource allocation
  • transactions
  • priorities
  • failures
  • recovery
  • history
  • domain invariants

The section contains:

14 tasks

The exercises begin with focused domain models and gradually progress toward systems containing many interacting entities, limited resources, state machines, schedules, network relationships, and operational workflows.

The final tasks approach the complexity of small backend systems rather than isolated programming exercises.

Modeling Principles

Separate Domain Entities

Different real-world concepts should normally be represented independently.

For example:

Candidate
Developer
DevOps

or:

Hospital
Patient
Doctor
Admission
Surgery

or:

PowerPlant
Generator
Substation
TransmissionLine
ConsumerRegion

or:

Airport
Aircraft
Flight
Passenger
Gate
Runway

A separate model is useful when a concept has its own:

identity
data
relationships
lifecycle
rules
behavior

The purpose is not to create as many structures as possible.

The purpose is to represent the domain clearly.

Reuse Shared Information

When several entities share information, unnecessary duplication should be avoided.

Examples include:

Person
├── Coach
└── Client

or:

MedicalStaff
├── Doctor
└── Nurse

The exact implementation may use:

composition
embedding
inheritance where appropriate
traits
interfaces

or another equivalent technique.

The important requirement is consistency.

Model Relationships Explicitly

Real systems are networks of related entities.

Simple examples:

Training -> Coach
Training -> Client

Concert -> Artist
Concert -> Stage

Larger systems contain deeper relationships:

Region
  -> DataCenter
    -> Rack
      -> Server
        -> ServiceReplica

or:

Hospital
  -> Department
    -> Ward
      -> Room
        -> Bed
          -> Admission
            -> Patient

or:

PowerPlant
  -> Generator
    -> TransmissionNetwork
      -> Substation
        -> ConsumerRegion

References should be validated instead of assuming that every referenced entity exists.

Separate Queries from Commands

Queries retrieve information.

Commands modify state.

For example:

search medicine

does not modify inventory.

purchase medicine

does.

Likewise:

search available hotel rooms

does not create a reservation.

create reservation

does.

And:

get account balance

does not change financial state.

create transfer

does.

This distinction becomes increasingly important as system complexity grows.

Preserve Source Data

Several tasks use external data sources such as:

JSON
YAML
CSV
custom structured text

A recommended processing flow is:

Raw Data
   |
   v
Parser
   |
   v
Validation
   |
   v
Domain Models
   |
   v
Domain Processing

Business logic should operate on validated domain models rather than directly on raw file representation.

Validate Domain Rules

Modeling is not only about defining structures.

The implementation must protect important rules.

Examples include:

candidate must satisfy mandatory requirements

training must reference an existing coach

stock must not become negative

hardware components must be compatible

server capacity must not be exceeded

vehicle capacity must not be exceeded

hotel rooms must not be double-booked

one hospital bed cannot contain two patients

one operating room cannot host overlapping surgeries

transmission-line capacity must not be exceeded

refunds must not exceed captured payments

one aircraft cannot operate overlapping flights

Invalid input should be distinguishable from a valid request that simply produces no result.

Model State Explicitly

Many entities have lifecycles.

For example:

Shipment:

created
  -> reserved
  -> packed
  -> dispatched
  -> delivered

or:

Admission:

requested
  -> admitted
  -> discharged

or:

Payment:

created
  -> authorized
  -> captured
  -> settled

or:

Flight:

scheduled
  -> boarding
  -> departed
  -> in_flight
  -> landed
  -> completed

State should not be changed arbitrarily.

Valid transitions should be explicitly defined.

Distinguish Desired State from Current State

Some systems contain both:

what should exist

and:

what currently exists

For example:

Desired Replicas = 6
Running Replicas = 5

or:

Regional Demand = 180 MW
Supplied Power  = 150 MW

The difference between desired and current state may trigger recovery or corrective operations.

Model Time and Intervals Carefully

Many tasks depend on time.

Examples include:

appointments
staff shifts
maintenance windows
production deadlines
flight schedules
hotel reservations
authorization expiration

Time intervals should have clearly defined boundaries.

Scheduling systems should explicitly detect overlapping intervals.

Model Priority

Some systems cannot process every request equally.

Examples include:

emergency triage
shipment priority
production priority
critical electricity consumers
flight operational priority

Priority rules should be explicit and deterministic.

Model Resource Allocation

Later tasks contain many limited resources.

Examples include:

medicine stock
hardware stock
server CPU and memory
vehicle capacity
hotel rooms
hospital beds
operating rooms
medical equipment
raw materials
production machines
electrical generation
transmission capacity
airport gates
runways
account balances

Where applicable, distinguish:

total
available
reserved
allocated
consumed
released

Model Network Constraints

Several tasks contain graph-like physical or logical networks.

Examples include:

service dependencies
logistics routes
power transmission
airport routes

An important distinction is:

resource exists

versus:

resource can reach the required destination

The Energy Grid task makes this distinction particularly important.

Total generation may exceed total demand while a region still experiences a local deficit because network capacity or topology prevents delivery.

Preserve History

Current state alone is often insufficient.

Larger systems may need to preserve:

what happened
when it happened
which entity changed
why it changed
what caused the change

Examples include:

shipment tracking events
server migration history
patient medical history
grid operational events
production history
flight events
hotel stay history
financial ledger entries
audit events

Historical information should remain stable even after current state changes.

Model Failure and Recovery

Real systems contain failures.

Examples include:

server failure
failed delivery
room maintenance
medical equipment failure
doctor unavailability
generator failure
transmission-line failure
machine failure
aircraft failure
declined payment

The implementation should model both:

failure

and:

what happens after failure

Recovery may involve:

migration
retry
redelivery
room reassignment
patient transfer
power rerouting
load shedding
rework
rebooking
diversion
reversal
refund

Preserve Atomic Operations

Some state changes affect multiple entities and must behave as one logical operation.

Examples:

reserve all required materials or none

reserve all requested hotel rooms or none

allocate patient and bed consistently

debit one account and credit another account

create all required replica placements or reject deployment

Failed operations should not leave partially modified state.

Support Idempotency

Large systems may receive the same command more than once.

Important operations may therefore contain:

RequestID

Processing the same request twice must not duplicate its effect.

Examples include:

deployment creation
shipment creation
hotel reservation
patient admission
medication administration
grid failure report
bank transfer
refund

Protect Domain Invariants

Some rules must always remain true.

Examples:

stock >= 0

allocated resources <= total resources

one room cannot have overlapping active reservations

one hospital bed cannot have two active patients

one doctor cannot participate in overlapping procedures

generator output <= available generation capacity

transmission flow <= line capacity

storage energy >= 0

accepted production + rejected production <= produced quantity

available account balance >= 0

refund total <= captured amount

one aircraft cannot operate overlapping flights

These invariants should be tested directly.

Keep Processing Deterministic

When several valid choices exist, define deterministic behavior.

Examples:

lowest ID first
earliest deadline first
highest priority first
lowest price first
lexicographical ordering

Identical input should produce identical results.

Task 1 — Technical Candidate Qualification

Create models for:

Developer
DevOps

and determine whether candidates satisfy mandatory technical requirements.

Main topics:

  • shared candidate information
  • role-specific data
  • validation
  • qualification rules
  • missing-requirement reporting

This task introduces domain entities and rule-based evaluation.

Task 2 — Gym Training Management

Create a gym training system containing:

Coaches
Clients
Training Sessions
Exercise Sets
Schedules

Main topics:

  • shared person data
  • entity relationships
  • ID-based references
  • lookup operations
  • state updates
  • referential validation

This task introduces a central model connected to several independent entities.

Task 3 — Music Festival Management

Create a festival system containing:

Concerts
Artists
Stages
Equipment
Staff
Guards

Data is loaded from several external sources.

Main topics:

  • multi-entity modeling
  • external data
  • parsing
  • relationships
  • joining independent datasets

Task 4 — Exchange Office Network

Create two currency exchange markets and process exchange requests.

Main topics:

  • typed currencies
  • exchange rates
  • account sectors
  • market comparison
  • transaction processing
  • logging
  • structured output

This task introduces transactional processing and mutable account state.

Task 5 — Medicine Store Network

Create two medicine markets supporting:

search
filtering
price comparison
purchase
stock updates
partial purchase

Main topics:

  • catalog modeling
  • query criteria
  • purchasing
  • inventory
  • best-match selection
  • query versus command behavior

Task 6 — Hardware Store Search Engine

Create a three-market hardware system containing:

CPU
Motherboard
GPU
RAM
PowerSupply
PcCase
Cooler
SSD

Main topics:

  • heterogeneous entities
  • large datasets
  • multi-market inventory
  • compatibility
  • filtering
  • configuration building
  • purchasing
  • delivery processing

This is the first large marketplace-style modeling exercise.

Task 7 — Infrastructure Resource Orchestrator

Create an infrastructure orchestration system containing:

Regions
Data Centers
Racks
Servers
Tenants
Services
Deployments
Replicas
Quotas
Placement Rules
Health Checks
Maintenance
Migrations

Main topics:

  • hierarchical infrastructure
  • resource allocation
  • desired versus current state
  • placement constraints
  • quotas
  • failure recovery
  • migration
  • health
  • audit history
  • idempotency

Task 8 — Logistics and Shipment Processing Network

Create a logistics system containing:

Customers
Warehouses
Packages
Shipments
Vehicles
Drivers
Routes
Delivery Attempts
Tracking Events
Returns

Main topics:

  • physical location
  • shipment lifecycle
  • capacity
  • driver eligibility
  • routing
  • tracking
  • failed delivery
  • redelivery
  • returns
  • history

Task 9 — Hotel and Reservation Network

Create a hotel network containing:

Hotels
Rooms
Room Types
Guests
Reservations
Stays
Payments
Services
Housekeeping
Maintenance
Employees
Invoices

Main topics:

  • availability
  • time intervals
  • overlapping reservations
  • multi-room booking
  • pricing
  • promotions
  • check-in
  • check-out
  • room reassignment
  • housekeeping
  • maintenance
  • billing
  • occupancy
  • idempotency

This task focuses on keeping commercial reservations and physical room operations consistent.

Task 10 — Banking and Payment Processing Platform

Create a banking and payment platform containing:

Customers
Accounts
Balances
Cards
Merchants
Transfers
Payments
Authorization Holds
Captures
Settlements
Refunds
Fees
Limits
Fraud Rules
Disputes
Ledger Entries
Statements

Main topics:

  • exact monetary representation
  • balances
  • ledger modeling
  • transfers
  • authorization
  • holds
  • capture
  • settlement
  • refunds
  • reversals
  • limits
  • fraud decisions
  • disputes
  • reconciliation
  • financial invariants
  • idempotency

Task 11 — Healthcare and Hospital Management Network

Create a healthcare network containing:

Hospitals
Departments
Wards
Rooms
Beds
Patients
Doctors
Nurses
Appointments
Emergency Cases
Admissions
Diagnoses
Treatments
Medications
Laboratory Tests
Medical Equipment
Operating Rooms
Surgeries
Transfers

Main topics:

  • patient lifecycle
  • medical history
  • appointment scheduling
  • staff scheduling
  • emergency triage
  • priority processing
  • bed allocation
  • medical equipment
  • surgery scheduling
  • resource conflicts
  • hospital capacity
  • patient transfer
  • failure recovery
  • idempotency

This task introduces scarce-resource scheduling where emergency priority may require explicit changes to an existing plan.

Task 12 — Energy Grid and Power Distribution Network

Create an electrical power network containing:

Power Plants
Generators
Substations
Transformers
Transmission Lines
Consumer Regions
Critical Consumers
Energy Storage
Demand
Generation
Failures
Maintenance

Main topics:

  • network topology
  • generation and demand
  • capacity
  • power allocation
  • transmission bottlenecks
  • reserve generation
  • energy storage
  • generator failure
  • line failure
  • overload
  • rerouting
  • grid islanding
  • load shedding
  • critical consumers
  • restoration
  • operational history

This task introduces a distributed resource network where having enough total capacity does not guarantee that the resource can reach every destination.

Task 13 — Manufacturing and Production Line Management

Create a manufacturing system containing:

Factories
Production Lines
Machines
Products
Bills of Materials
Raw Materials
Workers
Work Orders
Production Batches
Quality Checks
Warehouses

Main topics:

  • bill of materials
  • material reservation
  • production planning
  • scheduling
  • machine availability
  • worker skills
  • quality control
  • rework
  • scrap
  • inventory
  • machine failure
  • production history

This task models the complete transformation from raw material to finished product.

Task 14 — International Airport and Air Traffic Network

Create a transportation network containing ten airports.

The system includes:

Airports
Terminals
Gates
Runways
Airlines
Aircraft
Flights
Passengers
Baggage
Cargo
Pilots
Cabin Crew
Fuel
Maintenance
Weather
Air Traffic Sectors

Main topics:

  • graph relationships
  • scheduling
  • interval conflicts
  • resource allocation
  • passenger connections
  • baggage transfer
  • cargo
  • crew constraints
  • maintenance
  • weather
  • delay propagation
  • cancellation
  • rerouting
  • holding
  • diversion

This is the final and broadest Modeling task.

It combines many concepts introduced throughout the section into one interconnected transportation network.

Progression

The fourteen tasks gradually increase in domain size and behavioral complexity.

Conceptually:

Task 1
Domain entities and validation

        ↓

Task 2
Relationships and state updates

        ↓

Task 3
Multiple entities and external data

        ↓

Task 4
Transactions and account state

        ↓

Task 5
Search, purchasing, and inventory

        ↓

Task 6
Large marketplace and compatibility

        ↓

Task 7
Infrastructure allocation and recovery

        ↓

Task 8
Stateful logistics lifecycle

        ↓

Task 9
Reservation intervals and hotel operations

        ↓

Task 10
Financial lifecycle and ledger consistency

        ↓

Task 11
Healthcare scheduling and emergency priority

        ↓

Task 12
Distributed network capacity and recovery

        ↓

Task 13
Manufacturing and production planning

        ↓

Task 14
Large transportation and air-traffic network

The progression is not based only on the number of models.

Each stage introduces additional forms of interaction:

relationships
state
external data
inventory
transactions
resource allocation
time
priority
scheduling
networks
failure
recovery
history
atomicity
idempotency
domain invariants

Modeling vs Algorithms

The Algorithm section mainly asks:

How should this data be processed?

The Modeling section additionally asks:

What entities exist?

What information belongs to each entity?

How are entities related?

What state does the system maintain?

Which operations are queries?

Which operations are commands?

Which state transitions are legal?

Which resources are limited?

How are resources reserved and released?

How does priority affect processing?

How does network topology affect availability?

What happens when an operation fails?

How does the system recover?

Can the operation be safely retried?

What history must be preserved?

Which invariants must always remain true?

A correct output alone is therefore not enough.

The structure and consistency of the solution are part of the exercise.

Language Independence

The tasks are language-independent.

They may be implemented using:

Go
Rust

or another suitable language.

Different languages provide different modeling mechanisms.

For example:

Go:
structs
interfaces
composition

Rust:
structs
enums
traits
composition

The exercises describe domain behavior rather than requiring one specific object-oriented technique.

Suggested Architecture

The larger tasks naturally benefit from separation of responsibilities.

A conceptual architecture may look like:

Input / External Data
        |
        v
Parsing and Validation
        |
        v
Domain Models
        |
        v
Repositories / Stores
        |
        v
Domain Services
        |
        +-- Queries
        +-- Commands
        +-- Validation
        +-- Scheduling
        +-- Priority
        +-- Allocation
        +-- State Machines
        +-- Network Processing
        |
        v
State Changes
        |
        +-- Resource Updates
        +-- Inventory Updates
        +-- Financial Updates
        +-- Operational Updates
        |
        v
History / Audit / Ledger

This architecture is not mandatory.

The important goal is to avoid placing unrelated responsibilities into one oversized model or function.

Testing

Each task should test both successful and unsuccessful behavior.

Useful categories include:

  • valid model creation
  • invalid references
  • successful queries
  • empty query results
  • successful commands
  • failed commands
  • invalid state transitions
  • inventory changes
  • resource exhaustion
  • capacity conflicts
  • time conflicts
  • priority ordering
  • network disconnection
  • file parsing
  • failure handling
  • recovery
  • atomic operations
  • duplicate requests
  • history generation
  • domain invariants

For state-changing operations, tests should verify both returned result and final system state.

For operations affecting several entities, verify that all related state remains consistent.

Examples:

failed material reservation
must not reserve only part of the required material

failed multi-room reservation
must not reserve only some requested rooms

failed patient admission
must not occupy a bed without creating the admission

failed bank transfer
must not debit only the source account

failed grid reroute
must not leave transmission flow above capacity

Goal

The goal of the Modeling section is to practice turning real-world requirements into understandable and internally consistent software systems.

A successful solution should make it clear:

  • what entities exist
  • what information they own
  • how they are related
  • what operations are available
  • which operations modify state
  • which rules protect the domain
  • how state changes over time
  • how priority affects decisions
  • how resources are allocated
  • how network topology affects availability
  • how failures are represented
  • how recovery works
  • how duplicate commands are handled
  • how history is preserved
  • which invariants must always remain true

The later tasks intentionally resemble small backend and operational systems rather than isolated programming exercises.

The objective is not to reproduce production-scale healthcare, electrical-grid, manufacturing, aviation, banking, or infrastructure platforms.

The objective is to practice the modeling decisions that make complex systems understandable, testable, deterministic, and internally consistent.

Task 1 — Technical Candidate Qualification

Objective

Create a model for evaluating candidates for two technical positions:

Developer
DevOps

Both positions share common personal information, while each position also contains role-specific technical skills.

The implementation must determine whether a candidate satisfies the mandatory requirements for the selected technical position.

The result can then be used as part of the interview ranking process.

Common Candidate Information

Both Developer and DevOps candidates contain the following personal information:

First Name
Last Name
Diploma
Years of Experience

These attributes represent the common part of both technical positions.

A possible shared model is:

type Candidate struct {
    FirstName         string
    LastName          string
    Diploma           string
    YearsOfExperience int
}

The exact structure is left to the implementation.

Developer

A Developer contains the common candidate information and additional information about:

Programming Languages
Databases

A possible model is:

type Developer struct {
    Candidate Candidate
    Languages []string
    Databases []string
}

Developer Mandatory Requirements

A Developer satisfies the mandatory requirements only when all of the following conditions are true:

Years of Experience >= 5

The candidate must know:

Golang

and must know how to use both:

PostgreSQL
Mongo

Conceptually:

experience >= 5

AND

languages contains Golang

AND

databases contains PostgreSQL

AND

databases contains Mongo

Developer Example — Qualified

Developer{
    Candidate: Candidate{
        FirstName:         "Alex",
        LastName:          "Walker",
        Diploma:           "Computer Science",
        YearsOfExperience: 7,
    },
    Languages: []string{
        "Golang",
        "Python",
    },
    Databases: []string{
        "PostgreSQL",
        "Mongo",
    },
}

This candidate satisfies all mandatory Developer requirements.

Expected qualification result:

qualified

Developer Example — Not Qualified

Developer{
    Candidate: Candidate{
        FirstName:         "David",
        LastName:          "Hill",
        Diploma:           "Software Engineering",
        YearsOfExperience: 6,
    },
    Languages: []string{
        "Golang",
    },
    Databases: []string{
        "PostgreSQL",
    },
}

This candidate has enough experience and knows Golang and PostgreSQL, but does not satisfy the Mongo requirement.

Expected qualification result:

not qualified

DevOps

A DevOps candidate contains the common candidate information and a collection of additional technical skills.

A possible model is:

type DevOps struct {
    Candidate Candidate
    Skills    []string
}

DevOps Mandatory Requirements

A DevOps candidate must have at least:

5 years of experience

and must have all four required skills:

Golang
Python
AWS
Mongo

Conceptually:

experience >= 5

AND

skills contains Golang

AND

skills contains Python

AND

skills contains AWS

AND

skills contains Mongo

DevOps Example — Qualified

DevOps{
    Candidate: Candidate{
        FirstName:         "Emma",
        LastName:          "Grant",
        Diploma:           "Information Technology",
        YearsOfExperience: 8,
    },
    Skills: []string{
        "Golang",
        "Python",
        "AWS",
        "Mongo",
        "Linux",
    },
}

This candidate satisfies all mandatory DevOps requirements.

Expected qualification result:

qualified

DevOps Example — Not Qualified

DevOps{
    Candidate: Candidate{
        FirstName:         "Ben",
        LastName:          "Miller",
        Diploma:           "Computer Engineering",
        YearsOfExperience: 7,
    },
    Skills: []string{"Golang", "Python", "AWS"},
}

The candidate is missing:

Mongo

and therefore does not satisfy all mandatory requirements.

Qualification Result

Instead of returning only:

bool

the implementation may use a structured result that explains why a candidate passed or failed.

For example:

type QualificationResult struct {
    Qualified          bool
    MissingRequirements []string
}

Example:

QualificationResult{
    Qualified: false,
    MissingRequirements: []string{"Mongo"},
}

This makes the result more useful for interview evaluation because the caller can see exactly which requirements were not satisfied.

Developer Evaluation

Conceptually:

func EvaluateDeveloper(candidate Developer) QualificationResult

The function should verify:

  1. years of experience
  2. Golang knowledge
  3. PostgreSQL knowledge
  4. Mongo knowledge

Every missing mandatory requirement should be reported.

DevOps Evaluation

Conceptually:

func EvaluateDevOps(candidate DevOps) QualificationResult

The function should verify:

  1. years of experience
  2. Golang skill
  3. Python skill
  4. AWS skill
  5. Mongo skill

Every missing mandatory requirement should be reported.

Multiple Missing Requirements

The evaluation should not stop after the first failed requirement.

For example:

Developer{
    Candidate: Candidate{
        YearsOfExperience: 3,
    },
    Languages: []string{"Python"},
    Databases: []string{"PostgreSQL"},
}

is missing several requirements:

minimum 5 years of experience
Golang
Mongo

A useful result would therefore contain all three failures.

Skill Matching

Skill names should be compared consistently.

The implementation should define whether values such as:

Golang
golang
Go

represent the same skill.

The original task names the required language specifically as:

Golang

so an implementation should use a deterministic representation rather than relying on uncontrolled free-form spelling.

The same applies to:

PostgreSQL
Mongo
Python
AWS

Interview Ranking

The original task states that qualification should be usable for ranking candidates during the interview process.

However, it defines only mandatory pass/fail requirements and does not define a scoring formula.

Therefore this task requires determining whether candidates satisfy mandatory requirements.

A numerical ranking system should not be invented unless it is added as an explicit extension.

Requirements

The implementation must:

  1. model common personal information
  2. model Developer-specific technical information
  3. model DevOps-specific technical information
  4. evaluate Developer mandatory requirements
  5. evaluate DevOps mandatory requirements
  6. report whether the candidate is qualified
  7. identify missing mandatory requirements

Modeling Goal

The important part of this task is not only the final qualification check.

The implementation should avoid duplicating common candidate information between unrelated models.

Conceptually:

        Candidate
        ├── Developer-specific information
        └── DevOps-specific information

This allows shared personal data to remain consistent while role-specific technical requirements remain separate.

Optional Extension

A more extensible implementation may represent position requirements as data.

For example:

type PositionRequirements struct {
    MinimumExperience int
    RequiredLanguages []string
    RequiredDatabases []string
    RequiredSkills    []string
}

This would allow the evaluation engine to support additional technical positions without creating completely separate hard-coded validation logic.

This is an optional design extension and is not required by the original task.

Task 2 — Gym Training Management

Objective

Create a model for managing private training sessions inside a gym.

Each training session connects:

  • a coach
  • a client
  • a training time
  • an exercise set
  • a training duration

The implementation must also provide operations for reading and updating training information.

Training

Each training contains the following information:

Training ID
Training Time
Coach
Client
Exercise Set
Training Length

A possible model is:

type Training struct {
    ID          string
    Timeline    time.Time
    CoachID     string
    ClientID    string
    ExerciseSet []string
    Duration    time.Duration
}

The exact field types may be adapted to the implementation.

Person Information

Both coaches and clients contain common personal information:

ID
First Name
Last Name
Phone Number

A shared model may therefore be useful.

For example:

type Person struct {
    ID          string
    FirstName   string
    LastName    string
    PhoneNumber string
}

Coach

A coach may be modeled using the shared personal information.

For example:

type Coach struct {
    Person Person
}

No additional coach-specific attributes are required by the original task.

Client

A client contains the shared personal information and two additional attributes:

Height
Weight

For example:

type Client struct {
    Person Person
    Height float64
    Weight float64
}

Relationship Between Models

A training references one coach and one client.

Conceptually:

Training
├── Coach
├── Client
├── Timeline
├── Exercise Set
└── Duration

The implementation may store:

  • embedded Coach and Client values
  • references by ID
  • another equivalent relationship model

Using IDs is often useful when coaches and clients are stored independently.

Custom Data Module

Create a separate custom module containing example data for:

  • coaches
  • clients

For example:

coaches
clients

The original task allows the implementation to define its own example lists.

The important requirement is that training records can reference valid coach and client records.

Training Store

A useful implementation may also create a training collection.

For example:

type TrainingStore struct {
    Trainings []Training
    Coaches   []Coach
    Clients   []Client
}

The exact design is left to the implementation.

Required Operations

The implementation must support the following operations.

Get Coach Data by Training ID

Given a training ID, return the coach assigned to that training.

Conceptually:

GetCoachByTrainingID(trainingID)

The operation should:

  1. find the training
  2. obtain its coach reference
  3. find the corresponding coach
  4. return coach data

Get Client Data by Training ID

Given a training ID, return the client assigned to that training.

Conceptually:

GetClientByTrainingID(trainingID)

Get Training Start Time

Given a training ID, return the time when the training begins.

Conceptually:

GetTrainingTimeline(trainingID)

Get Exercise Set

Given a training ID, return the complete exercise set.

Conceptually:

GetExerciseSet(trainingID)

Update Training Time

The training timeline must be changeable.

This represents a case where the scheduled training time has changed.

Conceptually:

UpdateTrainingTimeline(
    trainingID,
    newTimeline,
)

The operation should modify only the selected training.

Update Exercise Set and Training Length

The implementation must also support changing:

Exercise Set
Training Length

Conceptually:

UpdateTrainingPlan(
    trainingID,
    newExerciseSet,
    newDuration,
)

Both values belong to the training session and should be updated together when the training plan changes.

Lookup Result

Operations should distinguish between:

training found
training not found

For example, a lookup should not silently return an empty coach or client when the training ID does not exist.

A possible result style is:

value, error

or an equivalent mechanism in the chosen language.

Referential Validation

When creating a training, the implementation should ensure that:

  • CoachID refers to an existing coach
  • ClientID refers to an existing client

A training should not reference entities that do not exist in the configured data store.

This validation follows naturally from the model relationships.

Training Validation

Useful validation includes:

  • Training ID is not empty
  • Coach exists
  • Client exists
  • Timeline is valid
  • Exercise set is not empty
  • Duration is greater than zero

The original task does not prescribe a detailed validation system, so the exact validation rules beyond valid model relationships may be chosen by the implementation.

Example Data

Example coach:

Coach{
    Person: Person{
        ID:          "coach-1",
        FirstName:   "Alex",
        LastName:    "Grant",
        PhoneNumber: "+381641111111",
    },
}

Example client:

Client{
    Person: Person{
        ID:          "client-1",
        FirstName:   "Ben",
        LastName:    "Walker",
        PhoneNumber: "+381642222222",
    },
    Height: 185,
    Weight: 86,
}

Example training:

Training{
    ID:       "training-1",
    CoachID:  "coach-1",
    ClientID: "client-1",
    ExerciseSet: []string{
        "Squat",
        "Bench Press",
        "Pull-Up",
    },
}

The actual example values may be changed.

Requirements

The implementation must:

  1. model a training session
  2. model coaches
  3. model clients
  4. store client height and weight
  5. create custom coach and client collections
  6. retrieve coach data using a training ID
  7. retrieve client data using a training ID
  8. retrieve training start time
  9. retrieve the exercise set
  10. update training time
  11. update exercise set
  12. update training duration
  13. execute examples for the required operations

Modeling Goal

The main modeling relationship is:

Coach       Client
   \         /
    \       /
     Training
        |
        +-- Timeline
        +-- Exercise Set
        +-- Duration

The training session is the central entity.

Coach and client information should remain independent domain data rather than being duplicated inside every training record.

Optional Extension

An extended implementation could add:

  • training status
  • maximum duration
  • trainer availability
  • client scheduling conflicts
  • training history

These are not required by the original task.

Task 3 — Music Festival Management

Objective

Create a model for a music festival.

The system must describe:

  • concerts
  • artists
  • stages
  • equipment
  • staff
  • guards

Some data is created directly in code, while other data must be loaded from external files.

The implementation must then connect these data sources and provide information about the people and equipment related to a specific concert and stage.

Domain Overview

The main relationships are:

        Artist
           |
        Concert
           |
        Stage
           |
        Equipment
        
        Stage
        ├── Staff
        └── Guards

A concert references both an artist and a stage.

A stage references equipment.

Staff and guards are assigned to stages.

Concert

Each concert contains:

  • ID
  • Day
  • Stage ID
  • Timeline
  • Artist ID

A possible model is:

type Concert struct {
    ID       string
    Day      int
    StageID  string
    Timeline time.Time
    ArtistID string
}

Artist

Each artist contains:

  • ID
  • Name

For example:

type Artist struct {
    ID   string
    Name string
}

Stage

Each stage contains:

  • ID
  • Name
  • Equipment ID

For example:

type Stage struct {
    ID          string
    Name        string
    EquipmentID string
}

Equipment

Each equipment record contains:

  • ID
  • Mix Desk

The mix desk represents the equipment used for mixing concert sound.

For example:

type Equipment struct {
    ID      string
    MixDesk string
}

Staff

Staff represents technical personnel working on the festival.

Each staff member contains:

  • ID
  • First Name
  • Last Name
  • Stage ID
  • Team
  • Position

A possible model is:

type Staff struct {
    ID        int
    FirstName string
    LastName  string
    StageID   string
    TeamID    string
    Position  string
}

Technical Positions

Supported technical positions are:

  • Video-E
  • Audio-E
  • SpecialEffects-E
  • Video-T
  • Audio-T
  • SpecialEffects-T

Their meanings are:

        Video-E          -> Video Engineer
        Audio-E          -> Audio Engineer
        SpecialEffects-E -> Special Effects Engineer
        
        Video-T          -> Video Technician
        Audio-T          -> Audio Technician
        SpecialEffects-T -> Special Effects Technician

A typed representation is recommended instead of unrestricted strings.

For example:

type StaffPosition string

with predefined allowed values.

Guard

Each stage also has guards.

A guard contains:

  • ID
  • First Name
  • Last Name
  • Stage ID
  • Team ID
  • Sector

For example:

type Guard struct {
    ID        int
    FirstName string
    LastName  string
    StageID   string
    TeamID    string
    Sector    string
}

Static Artist Data

Create a separate module containing the following artists:

[]Artist{
    {
        ID:   "1",
        Name: "Coldplay",
    },
    {
        ID:   "2",
        Name: "Nightwish",
    },
    {
        ID:   "3",
        Name: "Protoculture",
    },
}

Static Equipment Data

Create the following equipment records:

[]Equipment{
    {
        ID:      "1",
        MixDesk: "Soundcraft Vi3000",
    },
    {
        ID:      "2",
        MixDesk: "Allen&Heath SQ-7",
    },
    {
        ID:      "3",
        MixDesk: "Midas M32 Live",
    },
}

Static Stage Data

Create the following stages:

[]Stage{
    {
        ID:          "1",
        Name:        "Blue",
        EquipmentID: "1",
    },
    {
        ID:          "2",
        Name:        "Red",
        EquipmentID: "2",
    },
    {
        ID:          "3",
        Name:        "Green",
        EquipmentID: "3",
    },
}

Concert Data Source

Concert data must be stored in a separate YAML file.

Example:

- id: "1"
  day: 1
  stageID: "1"
  timeline: "2019-11-27T18:00:00Z"
  artistID: "1"

- id: "2"
  day: 1
  stageID: "2"
  timeline: "2019-11-27T18:00:00Z"
  artistID: "2"

- id: "3"
  day: 1
  stageID: "3"
  timeline: "2019-11-27T18:00:00Z"
  artistID: "3"

The implementation must:

  1. read the YAML file
  2. parse every concert
  3. convert the file data into the concert model

Guard Data Source

Guard data must be stored in a separate JSON file.

Example:

[
  {
    "id": 601,
    "firstName": "Paul",
    "lastName": "Hoffman",
    "stageID": "1",
    "teamID": "1",
    "sector": "1"
  },
  {
    "id": 602,
    "firstName": "Eliot",
    "lastName": "Page",
    "stageID": "1",
    "teamID": "1",
    "sector": "1"
  },
  {
    "id": 611,
    "firstName": "Darius",
    "lastName": "Shaw",
    "stageID": "1",
    "teamID": "1",
    "sector": "2"
  },
  {
    "id": 612,
    "firstName": "Freddie",
    "lastName": "Hoffman",
    "stageID": "1",
    "teamID": "1",
    "sector": "2"
  }
]

The implementation must:

  1. read the JSON file
  2. parse all guards
  3. convert them into guard models

Staff Data Source

Staff data must be stored in a separate CSV file.

The source uses semicolon-separated values.

Example:

401;David;Hill;1;1;Video-E
402;Alex;Grant;1;1;Video-E
403;Jason;Milles;1;1;Video-T
404;Zak;Walker;1;1;Video-T
405;Luke;Jackson;1;1;Video-T
201;Samanta;Russo;1;1;Audio-E
202;James;Hawkins;1;1;Audio-E
203;Rhona;Davidson;1;1;Audio-T
204;Wade;Atkinson;1;1;Audio-T
205;Norma;Bender;1;1;Audio-T
801;Lillie;Wood;1;1;SpecialEffects-E
802;Paul;Russell;1;1;SpecialEffects-T

The expected field order is:

  • ID
  • FirstName
  • LastName
  • StageID
  • TeamID
  • Position

The implementation must:

  1. read the CSV file
  2. use ; as the delimiter
  3. parse every staff record
  4. convert the data into staff models

Data Relationships

The implementation must connect the loaded data using IDs.

Concert to Stage

Concert.StageID
    ↓
Stage.ID

Concert to Artist

Concert.ArtistID
    ↓
Artist.ID

Stage to Equipment

Stage.EquipmentID
    ↓
Equipment.ID

Staff to Stage

Staff.StageID
    ↓
Stage.ID

Guard to Stage

Guard.StageID
    ↓
Stage.ID

Required Query

For a specific concert and its stage, the implementation must provide information about:

  • staff working on the stage
  • equipment used on that stage
  • guards working on that stage

The concert itself can be used to resolve the stage.

Conceptually:

        Concert
           ↓ StageID
        Stage
           ↓ EquipmentID
        Equipment
        
        Stage
        ├── Staff
        └── Guards

Suggested Result Model

A structured result may be used:

type ConcertStageReport struct {
    Concert   Concert
    Artist    Artist
    Stage     Stage
    Equipment Equipment
    Staff     []Staff
    Guards    []Guard
}

This is not required by the source, but it is a useful representation of the requested joined data.

Example Resolution

For:

Concert ID = 1

the concert references:

StageID = 1
ArtistID = 1

The stage is:

Blue

The artist is:

Coldplay

The stage references equipment:

EquipmentID = 1

which corresponds to:

Soundcraft Vi3000

Staff with:

StageID = 1

must be included.

Guards with:

StageID = 1

must also be included.

Data Validation

The implementation should detect broken references.

For example:

Concert.StageID does not exist
Concert.ArtistID does not exist
Stage.EquipmentID does not exist

These relationships are required for the requested data resolution.

File Errors

The implementation should also distinguish errors such as:

file not found
invalid YAML
invalid JSON
invalid CSV
invalid field value
unknown technical position

The exact error representation is left to the implementation.

Requirements

The implementation must:

  1. model concerts
  2. model artists
  3. model stages
  4. model equipment
  5. model staff
  6. model guards
  7. define all supported technical positions
  8. create the provided artist list
  9. create the provided equipment list
  10. create the provided stage list
  11. load concerts from YAML
  12. load guards from JSON
  13. load staff from semicolon-separated CSV
  14. resolve a concert’s stage
  15. resolve the artist
  16. resolve the stage equipment
  17. return all staff working on the stage
  18. return all guards working on the stage

Modeling Goal

The main challenge is joining information that originates from several independent data sources.

Conceptually:

            YAML
             |
          Concert
         /       \
     Artist      Stage
                  |
               Equipment
              /         \
            Staff       Guards
             |            |
            CSV          JSON

The implementation should avoid duplicating information across models.

IDs should be used to connect related entities.

Optional Extension

Possible extensions include:

  • multiple concert days
  • staff shifts
  • equipment replacement
  • guard teams
  • sector-level guard queries
  • stage scheduling conflicts
  • artist scheduling conflicts

These features are not required by the original task.

Task 4 — Exchange Office Network

Objective

Create a model for an exchange-office network.

The system contains two exchange markets with different exchange rates.

Users submit transactions that convert money from one currency into another.

For every transaction, the implementation must:

  • determine the requested currency pair
  • compare exchange rates between markets
  • select the better exchange market
  • calculate the converted amount
  • identify the user account
  • credit the converted amount to the destination-currency sector
  • produce a structured exchange result
  • produce logs
  • print output in JSON format

Supported Currencies

The system supports:

GBP — UK Pound
USD — US Dollar
EUR — Euro
RUB — Russian Ruble
JPY — Japanese Yen
INR — Indian Rupee

A typed currency representation is recommended.

For example:

type Currency string

const (
    CurrencyGBP Currency = "GBP"
    CurrencyUSD Currency = "USD"
    CurrencyEUR Currency = "EUR"
    CurrencyRUB Currency = "RUB"
    CurrencyJPY Currency = "JPY"
    CurrencyINR Currency = "INR"
)

Exchange Market

Each exchange market contains rates for supported currency transformations.

A possible model is:

type ExchangeMarket struct {
    ID    int
    Rates []ExchangeRate
}

with:

type ExchangeRate struct {
    From Currency
    To   Currency
    Rate float64
}

For example:

GBP -> USD = 1.189

means:

1 GBP = 1.189 USD

Exchange Market 1

Use the following rates:

GBP -> USD = 1.189
GBP -> EUR = 1.184
GBP -> RUB = 77.294
GBP -> JPY = 163.314
GBP -> INR = 94.480

USD -> GBP = 0.840
USD -> EUR = 0.995
USD -> RUB = 65.000
USD -> JPY = 137.265
USD -> INR = 79.435

EUR -> GBP = 0.844
EUR -> USD = 1.004
EUR -> RUB = 65.279
EUR -> JPY = 137.790
EUR -> INR = 79.768

RUB -> GBP = 0.012
RUB -> USD = 0.015
RUB -> EUR = 0.015
RUB -> JPY = 2.110
RUB -> INR = 1.222

JPY -> GBP = 0.006
JPY -> USD = 0.007
JPY -> EUR = 0.007
JPY -> RUB = 0.473
JPY -> INR = 0.578

INR -> GBP = 0.010
INR -> USD = 0.012
INR -> EUR = 0.012
INR -> RUB = 0.818
INR -> JPY = 1.727

Exchange Market 2

Use the following rates:

GBP -> USD = 1.180
GBP -> EUR = 1.189
GBP -> RUB = 76.294
GBP -> JPY = 164.314
GBP -> INR = 93.480

USD -> GBP = 0.860
USD -> EUR = 0.999
USD -> RUB = 64.000
USD -> JPY = 136.265
USD -> INR = 80.435

EUR -> GBP = 0.824
EUR -> USD = 1.000
EUR -> RUB = 66.279
EUR -> JPY = 138.790
EUR -> INR = 80.768

RUB -> GBP = 0.082
RUB -> USD = 0.010
RUB -> EUR = 0.018
RUB -> JPY = 2.010
RUB -> INR = 1.122

JPY -> GBP = 0.004
JPY -> USD = 0.009
JPY -> EUR = 0.010
JPY -> RUB = 0.495
JPY -> INR = 0.555

INR -> GBP = 0.012
INR -> USD = 0.010
INR -> EUR = 0.011
INR -> RUB = 0.805
INR -> JPY = 1.740

Exchange Rate Data Source

The exchange-rate tables must first be transformed into JSON.

The application must then load the exchange-market data from that JSON file.

A possible JSON structure is:

[
  {
    "marketID": 1,
    "rates": [
      {
        "from": "GBP",
        "to": "USD",
        "rate": 1.189
      }
    ]
  }
]

The exact JSON structure is left to the implementation.

The important requirement is that the rates are loaded from a file rather than hard-coded directly into processing logic.

Selecting the Best Exchange Market

For a transaction:

GBP -> USD

compare the GBP-to-USD rate from every market.

Market 1:

1.189

Market 2:

1.180

For the same source amount, the larger rate produces more destination currency.

Therefore:

Market 1

is the better market for this transformation.

For:

GBP -> EUR

the rates are:

Market 1 = 1.184
Market 2 = 1.189

Therefore:

Market 2

is better.

User Account

Each account contains:

Username
Account ID
Money stored in six currency sectors

The currency-sector mapping is:

Sector 0 -> USD
Sector 1 -> GBP
Sector 2 -> EUR
Sector 3 -> RUB
Sector 4 -> JPY
Sector 5 -> INR

A possible model is:

type Account struct {
    Username  string
    AccountID uint64
    Balances  map[Currency]float64
}

A typed currency map is preferable to accessing sectors using unexplained integer indexes throughout the application.

Account Data

Use the following accounts:

Alex   |2453425652|50000   |60000   |70000    |5000000|6000000|8000000
Ben    |8674652543|60000   |70000   |80000    |6000000|7000000|10000000
David  |8562953754|30000   |40000   |30000    |3000000|4000000|20000000
Emma   |848563421 |10000   |10000   |10000    |1000000|1000000|10000000
Fiona  |335647689 |5000    |5000    |5000     |100000 |100000 |1000000
Gina   |242564578 |4000    |4000    |4000     |50000  |50000  |500000
Alex   |667783546 |0       |0       |0        |0      |0      |0
Tina   |565283951 |0       |0       |0        |0      |0      |0
Simon  |4677859941|500000  |600000  |700000   |0      |0      |0
Selena |4877639720|250000  |350000  |450000   |0      |0      |0
Nolan  |7445668990|0       |0       |0        |2500000|4500000|7500000
Naomi  |352678541 |0       |0       |0        |7500000|8500000|3500000
Paul   |9122387545|10000000|10000000|10000000 |0      |0      |0
Lana   |8766234490|750000  |2000000 |10000000 |0      |0      |0
Nolan  |2556779340|0       |2500000 |2500000  |0      |0      |0

The balance column order is:

USD
GBP
EUR
RUB
JPY
INR

Transaction Model

Each transaction contains:

Source Currency
Destination Currency
Amount

A possible model is:

type Transaction struct {
    From   Currency
    To     Currency
    Amount float64
}

A user’s transaction request also needs account identity.

For example:

type TransactionRequest struct {
    RequestID string
    Username  string
    AccountID uint64
    Transaction Transaction
}

Transaction List

Use the following requests:

Alex  |2453425652|GBP-USD-500000
Ben   |8674652543|GBP-EUR-750000
David |8562953754|USD-EUR-800000
Ema   |848563421 |USD-JPY-900000
Fiona |335647689 |EUR-RUB-1000000
Gina  |242564578 |EUR-INR-1200000
Alex  |667783546 |RUB-GBP-15000000
Tina  |565283951 |RUB-USD-17500000
Simon |4677859941|JPY-GBP-25000000
Selena|4877639720|JPY-EUR-30000000
Nolan |7445668990|INR-RUB-5000000
Naomi |352678541 |INR-JPY-8000000
Paul  |9122387545|USD-EUR-650000|GBP-EUR-850000
Lana  |8766234490|RUB-GBP-16500000|RUB-USD-19500000
Nolan |2556779340|JPY-GBP-32000000|JPY-EUR-36500000

Some accounts contain more than one transaction.

The implementation must process every listed transaction.

Source Data Inconsistency

The account table contains:

Emma | 848563421

while the transaction table contains:

Ema | 848563421

The Account ID is the same.

The implementation should not silently assume that arbitrary username differences are equivalent.

A reasonable solution is to use:

AccountID

as the primary account identifier and report the username mismatch as a data inconsistency.

Exchange Calculation

For:

amount = A
rate   = R

the converted amount is:

converted = A * R

For example:

500000 GBP
GBP -> USD
best rate = 1.189

produces:

500000 * 1.189 = 594500 USD

Destination Sector Update

After conversion, the converted amount must be sent to the account sector corresponding to the destination currency.

For:

GBP -> USD

the resulting money belongs to:

USD
Sector 0

For:

RUB -> GBP

the result belongs to:

GBP
Sector 1

Important Source Limitation

The source explicitly states that the converted amount must be sent to the destination account sector.

It does not explicitly define:

  • whether the original source amount must be debited
  • whether insufficient source balance should reject a transaction
  • whether overdrafts are allowed

Several supplied transaction amounts are also larger than the corresponding example account balances.

Therefore the base task should not silently invent insufficient-funds behavior.

If source-balance deduction is implemented, it should be clearly documented as an extension to the original task.

Exchange Query Result

Each processed exchange query must contain:

Query ID
Request Person
Source Currency
Destination Currency
Original Amount
Selected Market
Best Exchange Rate
Converted Amount
Destination Sector

A possible model is:

type ExchangeResult struct {
    RequestID       string
    Username        string
    AccountID       uint64
    From            Currency
    To              Currency
    SourceAmount    float64
    MarketID        int
    ExchangeRate    float64
    ConvertedAmount float64
    DestinationSector int
}

Multiple Transactions

Accounts such as:

Paul
Lana
Nolan

contain multiple exchange operations.

Every transaction must be independently evaluated because different currency pairs may select different exchange markets.

Equal Exchange Rates

The source does not define what should happen when both markets have the same rate.

The implementation must define a deterministic rule.

For example:

select the market with the lowest Market ID

This rule should be documented.

Logging

The implementation must create logs for exchange processing.

Useful log information includes:

request ID
account ID
currency pair
selected market
exchange rate
converted amount
processing result

The exact logging framework is left to the implementation.

JSON Output

The final exchange result must be printable in JSON format.

Example:

{
  "requestID": "exchange-1",
  "username": "Alex",
  "accountID": 2453425652,
  "from": "GBP",
  "to": "USD",
  "sourceAmount": 500000,
  "marketID": 1,
  "exchangeRate": 1.189,
  "convertedAmount": 594500,
  "destinationSector": 0
}

Validation

The implementation should detect:

unknown account
unsupported currency
invalid currency pair
missing exchange rate
invalid transaction amount
invalid market data
malformed transaction input

Transaction amounts should be greater than zero.

A transformation where:

From == To

should be rejected because no exchange operation is required.

Requirements

The implementation must:

  1. model supported currencies
  2. model exchange markets
  3. model exchange rates
  4. transform the supplied exchange-rate tables into JSON
  5. load exchange rates from JSON
  6. model user accounts
  7. preserve the defined currency-sector mapping
  8. model transaction requests
  9. parse all supplied transactions
  10. compare exchange markets
  11. select the best exchange rate
  12. calculate the converted amount
  13. credit the destination currency sector
  14. return exchange-query information
  15. implement processing logs
  16. print results in JSON format

Modeling Goal

The task combines several independent domain concepts:

ExchangeMarket
      |
      +-- ExchangeRate

Account
      |
      +-- Currency Balances

TransactionRequest
      |
      +-- Account
      +-- Currency Pair
      +-- Amount
      |
      v
Exchange Processor
      |
      +-- Select Market
      +-- Convert
      +-- Update Destination Sector
      +-- Log
      +-- JSON Result

The implementation should keep exchange-rate data, account data, transaction input, and processing logic separated rather than placing all behavior into one large structure.

Task 5 — Medicine Store Network

Objective

Create a network of medicine stores.

Each medicine market contains its own medicine inventory.

The system must support:

  • medicine information queries
  • filtering by medical purpose
  • filtering by effectiveness
  • filtering by price
  • direct medicine lookup
  • medicine purchases
  • multi-item purchases
  • price comparison between markets
  • stock updates
  • partial-order behavior
  • out-of-stock reporting

Medicine Model

Each medicine contains:

  • ID
  • Medicine Name
  • Purpose
  • Level of Effectiveness
  • Price in Pounds
  • Availability

A possible model is:

type Medicine struct {
    ID            int
    Name          string
    Purpose       MedicinePurpose
    Effectiveness int
    Price         float64
    Availability  int
}

Medicine Purpose

Six purpose types exist:

  • Type 1 — Flu
  • Type 2 — Throat Infection
  • Type 3 — Nose Drops
  • Type 4 — Stomach Virus
  • Type 5 — High Pressure
  • Type 6 — Headache

The market tables use:

  • T1
  • T2
  • T3
  • T4
  • T5
  • T6

A typed model is recommended. For example:

type MedicinePurpose string

Effectiveness

Effectiveness is an integer from:

1
2
3

The implementation must reject values outside this range when validating market data.

Availability

Availability represents:

number of medicine boxes currently in stock

It must never become negative.

Medicine Market

A possible market model is:

type MedicineMarket struct {
    ID        int
    Medicines []Medicine
}

Medicine Market 1

Use the following data:

ID | Name | Purpose | Effectiveness | Price | Availability

1  | N13 | T1 | 2 | 14 | 10
2  | M10 | T4 | 3 | 12 | 8
3  | K21 | T6 | 2 | 10 | 7
4  | PL1 | T3 | 3 | 18 | 7
5  | TR1 | T2 | 2 | 14 | 6
6  | D10 | T5 | 1 | 11 | 4
7  | Q15 | T6 | 3 | 20 | 3
8  | L20 | T3 | 2 | 15 | 8
9  | GF1 | T1 | 1 | 12 | 9
10 | BC1 | T5 | 2 | 14 | 5
11 | ZC1 | T4 | 1 | 14 | 6
12 | MGS | T2 | 2 | 12 | 12
13 | W15 | T5 | 3 | 20 | 4
14 | H25 | T2 | 2 | 15 | 7
15 | AF1 | T6 | 1 | 12 | 6
16 | DFC | T3 | 2 | 14 | 7
17 | KP1 | T4 | 3 | 14 | 9
18 | TUP | T1 | 2 | 12 | 8

Medicine Market 2

Use the following data:

ID | Name | Purpose | Effectiveness | Price | Availability

1  | FR2 | T1 | 2 | 15 | 10
2  | FT3 | T4 | 3 | 13 | 6
3  | QE1 | T6 | 1 | 11 | 9
4  | GH1 | T3 | 3 | 19 | 7
5  | TY2 | T2 | 2 | 13 | 5
6  | V10 | T5 | 1 | 12 | 6
7  | AD3 | T6 | 3 | 21 | 5
8  | LP2 | T3 | 2 | 16 | 10
9  | BK1 | T1 | 1 | 13 | 6
10 | ML2 | T5 | 2 | 13 | 7
11 | DV1 | T4 | 3 | 12 | 8
12 | MG2 | T2 | 2 | 11 | 10
13 | WR1 | T5 | 3 | 19 | 3
14 | HR2 | T2 | 2 | 14 | 4
15 | AU1 | T6 | 1 | 10 | 6
16 | DF2 | T3 | 2 | 15 | 7
17 | ZR3 | T4 | 1 | 16 | 6
18 | MFR | T1 | 2 | 14 | 5

Query Types

Two major query forms exist:

  • Information Query
  • Purchase Query

An information query searches medicine data without changing stock.

A purchase query selects medicine, creates an order, and updates stock.

Information Queries

Information queries must not modify medicine availability.

Alex

Alex wants information about all medicines where:

Purpose = Flu
Effectiveness = 3
Price <= 15 pounds

This is an information-only request.

No inventory may be changed.

Purchase Queries

Ben

Ben wants to buy any medicine matching:

Purpose = Throat Infection
Effectiveness = 2
10 <= Price <= 13
Quantity = 2 boxes

The implementation must find the best solution based on price.

David

David wants to buy any medicine matching:

Purpose = Nose Drops
Effectiveness = 2
Price <= 16
Quantity = 2 boxes

Elena

Elena wants to buy any medicine matching:

Purpose = Stomach Virus
Effectiveness = 2
Price <= 16
Quantity = 1 box

Fred

Fred wants to buy medicine matching:

Purpose = High Pressure
Effectiveness = 3
Price <= 19
Quantity = 3 boxes

Direct Medicine Purchases

Some buyers request medicines directly by medicine name.

Kyle — Direct Order 1

Kyle wants:

AU1 — 3 boxes
AD3 — 3 boxes

Merry

Merry wants:

K21 — 2 boxes
W15 — 4 boxes

Partial Order Case

Kyle also submits another order:

Q15 — 3 boxes
AD3 — 3 boxes

with the rule:

If one medicine is found, execute the purchase anyway.

This means the order explicitly allows partial completion.

Selena

Selena wants to buy medicine matching:

Purpose = Stomach Virus
Effectiveness = 3
Excluded medicines:
    DV1
    KP1

Price = irrelevant
Quantity = 3 boxes

The implementation must not return either excluded medicine.

Search Query Model

A flexible query model may be used.

For example:

type MedicineSearch struct {
    Purpose           *MedicinePurpose
    Effectiveness     *int
    MinimumPrice      *float64
    MaximumPrice      *float64
    IncludedNames     []string
    ExcludedNames     []string
}

Optional fields allow the same model to represent different search combinations.

Purchase Item

Direct orders may use:

type PurchaseItem struct {
    MedicineName string
    Quantity     int
}

Purchase Request

A possible model is:

type PurchaseRequest struct {
    Buyer               string
    Search               *MedicineSearch
    Items                []PurchaseItem
    AllowPartialPurchase bool
}

The exact design is left to the implementation.

Selecting the Best Purchase

For every purchase made through search criteria, find the best solution for the buyer based on price.

This means the implementation must compare matching and sufficiently stocked medicine across markets.

When equivalent medicines satisfy the request, prefer the solution producing the lowest final purchase price.

Stock Requirements

A medicine is purchasable only if its availability is sufficient for the requested quantity.

For example:

Requested quantity = 3
Availability = 2

is insufficient.

The purchase must not cause:

Availability < 0

Stock Update

After a successful purchase:

newAvailability = oldAvailability - purchasedQuantity

Information-only queries must never change availability.

Out of Stock

After updating inventory, the system must report when a specific medicine becomes out of stock.

For example:

Availability before purchase = 3
Purchased = 3
Availability after purchase = 0

The result should include:

medicine is now out of stock

Medicine Not Found

If the requested medicine cannot be found, the system must report that condition.

This applies to both:

  • information queries
  • purchase queries

Do not silently return an empty successful result.

Multiple Medicine Orders

For orders containing multiple medicines, the request must define whether partial fulfillment is allowed.

All-or-Nothing

When:

AllowPartialPurchase = false

and one required item cannot be purchased, the complete order should not be executed.

Partial Purchase

When:

AllowPartialPurchase = true

available items may still be purchased even when another requested item cannot be supplied.

The Kyle Q15 + AD3 query explicitly requires this behavior.

Order Model

A purchase result should contain enough information to explain the complete order.

For example:

type OrderItem struct {
    MarketID       int
    Medicine       Medicine
    Quantity       int
    UnitPrice      float64
    TotalPrice     float64
    RemainingStock int
}

type Order struct {
    Buyer      string
    Items      []OrderItem
    FinalPrice float64
    Complete   bool
}

Purchase Output

For every completed purchase, print:

  • Buyer Name
  • Medicine Description
  • Medicine Name
  • Quantity
  • Selected Market
  • Unit Price
  • Item Price
  • Final Order Price

For a multi-item order, print every purchased item.

Information Result

Information queries should return matching medicines without creating an order.

For example:

type MedicineSearchResult struct {
    MarketID int
    Medicine Medicine
}

Important Search Distinction

Search availability and purchase availability are different concepts.

For information-only requests, a matching medicine may still be useful to display even if the query is not performing a purchase. For purchases, the market must contain enough boxes to satisfy the requested quantity.

Validation

Validate:

medicine purpose
effectiveness range 1..3
price >= 0
availability >= 0
purchase quantity > 0
market ID
medicine identity

Requirements

The implementation must:

  1. model medicine
  2. model medicine purpose
  3. model medicine markets
  4. load both supplied inventories
  5. support information-only queries
  6. support purchase queries
  7. filter by purpose
  8. filter by effectiveness
  9. filter by price
  10. support excluded medicine names
  11. support direct medicine-name purchases
  12. compare prices across markets
  13. select the best purchase solution
  14. verify stock
  15. update stock after purchase
  16. leave stock unchanged for information queries
  17. report medicine-not-found conditions
  18. report out-of-stock conditions
  19. support multi-item orders
  20. support configurable partial-order execution
  21. print completed order information
  22. calculate the final order price

Modeling Goal

The central distinction is between Search & Purchase.

A useful architecture is:

        MedicineMarket
              |
              v
        Search Engine
              |
              +-------------------+
              |                   |
              v                   v
        Information Query     Purchase Request
                                  |
                                  v
                            Order Processor
                                  |
                                  v
                            Inventory Update

Searching should not directly mutate inventory.

Inventory changes belong to successful purchase processing.

Task 6 — Hardware Store Search Engine

Objective

Create a hardware-store marketplace and search engine.

The system contains three markets.

Each market contains hardware components divided into sectors.

Markets may contain the same component with:

  • different price
  • different stock availability

The application must load the complete hardware database from a file, parse each component into the correct model, and provide search, comparison, purchase, configuration, and stock-update operations.

Hardware Sectors

The supported sectors are:

  • CPU
  • Motherboard
  • GPU
  • RAM
  • PowerSupply
  • PcCase
  • Cooler
  • SSD

Each sector has its own component attributes.

Do not model every hardware component as one large structure containing unrelated optional fields.

Each component type should preserve its own domain-specific attributes.

Common Market Information

Every sellable item contains at minimum:

  • Component ID
  • Price
  • Stock
  • Market ID

These values can be represented using a shared model or interface when useful.

CPU

CPU data contains:

  • ID
  • Maker
  • Chip Socket
  • Generation
  • Chip Model
  • Chip Version
  • Core Count
  • Base Frequency
  • Turbo Frequency
  • Price
  • Stock

A possible model is:

type CPU struct {
    ID             int
    Maker          string
    ChipSocket     string
    Generation     int
    ChipModel      string
    ChipVersion    string
    Cores          int
    BaseFrequency  float64
    TurboFrequency float64
    Price          float64
    Stock          int
}

Examples from the supplied database include:

Intel i5 11600
Intel i7 12700K
Intel i9 12900K
AMD Ryzen 7 5700X
AMD Ryzen 9 5950X
AMD Ryzen 9 7950X

Motherboard

Motherboard data contains:

  • ID
  • Maker
  • Processor Platform
  • Chip Socket
  • Model
  • RAM Version
  • M.2 Slot Count
  • Price
  • Stock

A possible model is:

type Motherboard struct {
    ID        int
    Maker     string
    Processor string
    ChipSocket string
    Model      string
    RAMVersion string
    M2Slots    int
    Price      float64
    Stock      int
}

GPU

GPU data contains:

  • ID
  • Manufacturer
  • Maker
  • Model
  • RAM
  • Price
  • Stock

A possible model is:

type GPU struct {
    ID           int
    Manufacturer string
    Maker        string
    Model        string
    RAM          int
    Price        float64
    Stock        int
}

RAM

RAM data contains:

  • ID
  • Manufacturer
  • Model
  • RAM Version
  • Capacity
  • Frequency
  • CL
  • Price
  • Stock

For example:

type RAM struct {
    ID           int
    Manufacturer string
    Model        string
    RAMVersion   string
    CapacityGB   int
    FrequencyMHz int
    CL           int
    Price        float64
    Stock        int
}

Power Supply

Power-supply data contains:

  • ID
  • Manufacturer
  • Model
  • Capacity
  • Price
  • Stock

For example:

type PowerSupply struct {
    ID           int
    Manufacturer string
    Model        string
    CapacityW    int
    Price        float64
    Stock        int
}

PC Case

PC-case data contains:

  • ID
  • Manufacturer
  • Model
  • Front Panel Support
  • Top Panel Support
  • Price
  • Stock

A possible model is:

type PcCase struct {
    ID           int
    Manufacturer string
    Model        string
    FrontPanel   string
    TopPanel     string
    Price        float64
    Stock        int
}

Cooler

Cooler data contains:

  • ID
  • Manufacturer
  • Model
  • Size
  • Socket Support
  • Price
  • Stock

A possible model is:

type Cooler struct {
    ID            int
    Manufacturer  string
    Model         string
    Size          string
    SocketSupport []string
    Price         float64
    Stock         int
}

The raw source uses values such as:

1200-AM4
1200-1700-AM4
1200-1700-AM4-AM5

These may be parsed into a collection of supported sockets.

SSD

SSD data contains:

  • ID
  • Manufacturer
  • Model
  • Size
  • PCI Support
  • Price
  • Stock

A possible model is:

type SSD struct {
    ID           int
    Manufacturer string
    Model        string
    Size         string
    PCISupport   float64
    Price        float64
    Stock        int
}

Source Data

The original task provides complete component tables for three markets.

The supplied database must be copied into a single input file.

The application must then:

  1. read the file
  2. detect market sections
  3. detect component sectors
  4. parse each row
  5. create the correct component structure
  6. preserve market-specific price and stock

The same component ID may exist in multiple markets.

Therefore:

Component ID alone is not a globally unique inventory key.

Inventory identity should include at least:

  • Market ID
  • Component Type
  • Component ID

Market Model

A possible structure is:

type HardwareMarket struct {
    ID           int
    CPUs         []CPU
    Motherboards []Motherboard
    GPUs         []GPU
    RAM          []RAM
    PowerSupplies []PowerSupply
    Cases        []PcCase
    Coolers      []Cooler
    SSDs         []SSD
}

Other designs are valid as long as component-specific attributes remain strongly modeled.

Search Engine

Create a search engine capable of:

  • searching
  • comparing
  • suggesting
  • purchasing
  • building complete configurations
  • updating stock

A useful conceptual separation is:

        Catalog
           |
        Search Engine
           |
           +-- Search
           +-- Compare
           +-- Suggest
           +-- Purchase
           +-- Build Configuration

Stock mutation should happen only during purchase or delivery operations.

Required Queries

Query 1 — Alex

Alex wants to buy a motherboard with:

Socket = 1700
Price >= 300
Price <= 400

The result must consider all markets and only available components.

Query 2 — Ben

Ben wants information about the best price for:

CPU Maker = Intel
Generation = 12
Cores = 12

This is an information query.

It should locate matching CPUs and identify the best available price.

Query 3 — Cane

Cane wants information about the difference between:

Intel
Generation = 12
Cores = 16

and:

AMD
Series = 5000
Cores = 16

The comparison should return the relevant component data rather than only a boolean result.

Useful comparison fields include the attributes present in the CPU models, such as:

  • model
  • generation
  • socket
  • cores
  • base frequency
  • turbo frequency
  • price
  • stock

Query 4 — Diana

Diana wants information about AMD 7000-series CPUs with:

8 cores
12 cores
16 cores

The search should return matching processors for all requested core counts.

Query 5 — David

David wants to buy:

Intel 12900K
AMD 5950X
AMD 7950X

Each component must be located in available market stock.

A successful purchase must decrease the corresponding market inventory.

Query 6 — Elena

Elena wants information about all motherboards where:

Model contains Z690
Price >= 400
Price <= 450

This is an information query.

No stock should be modified.

Query 7 — Fred

Fred wants to buy:

Motherboard platform = AMD
Socket = AM4
Quantity = 2
Selection = cheapest valid motherboard

The selected market must contain at least two units.

The search must compare prices across markets before purchasing.

Query 8 — Gina

Gina wants to buy the most expensive ASUS motherboard satisfying:

Socket = AM5
RAM Version = DDR5
M.2 Slots = 4
Manufacturer = ASUS
Selection = most expensive

The component must be in stock.

Query 9 — Helen

Helen wants information about all motherboards satisfying:

Processor = AMD
RAM Version = DDR4
M.2 Slots = 2
Price >= 200
Price <= 250

This is an information query.

Query 10 — Kyle

Kyle wants to buy the most expensive motherboard compatible with:

Intel i9 12900K

The implementation must first derive the CPU compatibility requirements and then find a compatible motherboard.

At minimum, the motherboard socket must match the CPU socket.

The source data provides:

Intel i9 12900K
Socket = 1700

The query must not simply search for a hard-coded motherboard ID.

Query 11 — Paul

Paul wants to buy two coolers for an:

NZXT H7 PC case

Requirements:

Mount position = top panel
CPU support = Intel i9 12900K
Price >= 220
Price <= 250
Quantity = 2

This requires combining data from:

PcCase
CPU
Cooler

The selected cooler must:

  1. fit the case’s top-panel support
  2. support the CPU socket
  3. satisfy the price range
  4. have sufficient stock

Query 12 — Lara

Lara wants to buy RAM for two configurations.

DDR4 Requirement

4 RAM slots
DDR4
3600 MHz
CL-16

DDR5 Requirement

4 RAM slots
DDR5
6400 MHz
CL-32

The source wording uses:

4 RAM slots

while the RAM records themselves describe module or kit capacity rather than motherboard slot count.

The implementation should not silently reinterpret this requirement.

A reasonable interpretation is that Lara requires four RAM units or modules matching each specification, but this interpretation must be documented if used.

Query 13 — Tina

Tina wants:

KINGSTON 500GB M.2 SSD — quantity 2
SAMSUNG 500GB M.2 SSD  — quantity 2

Behavior:

if both are available:
    buy both

if only one type is available:
    buy the available type anyway

This is an explicit partial-purchase case.

Query 14 — Victor

Victor wants a complete PC configuration.

Requirements:

CPU
AMD 7950X

Motherboard
most expensive motherboard compatible with the CPU

GPU
AMD RX6600 XT
8 GB RAM

RAM
DDR5
6400 MHz
CL-32

Power Supply
CHIEFTEC
850 W
Price between 150 and 200

PC Case
NZXT
Front panel supports 3x120mm
Top panel supports 3x120mm
Price between 200 and 250

Cooler
CORSAIR
supports AMD 7950X
3x120mm

SSD
three M.2 SSDs
1 TB each
PCI 4.0

The search engine must resolve the complete configuration using market inventory.

Configuration Compatibility

For Victor’s configuration, the implementation must check compatibility where the supplied data supports it.

Examples include:

CPU socket <-> motherboard socket
CPU socket <-> cooler socket support
Motherboard RAM version <-> RAM version

Do not invent compatibility properties that do not exist in the source data.

For example, the provided case data does not define GPU length, so GPU-case length compatibility cannot be validated from the supplied dataset.

Multi-Market Configuration

The source does not state that the complete PC configuration must come from one market.

Therefore the implementation must document whether:

all components must come from one market

or:

each component may be selected from the best available market

Do not silently assume one interpretation.

Delivery Processing

Simon represents delivery support.

Delivery operations add stock to existing market items.

A delivery contains:

  • Market ID
  • Item Type
  • Item ID
  • New Delivery Quantity

A possible model is:

type Delivery struct {
    MarketID int
    ItemType string
    ItemID   int
    Quantity int
}

Market 1 Deliveries

Apply:

CPU|10|2
CPU|20|3

Motherboard|8|3
Motherboard|16|2
Motherboard|21|1

GPU|5|3
GPU|7|2
GPU|10|2

RAM|8|8
RAM|10|8
RAM|12|8

PowerSupply|7|4
PowerSupply|9|6

PcCase|4|6
PcCase|6|4

Cooler|5|3
Cooler|7|3

SSD|5|4
SSD|9|4

Market 2 Deliveries

Apply:

CPU|9|2
CPU|18|3

Motherboard|7|3
Motherboard|15|2
Motherboard|20|1

GPU|4|3
GPU|6|2
GPU|9|2

RAM|7|8
RAM|9|8
RAM|11|8

PowerSupply|8|6
PowerSupply|10|4

PcCase|3|6
PcCase|5|4

Cooler|4|3
Cooler|6|3

SSD|6|4
SSD|8|4

Delivery Update

For:

Current Stock = S
Delivery = D

calculate:

New Stock = S + D

The update must target exactly:

Market ID
Component Type
Component ID

For example:

Market 1
CPU
ID 10
+2

must not update CPU ID 10 in Market 2 or Market 3.

Unknown Delivery Item

If a delivery references an item that does not exist in the specified market, report an error.

Do not silently create a new hardware model from incomplete delivery data.

Purchase Processing

A successful purchase must decrease stock.

For:

Current Stock = S
Purchased Quantity = Q

calculate:

New Stock = S - Q

only when:

S >= Q

Stock must never become negative.

Information Queries

Queries asking only for information must not change stock.

Examples include:

Ben
Cane
Diana
Elena
Helen

Purchase Queries

Purchase-oriented queries include:

Alex
David
Fred
Gina
Kyle
Paul
Lara
Tina
Victor

where applicable according to the specific request.

Search Result

A generic result should preserve both the hardware component and the market where it was found.

For example:

type MarketItem[T any] struct {
    MarketID int
    Item     T
}

or an equivalent language-specific design.

Purchase Result

A purchase result may contain:

type PurchaseResult struct {
    Buyer      string
    MarketID   int
    ItemType   string
    ItemID     int
    Quantity   int
    UnitPrice  float64
    FinalPrice float64
    RemainingStock int
}

Multi-component purchases should return multiple item results.

Validation

The parser and processing layer should validate:

known market IDs
known component types
valid component IDs
price >= 0
stock >= 0
purchase quantity > 0
delivery quantity > 0
valid numeric fields
required model attributes

Source Data Notes

The supplied hardware database contains several values worth handling carefully.

For example, a GPU entry in Market 2 contains:

Price = 0

and another GPU entry in Market 3 also contains:

Price = 0

The source does not explain whether zero means:

free
missing price
invalid data

The implementation should not silently reinterpret zero.

It should either:

  • preserve the source value and define how search handles it
  • or report it as invalid market data

The chosen behavior must be documented.

Required Tests

Create at least one test for every query type executed by the search engine.

This requirement applies to all supported query forms.

Examples include tests for:

price-range search
cheapest selection
most-expensive selection
component comparison
direct purchase
multi-item purchase
partial purchase
compatibility search
complete configuration search
delivery update

A single test should verify one clearly defined processing behavior.

Requirements

The implementation must:

  1. model every hardware sector
  2. preserve component-specific attributes
  3. load the complete provided database from a file
  4. parse all three markets
  5. preserve market-specific price and stock
  6. search across markets
  7. support information queries
  8. support purchases
  9. support price-range filtering
  10. support cheapest-item selection
  11. support most-expensive-item selection
  12. support component comparison
  13. support compatibility-based searches
  14. support direct multi-item purchase
  15. support partial purchases where explicitly requested
  16. build a complete PC configuration
  17. update stock after purchases
  18. process delivery updates
  19. prevent negative inventory
  20. report missing components
  21. keep information queries free of side effects
  22. create at least one test for every executed query type

Modeling Goal

This task should not become one massive function.

A useful conceptual architecture is:

        Raw Hardware Database
                |
                v
              Parser
                |
                v
        Hardware Markets
                |
                +-----------------------+
                |                       |
                v                       v
          Search Engine          Inventory Service
                |                       |
                +-- Find                +-- Purchase
                +-- Filter              +-- Delivery
                +-- Compare             +-- Stock Update
                +-- Suggest
                +-- Compatibility
                |
                v
        Configuration Builder

The key challenge is not only filtering lists.

The implementation must model several different hardware domains, preserve independent market inventory, and combine search results to answer higher-level requests.

Task 7 — Infrastructure Resource Orchestrator

Objective

Create an infrastructure resource orchestration system.

The system manages physical infrastructure and deploys application services across available compute resources.

The infrastructure contains:

  • regions
  • data centers
  • racks
  • servers
  • server resources
  • tenants
  • services
  • service replicas
  • deployments
  • resource quotas
  • placement rules
  • health checks
  • maintenance windows
  • failure events
  • replica migrations

The implementation must determine where service replicas may run, whether deployment requirements can be satisfied, and how the system should react when infrastructure becomes unavailable.

The task focuses on modeling resource allocation, placement constraints, service health, failure handling, and state changes inside a distributed infrastructure system.

Domain Overview

Conceptually:

        Region
          |
          +-- DataCenter
                |
                +-- Rack
                      |
                      +-- Server
                            |
                            +-- CPU Capacity
                            +-- Memory Capacity
                            +-- Storage Capacity
                            +-- Network Capacity
                            |
                            +-- Service Replicas

Application-side relationships:

        Tenant
          |
          +-- ResourceQuota
          |
          +-- Service
                |
                +-- Deployment
                      |
                      +-- ServiceReplica
                      +-- PlacementRules
                      +-- HealthChecks

Operational relationships:

        Server
          |
          +-- MaintenanceWindow
          +-- FailureEvent
        
        ServiceReplica
          |
          +-- Placement
          +-- Health
          +-- MigrationHistory

Region

A region represents a large infrastructure location.

For example:

type Region struct {
    ID   string
    Name string
}

Example regions:

eu-west
eu-central
us-east

Data Center

A data center belongs to one region. For example:

type DataCenter struct {
    ID       string
    Name     string
    RegionID string
}

Each data center may contain multiple racks.

Rack

A rack belongs to one data center. For example:

type Rack struct {
    ID           string
    DataCenterID string
}

Each rack may contain multiple servers.

Server

Each server contains:

  • ID
  • Rack ID
  • CPU Capacity
  • Memory Capacity
  • Storage Capacity
  • Network Capacity
  • Current Status

A possible model is:

type Server struct {
    ID            string
    RackID        string
    CPUCores      int
    MemoryGB      int
    StorageGB     int
    NetworkMbps   int
    Status        ServerStatus
}

Server Status

Supported server states may include:

  • available
  • maintenance
  • failed
  • disabled

A typed representation is recommended. For example:

type ServerStatus string

A server must not receive new workloads when its status is:

  • maintenance
  • failed
  • disabled

Resource Capacity

The implementation must track both:

  • total resources
  • allocated resources

For each server. A useful model is:

type ResourceCapacity struct {
    CPUCores    int
    MemoryGB    int
    StorageGB   int
    NetworkMbps int
}

The available capacity is:

available = total - allocated

The system must never allocate more resources than the server physically provides.

Tenant

A tenant represents an owner of one or more services.

For example:

type Tenant struct {
    ID   string
    Name string
}

Each tenant has resource limits.

Resource Quota

A tenant may have limits such as:

  • maximum CPU cores
  • maximum memory
  • maximum storage
  • maximum replicas

For example:

type ResourceQuota struct {
    TenantID      string
    MaxCPUCores   int
    MaxMemoryGB   int
    MaxStorageGB  int
    MaxReplicas   int
}

The total resource consumption of all active tenant replicas must not exceed the configured quota.

Service

A service represents an application component.

For example:

type Service struct {
    ID       string
    TenantID string
    Name     string
}

Examples:

service-api
service-worker
service-auth
service-db-proxy

Deployment

A deployment defines how a service should run. A possible model is:

type Deployment struct {
    ID               string
    ServiceID        string
    DesiredReplicas  int
    Resources        ReplicaResources
    PlacementRules   PlacementRules
}

Replica Resources

Each replica requests resources. For example:

type ReplicaResources struct {
    CPUCores    int
    MemoryGB    int
    StorageGB   int
    NetworkMbps int
}

Example:

CPU = 4 cores
Memory = 8 GB
Storage = 50 GB
Network = 200 Mbps

Every replica of the deployment requires these resources.

Service Replica

A service replica represents one running instance. For example:

type ServiceReplica struct {
    ID           string
    DeploymentID string
    ServerID     string
    Role         ReplicaRole
    Status       ReplicaStatus
}

Possible roles:

  • primary
  • backup
  • regular

Possible replica states:

  • pending
  • running
  • unhealthy
  • migrating
  • stopped
  • failed

Placement Rules

Deployments may define placement constraints. A possible model is:

type PlacementRules struct {
    MinimumDataCenters       int
    MaximumReplicasPerServer int
    SeparatePrimaryAndBackup bool
    SeparateReplicasByRack   bool
}

Additional restrictions may also be modeled where explicitly required.

Base Deployment Scenario

Create the following deployment:

Deployment ID:
deployment-api

Service:
service-api

Desired Replicas:
6

Each replica requires:

CPU = 4
Memory = 8 GB
Storage = 50 GB
Network = 200 Mbps

Placement rules:

  • minimum 2 data centers
  • maximum 2 replicas per server
  • primary and backup replicas cannot share the same server
  • servers under maintenance cannot receive workloads
  • failed servers cannot receive workloads

The implementation must determine whether all six replicas can be placed.

Deterministic Placement

When several servers are equally valid, placement should be deterministic.

A reasonable rule is:

  • DataCenter ID ascending
  • Rack ID ascending
  • Server ID ascending

The chosen rule must be documented.

The system should not produce random placement for identical input.

Placement Validation

A candidate server is valid only when all required conditions are satisfied.

At minimum:

  • server status = available
  • enough free CPU
  • enough free memory
  • enough free storage
  • enough free network capacity
  • maximum replicas per server not exceeded
  • placement rules remain valid
  • tenant quota remains valid

Placement Result

A possible result model is:

type ReplicaPlacement struct {
    ReplicaID    string
    ServerID     string
    RackID       string
    DataCenterID string
}

type DeploymentPlacementResult struct {
    DeploymentID string
    Successful   bool
    Placements   []ReplicaPlacement
    Errors       []string
}

If the complete deployment cannot be satisfied, the implementation must clearly report why.

Atomic Deployment Behavior

The implementation must define deployment behavior when only part of the requested replicas can be placed.

For the base task, use atomic deployment behavior:

  • either all requested replicas can be placed
  • or no new replicas are committed

This avoids leaving a partially created deployment without explicit intent.

An optional extension may support partial deployment.

Resource Reservation

When a placement succeeds, server resources must be reserved.

For each replica:

AllocatedCPU += replica.CPU
AllocatedMemory += replica.Memory
AllocatedStorage += replica.Storage
AllocatedNetwork += replica.Network

Resources must be released when a replica is permanently removed from the server.

Maintenance Window

A server may enter scheduled maintenance.

A possible model is:

type MaintenanceWindow struct {
    ID        string
    ServerID  string
    StartTime time.Time
    EndTime   time.Time
    Reason    string
}

During an active maintenance window:

new replicas cannot be placed on the server

The implementation should also determine whether already-running replicas must be migrated before maintenance begins.

For this task:

running replicas must be evacuated before planned maintenance

Failure Event

A server may fail unexpectedly.

A possible model is:

type FailureEvent struct {
    ID        string
    ServerID  string
    Timestamp time.Time
    Reason    string
}

When a server fails:

Server.Status = failed

Every running replica on that server becomes unavailable.

Failure Scenario

Assume:

server-17

fails.

The implementation must determine:

  • which replicas were running on server-17
  • which deployments are affected
  • which services are degraded
  • whether desired replica count is still satisfied
  • whether placement rules are still satisfied

Service Health

A service may have one of the following health states:

  • healthy
  • degraded
  • unavailable

A possible rule set:

        healthy:
            all desired replicas are running
        
        degraded:
            at least one replica is running,
            but fewer than desired replicas are available
        
        unavailable:
            no replicas are running

If primary/backup rules are used, role-specific availability must also be considered.

Replica Recovery

After a server failure, the orchestrator should attempt to restore the deployment to its desired state.

Conceptually:

  • determine missing replicas
  • find valid replacement servers
  • reserve resources
  • create replacement placements
  • restore desired replica count

Recovery must still respect all original placement rules.

Migration

A replica migration moves a workload from one server to another. A possible model is:

type ReplicaMigration struct {
    ID             string
    ReplicaID      string
    SourceServerID string
    TargetServerID string
    Reason         string
    Status         MigrationStatus
}

Possible reasons:

  • server failure
  • planned maintenance
  • manual relocation
  • capacity rebalance

Migration Rules

The target server must satisfy the same requirements as a new placement.

Resources should not be released from the source location before the migration behavior is safely determined.

The exact migration execution strategy may be simplified, but the final state must remain consistent.

Health Check

Services may define health checks. For example:

type HealthCheck struct {
    ID           string
    ReplicaID    string
    Passed       bool
    CheckedAt    time.Time
    FailureCount int
}

A replica may become unhealthy without its physical server being failed.

The orchestrator must distinguish server failure from application health failure.

Unhealthy Replica Scenario

If one replica repeatedly fails health checks:

Replica.Status = unhealthy

The implementation should determine whether it still counts toward service availability. For this task:

only replicas with Status = running and healthy count as available

Tenant Quota Validation

Before creating a deployment, calculate the tenant’s projected total resource usage. For example:

        Current Tenant Usage:
            CPU = 20
            Memory = 40 GB
        
        New Deployment:
            6 replicas
            4 CPU each
            8 GB each

Projected additional usage:

CPU = 24
Memory = 48 GB

The deployment must be rejected when the projected result exceeds tenant quota.

Placement Rule Scenario

Create an additional deployment:

        Service:
            service-auth
        
        Replicas:
            4

Rules:

  • minimum 2 data centers
  • maximum 1 replica per server
  • separate replicas by rack

The implementation must verify that:

  • no two replicas share the same server
  • no two replicas share the same rack
  • at least two data centers are used

Capacity Failure Scenario

Create a request where enough total infrastructure capacity exists globally, but placement constraints prevent a valid deployment.

For example:

  • 4 replicas requested
  • enough CPU and memory exist
  • only 2 valid racks exist
  • rule requires 4 distinct racks

The result must report:

placement constraint failure

rather than incorrectly reporting insufficient CPU or memory.

Infrastructure Snapshot

A useful implementation may provide an infrastructure snapshot. For example:

type InfrastructureSnapshot struct {
    Regions      []Region
    DataCenters  []DataCenter
    Racks        []Rack
    Servers      []Server
    Replicas     []ServiceReplica
    Deployments  []Deployment
}

The snapshot may be used for reporting or testing.

Audit History

Every significant state-changing action should produce an audit event. Examples:

deployment created
replica placed
replica migrated
server entered maintenance
server failed
replica became unhealthy
recovery started
recovery completed

A possible model:

type AuditEvent struct {
    ID         string
    Timestamp  time.Time
    EntityType string
    EntityID   string
    Action     string
    Details    string
}

The audit log should preserve history instead of only storing final state.

Idempotent Deployment Request

Deployment creation requests should contain a request ID. For example:

type DeploymentRequest struct {
    RequestID    string
    Deployment   Deployment
}

If the same RequestID is processed twice, the system must not allocate the same deployment twice.

The second request should return the previously known result or report that the request was already processed.

Validation

The implementation should validate:

  • duplicate entity IDs
  • unknown RegionID
  • unknown DataCenterID
  • unknown RackID
  • unknown ServerID
  • unknown TenantID
  • unknown ServiceID
  • invalid resource capacity
  • negative resource values
  • desired replicas <= 0
  • invalid placement rules
  • tenant quota violations
  • maintenance time ranges
  • duplicate request IDs

Resource values must not be negative.

Required Operations

The system must support:

  1. infrastructure creation
  2. tenant creation
  3. quota configuration
  4. service creation
  5. deployment creation
  6. replica placement
  7. placement validation
  8. resource reservation
  9. service health calculation
  10. server maintenance
  11. server failure handling
  12. unhealthy replica handling
  13. replica recovery
  14. replica migration
  15. audit history
  16. idempotent deployment requests

Required Test Scenarios

Create tests for at least:

  • successful deployment
  • insufficient server capacity
  • tenant quota exceeded
  • maintenance server excluded
  • failed server excluded
  • minimum data-center rule
  • maximum replicas per server
  • rack separation rule
  • server failure
  • successful replica recovery
  • failed replica recovery
  • health-check failure
  • migration
  • duplicate RequestID
  • resource release

Modeling Goal

The purpose of this task is not to recreate Kubernetes or another existing orchestration platform.

The goal is to model the fundamental relationships and rules involved in infrastructure orchestration.

A useful conceptual architecture is:

        Infrastructure Registry
                |
                +-- Regions
                +-- Data Centers
                +-- Racks
                +-- Servers
                |
                v
        Capacity Manager
                |
                v
        Placement Engine
                |
                +-- Resource Rules
                +-- Tenant Quotas
                +-- Placement Rules
                |
                v
        Replica Manager
                |
                +-- Deployment
                +-- Migration
                +-- Recovery
                |
                v
        Health Manager
                |
                +-- Server Failure
                +-- Health Checks
                |
                v
        Audit History

The important challenge is keeping infrastructure state, application state, resource allocation, and placement constraints consistent while the system changes over time.

Task 8 — Logistics and Shipment Processing Network

Objective

Create a logistics and shipment-processing network.

The system manages the complete lifecycle of packages from warehouse intake to final delivery.

The network contains:

  • customers
  • addresses
  • regions
  • warehouses
  • warehouse inventory
  • packages
  • shipments
  • shipment items
  • vehicles
  • drivers
  • routes
  • route stops
  • delivery assignments
  • tracking events
  • delivery attempts
  • returns
  • damaged or lost packages

The implementation must model package ownership, shipment state, warehouse location, vehicle capacity, driver eligibility, delivery routing, tracking history, failed delivery attempts, and returns.

The system must preserve both:

current shipment state

and:

complete shipment history

Domain Overview

Conceptually:

        Customer
           |
           +-- Address
           |
           +-- Shipment
                  |
                  +-- Package
                  |
                  +-- Tracking Events
                  |
                  +-- Delivery Attempts
                  |
                  +-- Return Shipment

Infrastructure:

        Region
           |
           +-- Warehouse
           |
           +-- Vehicle
           |
           +-- Driver
           |
           +-- Route

Operational flow:

        Shipment
           |
           v
        Warehouse
           |
           v
        Assignment
           |
           +-- Vehicle
           +-- Driver
           +-- Route
           |
           v
        Delivery

Customer

A customer contains:

  • ID
  • Name
  • Phone
  • Email

For example:

type Customer struct {
    ID    string
    Name  string
    Phone string
    Email string
}

Address

An address belongs to a region. A possible model is:

type Address struct {
    ID         string
    CustomerID string
    Street     string
    City       string
    PostalCode string
    RegionID   string
}

The system must validate that shipment destinations reference valid addresses.

Region

A region defines a delivery area. For example:

type Region struct {
    ID   string
    Name string
}

Examples:

  • region-1
  • region-2
  • region-3

Warehouse

A warehouse belongs to one region.

For example:

type Warehouse struct {
    ID       string
    RegionID string
    Name     string
}

A warehouse may contain many packages waiting for processing.

Package

Each package contains:

ID
Weight
Volume
Type
Current Warehouse
Status

A possible model is:

type Package struct {
    ID                string
    WeightKg          float64
    VolumeM3          float64
    PackageType       PackageType
    CurrentWarehouseID string
    Status            PackageStatus
}

Package Type

Possible package types may include:

standard
fragile
refrigerated
hazardous
oversized

Vehicle and driver restrictions may depend on package type.

Shipment

A shipment belongs to a customer and contains one or more packages. For example:

type Shipment struct {
    ID             string
    CustomerID     string
    DestinationID  string
    Priority       ShipmentPriority
    Status         ShipmentStatus
    Deadline       time.Time
    PackageIDs     []string
}

Shipment Priority

Supported priorities:

standard
express
critical

Higher-priority shipments should be considered first when several shipments compete for limited delivery capacity.

The exact tie-breaking rule must be deterministic. A reasonable order is:

  • critical
  • express
  • standard

Then:

earlier deadline

Then:

Shipment ID ascending

Shipment Status

Use a controlled state model.

Possible shipment states:

  • created
  • reserved
  • packed
  • ready_for_dispatch
  • dispatched
  • in_transit
  • out_for_delivery
  • delivered
  • delivery_failed
  • returning
  • returned
  • cancelled
  • lost

Not every transition is valid.

Shipment State Transitions

A possible allowed transition graph is:

        created
           |
           v
        reserved
           |
           v
        packed
           |
           v
        ready_for_dispatch
           |
           v
        dispatched
           |
           v
        in_transit
           |
           v
        out_for_delivery
           |
           +-------> delivered
           |
           +-------> delivery_failed
                          |
                          +-------> out_for_delivery
                          |
                          +-------> returning
                                         |
                                         v
                                      returned

Alternative transitions:

        created -> cancelled
        reserved -> cancelled
        packed -> cancelled
        in_transit -> lost
        out_for_delivery -> lost

The implementation must reject invalid state transitions.

Vehicle

Each vehicle contains:

  • ID
  • Vehicle Type
  • Maximum Weight
  • Maximum Volume
  • Allowed Regions
  • Supported Package Types
  • Status

A possible model is:

type Vehicle struct {
    ID                    string
    Type                  VehicleType
    MaxWeightKg           float64
    MaxVolumeM3           float64
    AllowedRegionIDs      []string
    SupportedPackageTypes []PackageType
    Status                VehicleStatus
}

Vehicle Status

Possible states:

  • available
  • assigned
  • maintenance
  • disabled

Only available vehicles may receive new delivery assignments.

Driver

A driver contains:

  • ID
  • Name
  • Licenses
  • Allowed Vehicle Types
  • Working Hours
  • Current Status

For example:

type Driver struct {
    ID                  string
    Name                string
    Licenses            []string
    AllowedVehicleTypes []VehicleType
    ShiftStart          time.Time
    ShiftEnd            time.Time
    Status              DriverStatus
}

Driver Status

Possible states:

  • available
  • assigned
  • off_shift
  • suspended

A driver may only be assigned when:

  • status = available
  • current time is within shift
  • driver is eligible for vehicle type

Route

A route defines a delivery sequence. For example:

type Route struct {
    ID        string
    RegionID  string
    Stops     []RouteStop
}

Route Stop

Each route stop may contain:

type RouteStop struct {
    Sequence  int
    AddressID string
}

The route model does not need to solve real-world GPS navigation.

The task focuses on shipment assignment and ordered stops.

Delivery Assignment

A delivery assignment connects:

  • vehicle
  • driver
  • route
  • shipments

For example:

type DeliveryAssignment struct {
    ID          string
    VehicleID   string
    DriverID    string
    RouteID     string
    ShipmentIDs []string
    Status      AssignmentStatus
}

Possible assignment states:

  • created
  • active
  • completed
  • cancelled

Capacity Validation

For all shipments assigned to one vehicle:

        total weight <= vehicle maximum weight

and:

        total volume <= vehicle maximum volume

The implementation must calculate capacity using every package contained in the assigned shipments.

Region Validation

A vehicle may only deliver shipments to regions listed in:

AllowedRegionIDs

A driver must also be allowed to operate the selected vehicle.

Package-Type Validation

A vehicle may restrict package types.

For example:

        standard truck:
            standard
            fragile
            oversized
        
        refrigerated van:
            standard
            refrigerated
        
        hazmat truck:
            standard
            hazardous

A shipment containing unsupported package types cannot be assigned to that vehicle.

Base Assignment Scenario

Create:

        Shipment: shipment-1001
        Priority: express
        Destination Region: region-2
        Deadline: 16:00

Packages:

        package-1:
            weight = 30 kg
            volume = 0.30 m3
            type = standard
        
        package-2:
            weight = 45 kg
            volume = 0.45 m3
            type = fragile

Total:

        weight = 75 kg
        volume = 0.75 m3

Vehicle:

        vehicle-1
        
        maximum weight = 1000 kg
        maximum volume = 12 m3
        allowed regions = region-1, region-2, region-3
        supported package types = standard, fragile
        status = available

Driver:

        driver-1
        
        licenses = B, C
        allowed vehicle type = truck
        shift = 08:00 - 18:00
        status = available

The system must determine whether:

shipment-1001

can be assigned to:

vehicle-1
driver-1

Warehouse Ownership

Before dispatch, every package must belong to a warehouse.

The shipment may only become:

ready_for_dispatch

when all of its packages are located at the correct dispatch warehouse.

If packages are split across multiple warehouses, the system must report that the shipment is not ready.

An optional extension may support package consolidation between warehouses.

Inventory Reservation

A warehouse should reserve packages for a shipment before packing.

A possible reservation model is:

type PackageReservation struct {
    ID          string
    ShipmentID  string
    PackageID   string
    WarehouseID string
    Status      ReservationStatus
}

Possible states:

  • reserved
  • released
  • consumed

A package must not be reserved for two active shipments at the same time.

Packing

When all shipment packages are reserved:

Shipment.Status = packed

after the packing operation succeeds.

The implementation should preserve package-to-shipment ownership.

Dispatch

When a valid delivery assignment becomes active:

Shipment.Status = dispatched

and then:

in_transit

The exact transition timing may be simplified, but transitions must remain valid.

Tracking Event

Every important shipment change must create a tracking event.

A possible model is:

type TrackingEvent struct {
    ID          string
    ShipmentID  string
    Timestamp   time.Time
    Type        TrackingEventType
    LocationID  string
    Description string
}

Example timeline:

        10:15 created
        10:22 reserved
        10:41 packed
        11:05 ready_for_dispatch
        11:20 dispatched
        12:03 in_transit
        14:42 out_for_delivery
        15:18 delivered

Tracking history must not be reconstructed only from final shipment status.

It must be stored explicitly.

Delivery Attempt

Each delivery attempt contains:

  • Attempt Number
  • Timestamp
  • Result
  • Reason

For example:

type DeliveryAttempt struct {
    ShipmentID    string
    AttemptNumber int
    Timestamp     time.Time
    Result        DeliveryAttemptResult
    Reason        string
}

Possible results:

  • delivered
  • failed

Possible failure reasons:

  • customer_not_available
  • invalid_address
  • customer_refused
  • vehicle_issue
  • package_damaged
  • other

Failed Delivery

When delivery fails:

Shipment.Status = delivery_failed

The shipment may then either:

be scheduled for another attempt

or:

enter return processing

Maximum Delivery Attempts

For the base task:

maximum delivery attempts = 3

After three failed attempts:

shipment must enter returning state

The implementation must not schedule a fourth normal delivery attempt.

Redelivery

If another attempt is allowed:

delivery_failed -> out_for_delivery

A new DeliveryAttempt must be created.

The previous failed attempts must remain in history.

Return Shipment

When a shipment can no longer be delivered, it must return to a warehouse.

A possible model is:

type ReturnShipment struct {
    ID              string
    OriginalShipmentID string
    TargetWarehouseID  string
    Reason          string
    Status          ReturnStatus
}

Possible return states:

  • created
  • in_transit
  • received

When the return reaches the warehouse:

Shipment.Status = returned

Lost Shipment

A shipment may become lost during transport.

For example:

in_transit -> lost

or:

out_for_delivery -> lost

A lost shipment is terminal for normal delivery processing.

It must not later transition directly to:

delivered

without an explicit recovery operation.

Damaged Package

A package may be marked damaged. A possible model:

type PackageIncident struct {
    PackageID   string
    ShipmentID  string
    Type        string
    Description string
    Timestamp   time.Time
}

If a damaged package prevents delivery, the shipment should enter a failure or return path according to the implemented rules.

Driver Shift Validation

A delivery assignment must fit inside the driver’s shift. For example:

        Driver shift ends at 18:00
        Estimated route completion = 19:15

The assignment should be rejected.

The task does not require advanced travel-time prediction. A route may provide:

EstimatedDuration

for validation.

Route Duration

A route may contain:

type Route struct {
    ID                string
    RegionID          string
    Stops             []RouteStop
    EstimatedDuration time.Duration
}

Driver shift validation may use this value.

Multiple Shipment Assignment

A vehicle may carry several shipments in one assignment. The system must validate the combined:

weight
volume
regions
package types
route compatibility

of all shipments.

Priority Processing

When several ready shipments exist and vehicle capacity is limited, process shipment priority in this order:

  • critical
  • express
  • standard

Within the same priority:

earlier deadline first

Then:

Shipment ID ascending

This guarantees deterministic selection.

Warehouse Capacity

An optional but recommended model may track warehouse storage capacity. For example:

type WarehouseCapacity struct {
    WarehouseID     string
    MaximumPackages int
    CurrentPackages int
}

A return operation should not silently exceed warehouse capacity.

If this extension is implemented, overflow must be reported.

Cancellation

A shipment may be cancelled only before dispatch. Allowed:

        created -> cancelled
        reserved -> cancelled
        packed -> cancelled
        ready_for_dispatch -> cancelled

Not allowed:

        in_transit -> cancelled
        delivered -> cancelled
        returned -> cancelled

When a reserved shipment is cancelled, package reservations must be released.

Idempotent Shipment Creation

Shipment creation requests should contain:

RequestID

Processing the same creation request twice must not create duplicate shipments.

A possible model:

type ShipmentCreateRequest struct {
    RequestID string
    Shipment  Shipment
}

Idempotent Delivery Confirmation

Delivery confirmation should also be idempotent.

If the same delivery confirmation event is received twice:

stock/state/history must not be mutated twice

The second processing attempt should return the already known result.

Audit History

In addition to customer-facing tracking events, the system should maintain internal audit history.

Examples:

  • shipment created
  • package reserved
  • shipment packed
  • assignment created
  • vehicle assigned
  • driver assigned
  • delivery attempt failed
  • shipment marked lost
  • return created
  • return received

A possible model is:

type AuditEvent struct {
    ID         string
    Timestamp  time.Time
    EntityType string
    EntityID   string
    Action     string
    Details    string
}

Tracking events and audit events are related but not identical.

Tracking is customer-visible shipment history. Audit history records internal system actions.

Query Operations

The system should support queries such as:

  • get current shipment status
  • get complete tracking history
  • get all shipments for customer
  • get all packages in warehouse
  • get all shipments assigned to vehicle
  • get current driver assignment
  • get all failed delivery attempts
  • get all active returns

Queries must not mutate shipment state.

Command Operations

State-changing operations include:

  • create shipment
  • reserve packages
  • pack shipment
  • create delivery assignment
  • dispatch shipment
  • record tracking event
  • record delivery attempt
  • confirm delivery
  • schedule redelivery
  • start return
  • receive return
  • cancel shipment
  • mark shipment lost

Validation

The implementation should validate:

  • unknown customer
  • unknown address
  • unknown region
  • unknown warehouse
  • unknown package
  • unknown shipment
  • unknown vehicle
  • unknown driver
  • unknown route
  • duplicate IDs
  • duplicate RequestID
  • package already assigned
  • package already reserved
  • weight <= 0
  • volume <= 0
  • invalid deadline
  • invalid shipment transition
  • vehicle capacity exceeded
  • vehicle region unsupported
  • package type unsupported
  • driver license invalid
  • driver unavailable
  • driver shift violation
  • invalid delivery attempt number

Required Test Scenarios

Create tests for at least:

  • successful shipment creation
  • duplicate RequestID
  • successful reservation
  • double package reservation rejected
  • successful packing
  • vehicle weight capacity exceeded
  • vehicle volume capacity exceeded
  • unsupported region
  • unsupported package type
  • driver not eligible
  • driver outside shift
  • successful assignment
  • successful dispatch
  • successful delivery
  • first failed delivery attempt
  • redelivery after failure
  • third failed attempt triggers return
  • successful return
  • shipment cancellation
  • lost shipment
  • invalid state transition
  • duplicate delivery confirmation
  • tracking-history ordering
  • priority-based shipment selection

Modeling Goal

The goal of this task is to model a real stateful logistics process.

The important relationships are:

        Customer
           |
        Shipment
           |
           +-- Packages
           +-- Destination
           +-- Tracking
           +-- Delivery Attempts
           |
           v
        Warehouse
           |
           v
        Delivery Assignment
           |
           +-- Vehicle
           +-- Driver
           +-- Route
           |
           v
        Delivery / Failure / Return

A useful architecture is:

        Shipment Service
              |
              +-- Shipment Lifecycle
              +-- Package Ownership
              +-- Reservations
              |
              v
        Warehouse Service
              |
              +-- Package Location
              +-- Packing
              +-- Returns
              |
              v
        Dispatch Service
              |
              +-- Vehicle Selection
              +-- Driver Validation
              +-- Capacity Validation
              +-- Route Assignment
              |
              v
        Delivery Service
              |
              +-- Delivery Attempts
              +-- Redelivery
              +-- Return Processing
              |
              v
        Tracking Service
              |
              +-- Tracking Events
              +-- Customer History
              |
              v
        Audit History

The main challenge is maintaining consistency between current shipment state, package location, delivery assignments, vehicle capacity, driver eligibility, tracking history, and return processing.

Task 9 — Hotel and Reservation Network

Objective

Create a hotel and reservation management system for a network of hotels.

The system manages:

  • hotel properties
  • rooms
  • room types
  • guests
  • reservations
  • reservation rooms
  • availability
  • seasonal pricing
  • promotions
  • payments
  • check-in
  • check-out
  • hotel services
  • housekeeping
  • room maintenance
  • employees
  • invoices
  • cancellations
  • no-shows
  • occupancy
  • operational history

The implementation must manage the complete reservation lifecycle while ensuring that rooms are never assigned to conflicting reservations.

The system must preserve both:

commercial state

such as reservations, prices, payments, and invoices, and:

operational state

such as room occupancy, housekeeping, maintenance, and guest stays.

Hotel Network

Create a network containing at least five hotels.

For example:

        HT01 — London Central
        HT02 — Paris Riverside
        HT03 — Rome Plaza
        HT04 — Belgrade Grand
        HT05 — Dubai Marina

Hotels may have different:

  • room inventories
  • room types
  • prices
  • services
  • policies
  • employees
  • maintenance schedules

Hotel

Each hotel contains:

  • ID
  • Name
  • City
  • Country
  • Timezone
  • Check-In Time
  • Check-Out Time
  • Cancellation Policy

A possible model is:

type Hotel struct {
    ID                 string
    Name               string
    City               string
    Country            string
    Timezone           string
    CheckInTime        string
    CheckOutTime       string
    CancellationPolicyID string
}

Room Type

Rooms are classified by type. Examples:

  • single
  • double
  • twin
  • deluxe
  • suite
  • family

A room type contains:

  • ID
  • Name
  • Maximum Guests
  • Maximum Adults
  • Maximum Children
  • Base Price Per Night

For example:

type RoomType struct {
    ID              string
    Name            string
    MaxGuests       int
    MaxAdults       int
    MaxChildren     int
    BasePrice       float64
}

Room

Each room belongs to one hotel and one room type. A possible model is:

type Room struct {
    ID         string
    HotelID    string
    RoomTypeID string
    Number     string
    Floor      int
    Status     RoomStatus
}

Possible room states:

  • available
  • occupied
  • dirty
  • cleaning
  • maintenance
  • out_of_service

Reservation availability and operational room status are related but are not the same concept.

A room may have no reservation conflict and still be unavailable because it is under maintenance.

Guest

Each guest contains:

  • ID
  • First Name
  • Last Name
  • Email
  • Phone
  • Document Number
  • Nationality

For example:

type Guest struct {
    ID             string
    FirstName      string
    LastName       string
    Email          string
    Phone          string
    DocumentNumber string
    Nationality    string
}

Reservation

A reservation belongs to:

  • one hotel
  • one primary guest

and may contain one or more rooms. A possible model is:

type Reservation struct {
    ID             string
    HotelID        string
    PrimaryGuestID string
    CheckInDate    time.Time
    CheckOutDate   time.Time
    Status         ReservationStatus
    CreatedAt      time.Time
}

Possible states:

pending
confirmed
checked_in
checked_out
cancelled
no_show

Reservation Rooms

A reservation may contain multiple rooms.

Do not duplicate the entire reservation for every room. A possible relationship model is:

type ReservationRoom struct {
    ReservationID string
    RoomID        string
    Adults        int
    Children      int
    NightlyRates  []NightlyRate
}

Date Interval Semantics

Reservation intervals use:

[check-in, check-out)

semantics. For example:

        Reservation A: June 10 -> June 12
        Reservation B: June 12 -> June 15

do not overlap.

Reservation A occupies nights:

June 10
June 11

and releases the room for another guest on June 12.

Overlapping Reservation Detection

Two active reservations for the same room conflict when:

A.CheckIn < B.CheckOut
AND
B.CheckIn < A.CheckOut

Cancelled reservations do not block availability.

The system must prevent double booking.

The system must support searches using:

  • Hotel
  • Check-In Date
  • Check-Out Date
  • Adults
  • Children
  • Required Room Count
  • Optional Room Type
  • Maximum Price

The result should contain only rooms that:

  • have no conflicting reservation
  • are not under maintenance
  • are not out of service
  • satisfy guest capacity

A request may require several rooms. Example:

  • 4 adults
  • 2 children
  • 3 rooms

The system should find a valid room combination.

The implementation must not independently return three rooms that cannot jointly satisfy the request.

Pricing

Room price may depend on:

  • hotel
  • room type
  • date
  • season
  • promotion

The final reservation price must be calculated per night.

Nightly Rate

A useful model is:

type NightlyRate struct {
    Date      time.Time
    BasePrice float64
    Adjustment float64
    FinalPrice float64
}

The complete reservation price is:

  • sum of all nightly rates
  • for all reserved rooms

Seasonal Pricing

Create seasonal pricing rules. For example:

type SeasonalRate struct {
    HotelID      string
    RoomTypeID   string
    StartDate    time.Time
    EndDate      time.Time
    Multiplier   float64
}

Example:

        Base Price = 150
        Summer Multiplier = 1.20
        Night Price = 180

Promotions

A promotion may provide:

  • percentage discount
  • fixed discount
  • minimum stay requirement
  • date restriction
  • room-type restriction

A possible model is:

type Promotion struct {
    ID             string
    HotelID        string
    PercentageOff  float64
    MinimumNights  int
    StartDate      time.Time
    EndDate        time.Time
}

The implementation must define deterministic behavior when several promotions match. For the base task:

select the single promotion producing the lowest valid final price

Promotions are not combined unless explicitly configured.

Reservation Quote

Before creating a reservation, the system should be able to generate a quote. For example:

type ReservationQuote struct {
    HotelID       string
    RoomIDs       []string
    CheckInDate   time.Time
    CheckOutDate  time.Time
    NightCount    int
    Subtotal      float64
    Discount      float64
    Total         float64
}

Generating a quote must not create a reservation.

Reservation Creation

When a reservation is created:

  • validate guest
  • validate hotel
  • validate dates
  • validate room capacity
  • validate availability
  • calculate price
  • create reservation

For the base task, reservation creation across several rooms is atomic. Either:

all requested rooms are reserved

or:

no reservation is created

Reservation State Machine

Normal lifecycle:

        pending
           |
           v
        confirmed
           |
           v
        checked_in
           |
           v
        checked_out

Alternative states:

        pending -> cancelled
        confirmed -> cancelled
        confirmed -> no_show

Invalid transitions must be rejected. Examples:

        cancelled -> checked_in
        checked_out -> checked_in
        pending -> checked_out
        no_show -> checked_in

Payment

A reservation may contain several payments. For example:

type Payment struct {
    ID            string
    ReservationID string
    Amount        float64
    Method        PaymentMethod
    Status        PaymentStatus
    Timestamp     time.Time
}

Possible methods:

  • card
  • cash
  • bank_transfer

Possible states:

  • pending
  • completed
  • failed
  • refunded

Deposit

A hotel may require a reservation deposit. For example:

20% of reservation total

The reservation may remain:

pending

until the required deposit is successfully processed.

Cancellation Policy

Different hotels may have different cancellation policies. A possible model:

type CancellationPolicy struct {
    ID                  string
    FreeCancellationHours int
    LateCancellationPercent float64
}

Example:

free cancellation until 48 hours before check-in
after that point, charge 50%

The cancellation operation must calculate any applicable charge.

No-Show

If a confirmed guest does not check in before the hotel’s configured no-show deadline:

Reservation.Status = no_show

The applicable policy determines the final charge.

Check-In

Check-in requires:

  • confirmed reservation
  • valid guest
  • arrival date
  • room ready
  • room not under maintenance

The assigned room must be available before check-in. After successful check-in:

        Reservation.Status = checked_in
        Room.Status = occupied

Room Reassignment

A reserved room may become unavailable before guest arrival. For example:

  • water leak
  • maintenance failure
  • electrical problem

The system should search for a replacement room. The replacement must:

  • belong to the same hotel
  • support required guest capacity
  • be available for the complete reservation interval
  • not reduce the booked room category unless explicitly allowed

Upgrade

If the originally reserved room cannot be used, the hotel may upgrade the guest. For the base task:

a free operational upgrade is allowed

when no equivalent room is available. The system must record the reassignment.

Stay

A stay represents the actual guest occupancy after check-in. A possible model is:

type Stay struct {
    ID            string
    ReservationID string
    ActualCheckIn time.Time
    ActualCheckOut *time.Time
}

Reservation and Stay should not be treated as identical concepts.

A reservation represents the commercial booking. A stay represents actual occupancy.

Hotel Services

Hotels may provide services such as:

  • breakfast
  • room_service
  • parking
  • spa
  • laundry
  • airport_transfer
  • minibar

A possible model is:

type HotelService struct {
    ID      string
    HotelID string
    Name    string
    Price   float64
}

Service Usage

Guest service usage must be recorded. For example:

type ServiceCharge struct {
    ID        string
    StayID    string
    ServiceID string
    Quantity  int
    UnitPrice float64
    Timestamp time.Time
}

These charges become part of the final invoice.

Minibar

Minibar consumption may also be modeled as service charges. Example:

        Water     x2
        Juice     x1
        Snack     x3

Each item contributes to the guest invoice.

Housekeeping

After check-out:

Room.Status = dirty

The room cannot immediately become available for another check-in.

A housekeeping task must be completed first.

Housekeeping Task

A possible model is:

type HousekeepingTask struct {
    ID         string
    RoomID     string
    EmployeeID string
    Status     HousekeepingStatus
    CreatedAt  time.Time
    CompletedAt *time.Time
}

Possible states:

  • created
  • assigned
  • in_progress
  • completed
  • cancelled

After successful cleaning:

Room.Status = available

provided no maintenance restriction exists.

Employee

Hotel employees may have roles such as:

  • reception
  • housekeeping
  • maintenance
  • manager

A possible model:

type Employee struct {
    ID      string
    HotelID string
    Name    string
    Role    EmployeeRole
    Status  EmployeeStatus
}

Tasks must only be assigned to employees with an appropriate role.

Maintenance

A room may become unavailable because of maintenance. A possible model is:

type RoomMaintenance struct {
    ID        string
    RoomID    string
    StartTime time.Time
    EndTime   time.Time
    Reason    string
    Status    MaintenanceStatus
}

During active maintenance:

  • room cannot be assigned
  • room cannot accept check-in

Maintenance Conflict

If maintenance is scheduled for a room that already has a future reservation, the system must report the conflict.

It must not silently invalidate the reservation. The hotel may then:

  • move maintenance
  • reassign guest
  • cancel maintenance

according to the chosen operation.

Check-Out

At check-out:

  • calculate room charges
  • calculate service charges
  • apply valid discounts
  • calculate previous payments
  • calculate remaining balance
  • create final invoice
  • complete payment
  • close stay
  • mark room dirty
  • create housekeeping task

Invoice

A possible model is:

type Invoice struct {
    ID            string
    ReservationID string
    StayID        string
    Items         []InvoiceItem
    Subtotal      float64
    Discount      float64
    Total         float64
    Paid          float64
    Balance       float64
}

with:

type InvoiceItem struct {
    Description string
    Quantity    int
    UnitPrice   float64
    Total       float64
}

Invoice Consistency

The invoice must preserve the actual prices used.

Changing a room’s current base price after check-out must not change an already issued invoice.

Historical financial records must remain stable.

Occupancy

The system should calculate hotel occupancy for a date or date range. For example:

        Total Operational Rooms = 100
        Occupied Rooms = 82
        Occupancy = 82%

Rooms marked:

  • maintenance
  • out_of_service

should be distinguishable from normal unoccupied rooms.

The system should support searching across all hotels. Example:

        City = any
        Check-In = July 10
        Check-Out = July 15
        Guests = 2
        Maximum Total Price = 1200

Results may be sorted by:

  • lowest total price
  • hotel name
  • room category

The selected ordering must be deterministic.

Overbooking Scenario

Create a scenario where two reservation requests attempt to reserve the same last available room for overlapping dates.

Only one reservation may succeed. The final state must contain exactly one valid reservation for that room and interval.

The implementation does not require actual parallel execution.

It must model the operation so that availability validation and reservation creation behave as one consistent state change.

Multi-Hotel Scenario

Create a guest journey containing:

        HT01 — 3 nights
        HT02 — 2 nights
        HT04 — 4 nights

These are separate hotel reservations but may belong to one travel plan. An optional model is:

type TravelPlan struct {
    ID             string
    GuestID        string
    ReservationIDs []string
}

Failure or cancellation of one reservation must not automatically modify the others.

Audit History

State-changing operations should generate audit events. Examples:

  • reservation created
  • deposit received
  • reservation confirmed
  • room reassigned
  • guest checked in
  • service added
  • guest checked out
  • invoice created
  • room marked dirty
  • housekeeping completed
  • reservation cancelled
  • maintenance scheduled

A possible model is:

type AuditEvent struct {
    ID         string
    Timestamp  time.Time
    EntityType string
    EntityID   string
    Action     string
    Details    string
}

Idempotency

Important commands should contain unique request IDs. Examples:

  • reservation creation
  • payment processing
  • check-in
  • check-out
  • service charge creation

Processing the same request twice must not:

  • create duplicate reservations
  • charge a guest twice
  • check in twice
  • create duplicate service charges
  • create duplicate invoices

Query Operations

The system should support:

  • search available rooms
  • get reservation
  • get guest reservations
  • get hotel occupancy
  • get current guests
  • get room status
  • get room maintenance
  • get housekeeping queue
  • get reservation payments
  • get stay service charges
  • get final invoice

Queries must not modify state.

Command Operations

State-changing operations include:

  • create reservation
  • confirm reservation
  • process payment
  • cancel reservation
  • mark no-show
  • reassign room
  • check in guest
  • add service charge
  • check out guest
  • schedule maintenance
  • create housekeeping task
  • complete housekeeping

Validation

The implementation should validate:

  • duplicate IDs
  • unknown hotel
  • unknown room
  • unknown room type
  • unknown guest
  • unknown employee
  • unknown reservation
  • invalid date interval
  • check-out <= check-in
  • room capacity exceeded
  • reservation overlap
  • room unavailable
  • maintenance conflict
  • invalid reservation transition
  • invalid room transition
  • negative price
  • invalid payment amount
  • duplicate request ID
  • invalid promotion
  • invalid cancellation policy

Required Test Scenarios

Create tests for at least:

  • successful availability search
  • no available room
  • successful single-room reservation
  • successful multi-room reservation
  • overlapping reservation rejection
  • back-to-back reservations
  • room-capacity validation
  • seasonal price calculation
  • promotion selection
  • reservation quote
  • deposit processing
  • reservation confirmation
  • free cancellation
  • late cancellation
  • no-show
  • successful check-in
  • check-in rejected for unavailable room
  • room reassignment
  • free upgrade
  • service charge
  • successful check-out
  • invoice generation
  • room becomes dirty
  • housekeeping completion
  • maintenance conflict
  • occupancy calculation
  • network-wide hotel search
  • duplicate reservation request
  • duplicate payment request
  • duplicate check-out request

Modeling Goal

The purpose of this task is to model a reservation system where commercial and operational state must remain consistent.

A useful conceptual architecture is:

        Hotel Registry
              |
              +-- Hotels
              +-- Rooms
              +-- Room Types
              |
              v
        Availability Service
              |
              +-- Date Intervals
              +-- Capacity
              +-- Maintenance
              |
              v
        Pricing Service
              |
              +-- Base Rates
              +-- Seasonal Rates
              +-- Promotions
              |
              v
        Reservation Service
              |
              +-- Booking
              +-- Cancellation
              +-- Reassignment
              |
              v
        Stay Service
              |
              +-- Check-In
              +-- Services
              +-- Check-Out
              |
              v
        Hotel Operations
              |
              +-- Housekeeping
              +-- Maintenance
              |
              v
        Billing Service
              |
              +-- Payments
              +-- Charges
              +-- Invoice
              |
              v
        Audit History

The main challenge is keeping reservations, room availability, actual occupancy, pricing, payments, housekeeping, maintenance, and billing consistent across the complete guest lifecycle.

Task 10 — Banking and Payment Processing Platform

Objective

Create a banking and payment-processing platform.

The system manages:

  • customers
  • bank accounts
  • currency balances
  • payment cards
  • merchants
  • transfers
  • card payments
  • payment authorization
  • capture
  • settlement
  • refunds
  • reversals
  • fees
  • transaction limits
  • account holds
  • ledger entries
  • statements
  • fraud rules
  • account freezes
  • disputes
  • idempotency
  • audit history

The implementation must preserve financial consistency across the complete transaction lifecycle.

This task is intentionally focused on modeling financial state rather than implementing a real banking protocol.

The most important rule is:

financial state must never be changed without a corresponding financial record

Domain Overview

Conceptually:

        Customer
           |
           +-- Account
           |      |
           |      +-- Currency Balance
           |      +-- Ledger
           |      +-- Transfer
           |
           +-- Card
                  |
                  v
               Payment
                  |
                  +-- Authorization
                  +-- Capture
                  +-- Settlement
                  +-- Refund
                  +-- Reversal

Merchant side:

        Merchant
           |
           +-- Merchant Account
           |
           +-- Payments
           |
           +-- Settlements

Risk and control:

        Transaction
           |
           +-- Limits
           +-- Fraud Rules
           +-- Holds
           +-- Disputes
           +-- Audit History

Customer

A customer contains:

  • ID
  • First Name
  • Last Name
  • Country
  • Status

For example:

type Customer struct {
    ID        string
    FirstName string
    LastName  string
    Country   string
    Status    CustomerStatus
}

Possible states:

  • active
  • restricted
  • suspended
  • closed

Account

A customer may own multiple accounts. A possible model is:

type Account struct {
    ID         string
    CustomerID string
    Type       AccountType
    Status     AccountStatus
}

Possible account types:

  • current
  • savings
  • business

Possible states:

  • active
  • frozen
  • restricted
  • closed

Currency

Support at least:

  • EUR
  • USD
  • GBP
  • RSD
  • JPY

Use a controlled currency representation.

Account Balance

An account may contain balances in several currencies. A possible model is:

type AccountBalance struct {
    AccountID string
    Currency  Currency
    Available float64
    Held      float64
}

Conceptually:

total balance = available + held

Money reserved by an authorization moves from available to held without immediately becoming a completed payment.

Money Representation

Financial calculations must avoid uncontrolled floating-point rounding errors.

A recommended implementation represents monetary values using:

integer minor units

such as:

  • EUR cents
  • USD cents
  • GBP pence

or another exact decimal representation. For example:

€125.42

may internally be represented as:

12542 cents

Ledger

The ledger is the authoritative financial history.

A possible model is:

type LedgerEntry struct {
    ID            string
    AccountID     string
    Currency      Currency
    Amount        int64
    Direction     LedgerDirection
    ReferenceType string
    ReferenceID   string
    Timestamp     time.Time
}

Possible directions:

  • debit
  • credit

Balances should be consistent with ledger activity.

The implementation must not silently change a balance without recording the corresponding ledger operation.

Double-Entry Principle

For transfers between accounts, financial movement should be represented by matching entries.

Example:

Account A:
-100 EUR

Account B:
+100 EUR

Conceptually:

total debits = total credits

for the same transfer before fees or currency conversion are considered.

Transfer

A transfer moves money between accounts. A possible model is:

type Transfer struct {
    ID              string
    SourceAccountID string
    TargetAccountID string
    Currency        Currency
    Amount          int64
    Status          TransferStatus
}

Possible states:

  • created
  • validated
  • processing
  • completed
  • failed
  • reversed

Transfer Validation

Before processing a transfer, validate:

  • source account exists
  • target account exists
  • accounts are active
  • currency is supported
  • amount > 0
  • sufficient available balance
  • transaction limits
  • fraud rules

Atomic Transfer

A transfer must be atomic. Either:

source debit and destination credit both succeed

or:

neither is committed

The system must never leave:

  • source debited
  • destination not credited

as a successful final state.

Transfer Scenario

Account:

        ACC-100
        EUR Available = 5,000.00

Transfer:

        ACC-100 -> ACC-200
        Amount = 1,250.00 EUR

After successful processing:

        ACC-100: Available = 3,750.00 EUR
        ACC-200: Available += 1,250.00 EUR

The ledger must contain the corresponding entries.

Payment Card

A customer may have payment cards connected to an account. A possible model is:

type Card struct {
    ID          string
    CustomerID  string
    AccountID   string
    LastFour    string
    Status      CardStatus
}

Possible states:

  • active
  • blocked
  • expired
  • cancelled

Do not store or expose unnecessary sensitive card information for the purpose of this exercise.

Merchant

A merchant contains:

  • ID
  • Name
  • Category
  • Settlement Account
  • Status

For example:

type Merchant struct {
    ID                  string
    Name                string
    Category            string
    SettlementAccountID string
    Status              MerchantStatus
}

Payment

A card payment contains:

  • Payment ID
  • Card ID
  • Merchant ID
  • Currency
  • Amount
  • Status
  • Created At

A possible model is:

type Payment struct {
    ID         string
    CardID     string
    MerchantID string
    Currency   Currency
    Amount     int64
    Status     PaymentStatus
    CreatedAt  time.Time
}

Payment Lifecycle

A normal payment lifecycle is:

        created => authorized => captured => settled

Alternative states:

  • declined
  • reversed
  • partially_refunded
  • refunded

Authorization

Authorization validates whether the payment may proceed.

The system checks:

  • card status
  • account status
  • merchant status
  • available balance
  • transaction limits
  • fraud rules

If successful:

payment amount moves from available balance to held balance

Example:

        Available = 1000.00
        Held      = 0.00
        
        Authorize 250.00

becomes:

Available = 750.00
Held      = 250.00

Authorization Hold

A possible model is:

type AuthorizationHold struct {
    ID        string
    PaymentID string
    AccountID string
    Currency  Currency
    Amount    int64
    Status    HoldStatus
    ExpiresAt time.Time
}

Possible states:

  • active
  • captured
  • released
  • expired

Declined Authorization

If authorization fails:

Payment.Status = declined

No funds may be held. The result should report the reason. Examples:

  • insufficient_funds
  • card_blocked
  • account_frozen
  • limit_exceeded
  • fraud_rejected
  • merchant_unavailable

Capture

Capture converts authorized funds into a payment obligation. For the base task:

capture amount <= authorized amount

The corresponding hold is reduced or consumed.

Partial Capture

Support partial capture. Example:

        Authorized = 500.00
        Captured   = 420.00

The unused:

80.00

must be released back to available balance.

Settlement

Settlement transfers captured funds to the merchant side.

A settlement may contain many captured payments. A possible model is:

type Settlement struct {
    ID         string
    MerchantID string
    PaymentIDs []string
    Gross      int64
    Fees       int64
    Net        int64
    Status     SettlementStatus
}

Conceptually:

Net = Gross - Fees

Fees

The platform may charge fees. A possible model is:

type FeeRule struct {
    ID              string
    MerchantCategory string
    FixedFee         int64
    PercentageBps    int64
}

PercentageBps may use basis points. For example:

150 basis points = 1.50%

Fee calculation must use deterministic rounding.

Refund

A settled or captured payment may be refunded according to the implemented policy. A possible model is:

type Refund struct {
    ID        string
    PaymentID string
    Amount    int64
    Status    RefundStatus
}

Possible states:

  • created
  • completed
  • failed

Partial Refund

Support multiple partial refunds. Example:

Original Payment = 500.00

Refund 1 = 100.00
Refund 2 = 150.00

Total refunded:

250.00

Remaining refundable amount:

250.00

The system must reject a later refund that would cause:

total refunds > captured amount

Full Refund

When total completed refunds equal the captured amount:

Payment.Status = refunded

When:

0 < refunded amount < captured amount

use:

partially_refunded

Reversal

A reversal cancels a payment before normal settlement is completed.

For example, an authorization may be reversed. When an active hold is reversed:

  • held amount decreases
  • available amount increases

The same money must not be released twice.

Authorization Expiration

An authorization hold may expire before capture. When expired:

  • hold is released
  • payment cannot be captured using that expired authorization

The corresponding funds return to available balance.

Transaction Limits

Accounts or cards may have limits. Examples:

  • maximum single transaction
  • daily spending limit
  • daily transfer limit
  • monthly spending limit

A possible model is:

type TransactionLimit struct {
    EntityID       string
    SingleLimit    int64
    DailyLimit     int64
    MonthlyLimit   int64
}

Completed and relevant pending operations must be considered consistently when evaluating limits.

Daily Limit Scenario

Card:

Daily Limit = 2,000.00
Already Used = 1,600.00

New payment:

500.00

must be rejected because:

1,600 + 500 > 2,000

Account Freeze

An account may become frozen.

When:

Account.Status = frozen

new outgoing transfers and payments must be rejected.

Incoming credits may remain allowed.

The implementation must document the chosen incoming-credit policy.

Card Blocking

A blocked card must not create new payment authorizations.

Existing completed payments remain part of history. Blocking a card must not delete previous transactions.

Fraud Rule

Create a simplified rule engine. A possible model is:

type FraudRule struct {
    ID        string
    Type      FraudRuleType
    Threshold int64
    Action    FraudAction
}

Possible actions:

  • allow
  • review
  • reject

Example rules:

single payment > 5,000 -> review
single payment > 20,000 -> reject
more than 5 payments in 2 minutes -> review
payment from blocked merchant -> reject

Fraud Decision

A possible result is:

type FraudDecision struct {
    TransactionID string
    Action        FraudAction
    TriggeredRules []string
}

All triggered rules should be reported. The strongest action wins:

reject > review > allow

Manual Review

A transaction marked review should not automatically become completed. It remains pending until approved or rejected by an explicit review operation.

Merchant Settlement Scenario

Merchant:

MERCHANT-10

Captured payments:

Payment 1 = 100.00
Payment 2 = 250.00
Payment 3 = 650.00

Gross:

1,000.00

If total fees are:

25.00

merchant settlement is:

975.00

The ledger must preserve how the final amount was derived.

Currency Conversion

An optional but recommended extension supports transfers between different currencies.

For example:

        Source: EUR
        Destination: USD

The conversion operation should preserve:

  • source amount
  • exchange rate
  • destination amount
  • rate timestamp

Historical transactions must retain the rate actually used.

Changing the current exchange rate must not modify historical transaction results.

Statement

The platform should generate account statements. A statement contains ledger entries for:

  • account
  • currency
  • date range

A possible model is:

type AccountStatement struct {
    AccountID      string
    Currency       Currency
    OpeningBalance int64
    Entries        []LedgerEntry
    ClosingBalance int64
}

The statement should satisfy:

        Opening Balance + Credits - Debits = Closing Balance

Dispute

A customer may dispute a completed payment. A possible model is:

type Dispute struct {
    ID        string
    PaymentID string
    CustomerID string
    Reason    string
    Status    DisputeStatus
}

Possible states:

  • opened
  • under_review
  • accepted
  • rejected
  • closed

A dispute does not automatically mean that the original payment was invalid.

The financial effect of an accepted dispute should be represented explicitly.

Transaction History

The system must preserve transaction history. Examples:

  • transfer created
  • transfer completed
  • authorization approved
  • authorization declined
  • hold created
  • payment captured
  • payment settled
  • refund completed
  • authorization reversed
  • account frozen
  • card blocked
  • dispute opened

History must not disappear when the current state changes.

Idempotency

Idempotency is mandatory for financial commands. Requests should contain:

RequestID

Examples:

  • transfer request
  • payment authorization
  • capture request
  • refund request
  • settlement request

Processing the same RequestID twice must not:

  • debit twice
  • credit twice
  • create two holds
  • capture twice
  • refund twice
  • settle twice

The system should return the previously known result for an already processed request.

Duplicate Payment Scenario

Request:

RequestID = PAY-REQ-100
Amount = 250.00

is received twice. The final account state must reflect:

one authorization

not:

two authorizations

Financial Invariants

The implementation must preserve important invariants. Examples:

  • available balance >= 0
  • held balance >= 0
  • refund total <= captured amount
  • capture amount <= authorized amount
  • released hold cannot be released again
  • completed transfer has matching ledger entries
  • duplicate request does not duplicate financial effect
  • closed account cannot initiate new transactions

These invariants should be tested directly.

Reconciliation

Create a reconciliation operation that compares:

stored account balances

with:

balances derived from ledger entries

A possible result is:

type ReconciliationResult struct {
    AccountID       string
    Currency        Currency
    StoredBalance   int64
    CalculatedBalance int64
    Difference      int64
    Valid           bool
}

A non-zero difference indicates inconsistent financial state.

Reconciliation Scenario

Assume:

Stored Balance = 10,000.00
Ledger-Derived Balance = 9,950.00

The result must report:

Difference = 50.00
Valid = false

The reconciliation operation must not silently modify the balance.

Repair should be a separate explicit operation.

Audit History

Financial operations should generate audit events. A possible model is:

type AuditEvent struct {
    ID         string
    Timestamp  time.Time
    ActorID    string
    EntityType string
    EntityID   string
    Action     string
    Details    string
}

Audit history should record administrative operations such as:

  • account frozen
  • account unfrozen
  • card blocked
  • limit changed
  • manual review approved
  • manual review rejected
  • dispute resolved

Query Operations

The platform should support:

  • get account balance
  • get available balance
  • get held balance
  • get account transactions
  • get payment
  • get transfer
  • get active holds
  • get merchant settlements
  • get refunds for payment
  • get card spending
  • get triggered fraud rules
  • generate account statement
  • get disputes
  • reconcile account

Queries must not modify financial state.

Command Operations

State-changing operations include:

  • create account
  • deposit funds
  • create transfer
  • authorize payment
  • capture payment
  • reverse authorization
  • expire hold
  • settle payments
  • refund payment
  • freeze account
  • unfreeze account
  • block card
  • change transaction limit
  • open dispute
  • resolve dispute
  • approve manual review
  • reject manual review

Validation

The implementation should validate:

  • duplicate IDs
  • unknown customer
  • unknown account
  • unknown card
  • unknown merchant
  • unknown payment
  • unknown transfer
  • unsupported currency
  • amount <= 0
  • insufficient available balance
  • account frozen
  • card blocked
  • merchant disabled
  • transaction limit exceeded
  • invalid payment transition
  • capture exceeds authorization
  • refund exceeds captured amount
  • duplicate RequestID
  • invalid fee
  • invalid ledger relationship
  • invalid hold state

Required Test Scenarios

Create tests for at least:

  • successful deposit
  • successful transfer
  • insufficient-funds transfer
  • atomic transfer behavior
  • matching transfer ledger entries
  • successful payment authorization
  • declined authorization
  • authorization hold
  • partial capture
  • full capture
  • unused hold release
  • authorization reversal
  • authorization expiration
  • successful settlement
  • fee calculation
  • partial refund
  • multiple partial refunds
  • refund limit rejection
  • full refund
  • daily spending limit
  • single transaction limit
  • frozen account
  • blocked card
  • fraud review
  • fraud rejection
  • manual review approval
  • manual review rejection
  • merchant settlement
  • account statement
  • dispute creation
  • duplicate transfer request
  • duplicate authorization request
  • duplicate capture request
  • duplicate refund request
  • ledger reconciliation success
  • ledger reconciliation failure

Modeling Goal

The purpose of this task is to model a financial system where every state change must remain traceable and financially consistent.

A useful conceptual architecture is:

        Customer Service
              |
              +-- Customers
              +-- Accounts
              +-- Cards
              |
              v
        Transaction Service
              |
              +-- Transfers
              +-- Payments
              |
              v
        Authorization Service
              |
              +-- Validation
              +-- Holds
              +-- Limits
              +-- Fraud Rules
              |
              v
        Payment Lifecycle
              |
              +-- Authorization
              +-- Capture
              +-- Reversal
              +-- Refund
              |
              v
        Settlement Service
              |
              +-- Merchant Settlement
              +-- Fees
              |
              v
           Ledger
              |
              +-- Debits
              +-- Credits
              +-- Statements
              +-- Reconciliation
              |
              v
        Risk and Control
              |
              +-- Account Freeze
              +-- Card Block
              +-- Manual Review
              +-- Disputes
              |
              v
        Audit History

The main challenge is maintaining consistency between balances, holds, ledger entries, payments, transfers, settlements, refunds, limits, and transaction history.

A successful implementation should make it impossible for a normal operation to create money, lose money, charge the same request twice, refund more than was paid, or leave financial state without an auditable explanation.

Task 11 — Healthcare and Hospital Management Network

Objective

Create a healthcare and hospital management system for a network of hospitals.

The system must manage:

  • hospitals
  • departments
  • wards
  • rooms
  • beds
  • patients
  • doctors
  • nurses
  • medical staff
  • appointments
  • emergency admissions
  • triage
  • hospital admissions
  • diagnoses
  • treatments
  • medications
  • medication orders
  • laboratory tests
  • medical procedures
  • surgeries
  • operating rooms
  • staff shifts
  • equipment
  • bed allocation
  • discharge
  • patient transfers
  • medical history
  • billing records
  • operational events
  • audit history

The implementation must coordinate both:

medical state

and:

hospital operational state

A patient may require treatment immediately while the hospital simultaneously has limited:

  • beds
  • doctors
  • nurses
  • operating rooms
  • equipment

The system must therefore manage priorities, schedules, resource conflicts, and patient history while preserving consistent state.

Hospital Network

Create a network containing at least three hospitals.

For example:

        H01 — Central General Hospital
        H02 — North Medical Center
        H03 — South Regional Hospital

Hospitals may have different:

  • departments
  • bed capacities
  • medical staff
  • equipment
  • operating rooms
  • specializations

Patients may be transferred between hospitals when the required resources are unavailable locally.

Hospital

A possible model is:

type Hospital struct {
    ID      string
    Name    string
    City    string
    Country string
}

Department

Each hospital contains departments. Examples:

  • Emergency
  • Cardiology
  • Neurology
  • Surgery
  • Orthopedics
  • Pediatrics
  • Internal Medicine
  • Intensive Care
  • Radiology
  • Laboratory

A possible model is:

type Department struct {
    ID         string
    HospitalID string
    Name       string
    Type       DepartmentType
}

Ward

Departments may contain wards.

type Ward struct {
    ID           string
    DepartmentID string
    Name         string
    Type         WardType
}

Possible ward types:

general
intensive_care
isolation
pediatric
postoperative

Room

A ward contains rooms.

type Room struct {
    ID     string
    WardID string
    Number string
    Status RoomStatus
}

Possible states:

available
occupied
cleaning
maintenance
closed

Bed

Beds are independently allocatable resources.

type Bed struct {
    ID     string
    RoomID string
    Status BedStatus
}

Possible states:

  • available
  • reserved
  • occupied
  • cleaning
  • maintenance
  • unavailable

A bed may belong to a valid room while still being unavailable independently.

Patient

A patient contains:

  • ID
  • First Name
  • Last Name
  • Date of Birth
  • Sex
  • Blood Type
  • Phone
  • Emergency Contact

For example:

type Patient struct {
    ID               string
    FirstName        string
    LastName         string
    DateOfBirth      time.Time
    Sex              string
    BloodType        string
    Phone            string
    EmergencyContact string
}

Medical Staff

Create a common staff model.

type MedicalStaff struct {
    ID           string
    HospitalID   string
    FirstName    string
    LastName     string
    Status       StaffStatus
}

Possible states:

  • available
  • working
  • off_shift
  • on_leave
  • unavailable

Doctor

Doctors contain additional information:

  • Specialization
  • Department
  • Qualifications

For example:

type Doctor struct {
    StaffID       string
    DepartmentID  string
    Specialization string
    Qualifications []string
}

Nurse

A nurse may contain:

type Nurse struct {
    StaffID      string
    DepartmentID string
    Skills       []string
}

Staff Shift

Staff availability must be represented explicitly.

type StaffShift struct {
    ID        string
    StaffID   string
    StartTime time.Time
    EndTime   time.Time
    Status    ShiftStatus
}

A staff member cannot be assigned to two operations during overlapping time intervals.

Appointment

Patients may schedule appointments.

type Appointment struct {
    ID           string
    PatientID    string
    DoctorID     string
    DepartmentID string
    StartTime    time.Time
    EndTime      time.Time
    Reason       string
    Status       AppointmentStatus
}

Possible states:

  • scheduled
  • confirmed
  • in_progress
  • completed
  • cancelled
  • no_show

Appointment Validation

A valid appointment requires:

  • patient exists
  • doctor exists
  • doctor belongs to appropriate department
  • doctor is working during requested interval
  • doctor has no conflicting appointment
  • start time < end time

Two active appointments for the same doctor may not overlap.

Emergency Department

Emergency patients may arrive without an appointment. Create an emergency case:

type EmergencyCase struct {
    ID          string
    PatientID   string
    HospitalID  string
    ArrivalTime time.Time
    Priority    TriagePriority
    Status      EmergencyStatus
}

Triage

Emergency patients must be prioritized. Use five priority levels:

        P1 — Immediate
        P2 — Very Urgent
        P3 — Urgent
        P4 — Standard
        P5 — Non-Urgent

Lower numeric value means higher priority. For patients with the same priority:

earlier arrival is processed first

If both are equal:

lower EmergencyCase ID is processed first

Emergency Queue

The queue must therefore be ordered by:

  • Priority
  • ArrivalTime
  • EmergencyCaseID

Example:

        E01 — P3 — 10:00
        E02 — P1 — 10:05
        E03 — P2 — 09:58
        E04 — P1 — 10:07

Processing order:

        E02
        E04
        E03
        E01

Emergency priority may override normal appointment scheduling when explicitly required by the scenario.

Emergency Preemption

Suppose:

Operating Room OR-01
Scheduled Surgery: 14:00

At:

13:20

a P1 emergency patient arrives and requires immediate surgery.

If no alternative operating room is available, the system may postpone the scheduled non-emergency procedure.

The operation must:

  • identify the conflict
  • preserve the original surgery
  • change its schedule explicitly
  • record the reason
  • allocate resources to the emergency case
  • generate operational events

The scheduled surgery must not silently disappear.

Admission

A patient may be admitted to the hospital.

type Admission struct {
    ID           string
    PatientID    string
    HospitalID   string
    DepartmentID string
    WardID       string
    BedID        string
    AdmittedAt   time.Time
    DischargedAt *time.Time
    Status       AdmissionStatus
}

Possible states:

  • requested
  • admitted
  • transferred
  • discharged
  • cancelled

Bed Allocation

Before admission:

  • appropriate ward must exist
  • bed must be available
  • bed must not already be reserved
  • bed must satisfy patient requirements

Examples of special requirements:

  • intensive care
  • isolation
  • pediatric ward
  • postoperative care

Atomic Bed Allocation

Admission and bed allocation should behave as one logical operation. The system must not produce:

Admission.Status = admitted

while leaving:

Bed.Status = available

for another patient. After successful admission:

Admission.Status = admitted
Bed.Status = occupied

Bed Capacity Scenario

Suppose ICU contains:

ICU-B01 — occupied
ICU-B02 — occupied
ICU-B03 — available

Two patients request ICU admission.

Only one may receive:

ICU-B03

The other patient must remain waiting or be considered for transfer.

Diagnosis

A patient may receive multiple diagnoses.

type Diagnosis struct {
    ID          string
    PatientID   string
    DoctorID    string
    Code        string
    Description string
    DiagnosedAt time.Time
}

Diagnoses form part of permanent medical history.

Treatment Plan

A diagnosis may result in a treatment plan.

type TreatmentPlan struct {
    ID          string
    PatientID   string
    DiagnosisID string
    DoctorID    string
    Status      TreatmentStatus
}

Possible states:

  • created
  • active
  • completed
  • cancelled

Medication

Create a medication catalog.

type Medication struct {
    ID       string
    Name     string
    Unit     string
    Stock    int
}

Medication Order

Doctors may prescribe medication.

type MedicationOrder struct {
    ID           string
    PatientID    string
    DoctorID     string
    MedicationID string
    Dose         string
    Frequency    string
    StartTime    time.Time
    EndTime      time.Time
    Status       MedicationOrderStatus
}

Medication orders must reference existing:

  • patient
  • doctor
  • medication

Medication Inventory

Hospital medication stock must not become negative. Dispensing medication should:

  • validate active order
  • validate stock
  • reduce stock
  • record administration

Medication Administration

type MedicationAdministration struct {
    ID                string
    MedicationOrderID string
    PatientID         string
    StaffID           string
    Timestamp         time.Time
    Quantity          int
}

Administration history must remain available after the treatment ends.

Laboratory Test

A doctor may request a laboratory test.

type LabTestOrder struct {
    ID          string
    PatientID   string
    DoctorID    string
    TestType    string
    Priority    TestPriority
    Status      LabTestStatus
    RequestedAt time.Time
}

Possible states:

  • requested
  • sample_collected
  • processing
  • completed
  • cancelled

Laboratory Result

type LabResult struct {
    ID        string
    TestID    string
    Values    map[string]string
    ResultAt  time.Time
    Reviewed  bool
}

Completed results become part of patient medical history.

Medical Equipment

Hospitals contain limited equipment. Examples:

  • MRI
  • CT Scanner
  • X-Ray
  • Ventilator
  • Ultrasound
  • ECG
  • Dialysis Machine

A possible model:

type MedicalEquipment struct {
    ID         string
    HospitalID string
    Type       string
    Status     EquipmentStatus
}

Possible states:

  • available
  • reserved
  • in_use
  • maintenance
  • failed

Equipment Reservation

Procedures requiring equipment must reserve it for a time interval.

The system must prevent overlapping reservations for the same equipment.

Medical Procedure

type MedicalProcedure struct {
    ID          string
    PatientID   string
    DoctorID    string
    Type        string
    StartTime   time.Time
    EndTime     time.Time
    EquipmentIDs []string
    Status      ProcedureStatus
}

Operating Room

type OperatingRoom struct {
    ID         string
    HospitalID string
    Status     OperatingRoomStatus
}

Possible states:

  • available
  • reserved
  • in_use
  • cleaning
  • maintenance
  • closed

Surgery

A surgery may require:

  • operating room
  • surgeon
  • assistant surgeon
  • anesthesiologist
  • nurses
  • equipment
  • patient

A possible model:

type Surgery struct {
    ID              string
    PatientID       string
    OperatingRoomID string
    SurgeonIDs      []string
    StaffIDs        []string
    EquipmentIDs    []string
    StartTime       time.Time
    EndTime         time.Time
    Priority        SurgeryPriority
    Status          SurgeryStatus
}

Surgery Scheduling

A surgery may be scheduled only when all required resources are simultaneously available.

The scheduler must validate:

  • operating room availability
  • doctor availability
  • staff shifts
  • staff schedule conflicts
  • equipment availability
  • patient availability

A resource conflict invalidates the proposed schedule.

Surgery Priority

Possible priorities:

  • elective
  • urgent
  • emergency

Emergency surgery has the highest priority. Urgent surgery has priority over elective surgery. Priority alone does not permit silently deleting existing schedules. Any displacement must be represented as an explicit rescheduling operation.

Surgery Conflict Scenario

Suppose:

        S01:
            OR-01
            14:00 -> 16:00
            elective
        
        S02:
            OR-01
            15:00 -> 17:00
            urgent

Both cannot use the room simultaneously.

The scheduler must reject the conflicting schedule or explicitly reschedule one surgery.

Patient Transfer

A patient may need a resource unavailable at the current hospital. Example:

H01 has no available ICU bed
H02 has ICU capacity

Create:

type PatientTransfer struct {
    ID             string
    PatientID      string
    SourceHospitalID string
    TargetHospitalID string
    Reason         string
    Status         TransferStatus
}

Possible states:

requested
approved
in_transit
completed
cancelled

Transfer Validation

Before transfer:

  • target hospital must exist
  • target hospital must support required department
  • required bed must be available
  • required resources must exist
  • patient must be transportable

For the base task, transportability may be represented as an explicit boolean decision rather than inferred medically.

Discharge

A patient may be discharged when the responsible medical process explicitly permits it. The system should:

- close admission
- record discharge time
- release bed
- preserve medical history
- create cleaning requirement

After discharge:

Bed.Status = cleaning

The bed becomes:

available

only after cleaning is completed.

Patient Medical History

The system should be able to produce a patient history containing:

  • appointments
  • admissions
  • diagnoses
  • treatments
  • medication orders
  • medication administrations
  • laboratory tests
  • procedures
  • surgeries
  • transfers
  • discharges

Historical records must not disappear when current state changes.

Hospital Capacity

Create a hospital-capacity report. Example:

Hospital: H01

General Beds:
Total      = 120
Occupied   = 94
Available  = 18
Unavailable = 8

ICU Beds:
Total      = 20
Occupied   = 18
Available  = 2

Operating Rooms:
Total      = 8
Available  = 3
In Use     = 4
Maintenance = 1

The system should support:

find hospitals capable of accepting this patient

Criteria may include:

  • department
  • bed type
  • required equipment
  • specialist availability

Results should be deterministic.

A reasonable ordering is:

  • highest resource suitability
  • then lowest HospitalID

Distance is not part of the base task unless explicitly added.

Resource Failure

Medical resources may fail. Example:

CT-01 fails at 11:20

The system must identify:

  • currently affected procedure
  • future reservations
  • patients waiting for the equipment

Possible recovery actions:

  • use another machine
  • reschedule procedure
  • transfer patient

The selected action must be explicit.

Staff Unavailability

A doctor may unexpectedly become unavailable. The system must identify affected:

  • appointments
  • procedures
  • surgeries

Possible recovery:

  • replacement staff
  • rescheduling
  • cancellation
  • patient transfer

Operational Event

Create a general operational event model.

type OperationalEvent struct {
    ID         string
    Timestamp  time.Time
    HospitalID string
    EntityType string
    EntityID   string
    EventType  string
    Details    string
}

Examples:

  • patient admitted
  • bed allocated
  • emergency priority changed
  • surgery scheduled
  • surgery postponed
  • equipment failed
  • patient transferred
  • patient discharged

Idempotency

Important commands should contain RequestID. Examples:

  • create appointment
  • admit patient
  • schedule surgery
  • create transfer
  • discharge patient
  • dispense medication

Processing the same request twice must not:

  • create duplicate appointments
  • allocate two beds
  • schedule duplicate surgeries
  • transfer the same patient twice
  • dispense medication twice
  • discharge twice

Domain Invariants

The implementation must preserve important invariants.

Examples:

        one bed cannot be occupied by two active admissions
        
        one operating room cannot contain overlapping surgeries
        
        one doctor cannot participate in overlapping procedures
        
        unavailable equipment cannot be reserved
        
        medication stock cannot become negative
        
        discharged patient cannot remain assigned to an occupied bed
        
        completed transfer cannot leave two active admissions
        
        patient history must not be deleted when current state changes

Query Operations

Support queries such as:

  • get patient
  • get patient medical history
  • get doctor schedule
  • get available doctors
  • get available beds
  • get hospital capacity
  • get emergency queue
  • get active admissions
  • get scheduled surgeries
  • get available operating rooms
  • get equipment status
  • get pending laboratory tests
  • find suitable hospital
  • get patient medications

Queries must not modify state.

Command Operations

State-changing operations include:

  • create appointment
  • cancel appointment
  • register emergency case
  • update triage priority
  • admit patient
  • allocate bed
  • create diagnosis
  • create treatment plan
  • order medication
  • administer medication
  • request laboratory test
  • complete laboratory test
  • schedule procedure
  • schedule surgery
  • reschedule surgery
  • transfer patient
  • mark equipment failed
  • restore equipment
  • discharge patient
  • complete bed cleaning

Validation

Validate:

  • duplicate IDs
  • unknown hospital
  • unknown department
  • unknown patient
  • unknown doctor
  • unknown nurse
  • unknown room
  • unknown bed
  • unknown equipment
  • unknown medication
  • invalid time interval
  • appointment conflict
  • staff schedule conflict
  • operating-room conflict
  • equipment conflict
  • bed already occupied
  • invalid ward type
  • insufficient medication stock
  • invalid state transition
  • duplicate RequestID

Required Test Scenarios

Create tests for at least:

  • successful appointment
  • appointment conflict
  • doctor unavailable
  • emergency queue ordering
  • same-priority emergency ordering
  • successful admission
  • no available bed
  • ICU allocation
  • duplicate bed allocation prevention
  • successful diagnosis creation
  • medication order
  • successful medication administration
  • insufficient medication stock
  • laboratory test lifecycle
  • equipment reservation
  • equipment conflict
  • successful surgery scheduling
  • operating-room conflict
  • staff conflict
  • emergency surgery preemption
  • scheduled surgery rescheduling
  • equipment failure
  • doctor becomes unavailable
  • successful patient transfer
  • transfer rejected because target has no bed
  • successful discharge
  • bed enters cleaning state
  • bed becomes available after cleaning
  • patient history generation
  • hospital capacity report
  • duplicate admission request
  • duplicate medication administration request

Large Network Scenario

Create a test dataset containing at least:

  • 3 hospitals
  • 12 departments
  • 20 wards
  • 60 rooms
  • 120 beds
  • 40 doctors
  • 60 nurses
  • 100 patients
  • 50 appointments
  • 20 active admissions
  • 10 operating rooms
  • 25 medical devices
  • 15 scheduled procedures
  • 8 scheduled surgeries
  • 20 medication orders
  • 15 laboratory tests

Then simulate:

  • one P1 emergency arrival
  • one ICU capacity exhaustion
  • one operating-room conflict
  • one equipment failure
  • one doctor becoming unavailable
  • one inter-hospital patient transfer

Verify that all affected resources and patient states remain consistent.

Modeling Goal

The purpose of this task is to model a system where human priority, scheduling, physical resources, and long-lived history interact.

A useful conceptual architecture is:

        Hospital Registry
              |
              +-- Hospitals
              +-- Departments
              +-- Wards
              +-- Rooms
              +-- Beds
              |
              v
        Patient Service
              |
              +-- Patients
              +-- Appointments
              +-- Admissions
              +-- Medical History
              |
              v
        Emergency Service
              |
              +-- Triage
              +-- Priority Queue
              +-- Emergency Allocation
              |
              v
        Clinical Service
              |
              +-- Diagnoses
              +-- Treatments
              +-- Medications
              +-- Laboratory
              |
              v
        Scheduling Service
              |
              +-- Doctors
              +-- Staff
              +-- Equipment
              +-- Operating Rooms
              +-- Surgeries
              |
              v
        Capacity and Transfer Service
              |
              +-- Bed Allocation
              +-- Hospital Capacity
              +-- Patient Transfer
              |
              v
        Operational History

The main challenge is maintaining consistency when several limited resources are required by the same medical operation and when emergency priority changes an already planned schedule.

Task 12 — Energy Grid and Power Distribution Network

Objective

Create an energy-grid management system for a network of power producers, substations, transmission infrastructure, storage systems, regions, and consumers.

The system must manage:

  • power plants
  • generators
  • renewable generation
  • substations
  • transformers
  • transmission lines
  • distribution lines
  • energy storage
  • consumer regions
  • critical consumers
  • generation capacity
  • electricity demand
  • power allocation
  • network capacity
  • maintenance
  • failures
  • overloads
  • power rerouting
  • energy deficits
  • load shedding
  • reserve capacity
  • restoration
  • operational events
  • grid history

The implementation must continuously maintain the relationship between:

Generation
Demand
Network Capacity

while reacting to failures and changing conditions. This task models a simplified power network.

It is not intended to reproduce physical AC power-flow calculations.

The focus is software modeling, capacity allocation, network constraints, operational decisions, failure propagation, and recovery.

Grid Structure

Conceptually:

        Power Plant
             |
             v
        Generator
             |
             v
        Transmission Network
             |
             v
        Substation
             |
             v
        Transformer
             |
             v
        Distribution Network
             |
             v
        Consumer Region

Alternative paths may exist between substations.

This allows the system to reroute available power when a line fails.

Power Plant

A power plant contains one or more generators.

type PowerPlant struct {
    ID       string
    Name     string
    Type     PowerPlantType
    RegionID string
    Status   PlantStatus
}

Possible types:

  • nuclear
  • gas
  • coal
  • hydro
  • wind
  • solar

Possible states:

  • available
  • running
  • reduced
  • maintenance
  • failed
  • offline

Generator

A generator represents an individual generation unit.

type Generator struct {
    ID             string
    PowerPlantID   string
    MaximumOutputMW float64
    MinimumOutputMW float64
    CurrentOutputMW float64
    Status         GeneratorStatus
}

Possible states:

  • available
  • running
  • reduced
  • maintenance
  • failed
  • offline

Generation Rules

For an active generator:

MinimumOutputMW <= CurrentOutputMW <= MaximumOutputMW

A failed or offline generator must produce:

0 MW

unless a specific transitional state is explicitly modeled.

Renewable Generation

Wind and solar generators may have:

AvailableOutputMW

that changes according to environmental conditions. For example:

Solar Maximum = 120 MW
Available Now  = 65 MW

The system cannot schedule:

100 MW

from that generator at that moment.

Substation

Substations connect sections of the network.

type Substation struct {
    ID       string
    Name     string
    RegionID string
    Status   SubstationStatus
}

Possible states:

  • available
  • operational
  • maintenance
  • failed
  • isolated

Transformer

Transformers have limited transfer capacity.

type Transformer struct {
    ID            string
    SubstationID  string
    CapacityMW    float64
    CurrentLoadMW float64
    Status        TransformerStatus
}

The invariant is:

CurrentLoadMW <= CapacityMW

during normal operation.

Transmission Line

A transmission line connects two grid nodes.

type TransmissionLine struct {
    ID          string
    FromNodeID  string
    ToNodeID    string
    CapacityMW  float64
    CurrentFlowMW float64
    Status      LineStatus
}

Possible states:

  • available
  • active
  • maintenance
  • failed
  • isolated

For this exercise, lines may be treated as bidirectional unless explicitly configured otherwise.

Transmission Capacity

For every active line:

abs(CurrentFlowMW) <= CapacityMW

The system must not reroute power through a path whose capacity would be exceeded.

Consumer Region

A consumer region represents aggregated electricity demand.

type ConsumerRegion struct {
    ID            string
    Name          string
    DemandMW      float64
    Priority      ConsumerPriority
    SuppliedMW    float64
}

Possible priority levels:

  • critical
  • high
  • normal
  • low

Critical Consumers

A region may contain critical consumers. Examples:

  • hospital
  • emergency service
  • water treatment
  • telecommunications
  • data center
  • transport infrastructure

A possible model is:

type CriticalConsumer struct {
    ID              string
    RegionID        string
    Name            string
    MinimumSupplyMW float64
    Priority        int
}

Critical minimum supply should be protected before lower-priority discretionary demand when load shedding becomes necessary.

Demand

Demand changes over time. Create:

type DemandSnapshot struct {
    Timestamp time.Time
    RegionID  string
    DemandMW  float64
}

The grid should be able to process a complete demand snapshot for all regions.

Generation Snapshot

Similarly:

type GenerationSnapshot struct {
    Timestamp        time.Time
    GeneratorID      string
    AvailableOutputMW float64
    CurrentOutputMW  float64
}

Base Grid

Create the following simplified grid. Power plants:

PP01 — North Nuclear
PP02 — West Gas
PP03 — River Hydro
PP04 — East Wind
PP05 — South Solar

Installed generation:

PP01 = 400 MW
PP02 = 220 MW
PP03 = 180 MW
PP04 = 140 MW
PP05 = 120 MW

Total installed generation:

1060 MW

Actual available generation may be lower.

Substations

Create:

S01
S02
S03
S04
S05
S06
S07
S08

Transmission Network

Create links:

S01 <-> S02
S01 <-> S03
S02 <-> S04
S02 <-> S05
S03 <-> S05
S03 <-> S06
S04 <-> S07
S05 <-> S07
S05 <-> S08
S06 <-> S08
S07 <-> S08

This topology provides multiple alternative routes.

Example Transmission Capacities

Use:

S01-S02 = 250 MW
S01-S03 = 220 MW
S02-S04 = 160 MW
S02-S05 = 180 MW
S03-S05 = 150 MW
S03-S06 = 170 MW
S04-S07 = 140 MW
S05-S07 = 160 MW
S05-S08 = 180 MW
S06-S08 = 150 MW
S07-S08 = 120 MW

Consumer Regions

Create six regions:

R01 — North
R02 — West
R03 — Central
R04 — East
R05 — South
R06 — Metropolitan

Example demand:

R01 = 120 MW
R02 = 135 MW
R03 = 180 MW
R04 = 110 MW
R05 = 125 MW
R06 = 250 MW

Total demand:

920 MW

Available Generation Scenario

Assume current available generation is:

PP01 = 360 MW
PP02 = 170 MW
PP03 = 150 MW
PP04 = 95 MW
PP05 = 75 MW

Total:

850 MW

Demand:

920 MW

Therefore:

Deficit = 70 MW

The system must determine how to respond to the deficit.

Generation and Demand Balance

Conceptually:

Generation + Storage Discharge
=
Supplied Demand + Storage Charge + Unallocated Surplus

The implementation should produce a balance report.

type GridBalance struct {
    TotalGenerationMW float64
    TotalDemandMW     float64
    TotalSuppliedMW   float64
    DeficitMW         float64
    SurplusMW         float64
}

Reserve Capacity

Some generation may be available but not currently active.

type GenerationReserve struct {
    GeneratorID       string
    AvailableReserveMW float64
    ActivationTime    time.Duration
}

Before load shedding, the system should attempt to use eligible reserve capacity according to the configured policy.

Energy Storage

Create storage systems.

type EnergyStorage struct {
    ID                string
    NodeID            string
    CapacityMWh       float64
    StoredEnergyMWh   float64
    MaxChargeMW       float64
    MaxDischargeMW    float64
    Status            StorageStatus
}

Possible states:

  • available
  • charging
  • discharging
  • maintenance
  • failed

Storage Constraints

The system must preserve:

0 <= StoredEnergyMWh <= CapacityMWh

and:

ChargeRate <= MaxChargeMW
DischargeRate <= MaxDischargeMW

Storage cannot discharge more energy than it currently contains.

Surplus Scenario

If:

Generation = 980 MW
Demand     = 900 MW

then:

Surplus = 80 MW

The system may:

  • charge storage
  • reduce generation
  • leave remaining surplus unallocated

according to configured rules.

Deficit Resolution

When demand exceeds generation, use the following conceptual order:

1. Increase available generation
2. Activate reserve generation
3. Discharge available storage
4. Reroute power where network constraints prevent delivery
5. Apply load shedding if deficit remains

Every decision must be recorded.

Load Shedding

Load shedding intentionally reduces supplied demand. Create:

type LoadSheddingAction struct {
    RegionID       string
    RequestedMW    float64
    ShedMW         float64
    RemainingMW    float64
    Reason         string
}

Load-Shedding Priority

For the base task, protect demand in this order:

critical
high
normal
low

Shedding should begin with:

low

priority demand. Within equal priority:

higher available shed capacity first

and then:

lower RegionID

for deterministic behavior.

Critical minimum supply should not be shed while lower-priority reducible demand remains available.

Load-Shedding Scenario

Suppose the unresolved deficit is:

70 MW

and available reducible demand is:

R05 — low    — 40 MW reducible
R04 — normal — 30 MW reducible
R03 — normal — 50 MW reducible
R06 — high   — 25 MW reducible

The system should first shed:

R05 = 40 MW

Remaining deficit:

30 MW

Then use eligible normal-priority demand according to the deterministic rule.

The final report must identify exactly where the 70 MW reduction occurred.

Network Reachability

Generation existing somewhere in the grid does not automatically mean it can reach every consumer.

The implementation must evaluate:

  • network connectivity
  • line status
  • line capacity
  • transformer capacity

A region may experience a local deficit even when total grid generation is sufficient.

Power Route

A simplified power route may be represented as:

type PowerRoute struct {
    Nodes       []string
    AllocatedMW float64
}

A route is valid only if every line on the path can accept the additional allocation.

Rerouting

When a transmission line fails:

remove that line from available topology

and determine whether affected power can be routed through alternative paths. For example:

S02 -> S05

fails. Possible alternatives may include:

S02 -> S01 -> S03 -> S05

or:

S02 -> S04 -> S07 -> S05

The implementation must verify capacity along the complete alternative route.

Bottleneck Capacity

For a path:

S02 -> S01 -> S03 -> S05

with available capacities:

S02-S01 = 80 MW
S01-S03 = 120 MW
S03-S05 = 45 MW

the maximum additional transfer through the path is:

45 MW

The path capacity is limited by its bottleneck.

Generator Failure

Create a failure scenario:

PP01 / Generator G01 fails

Lost production:

180 MW

The system must determine:

  • new total generation
  • new deficit
  • available reserve
  • available storage
  • network ability to redistribute power
  • required load shedding

Failure Propagation

A failure may affect more than one entity. Example:

        Generator Failure
                |
                v
        Generation Deficit
                |
                v
        Higher Flow on Alternative Lines
                |
                v
        Transmission Capacity Reached
                |
                v
        Local Deficit
                |
                v
        Load Shedding

The implementation must not stop analysis after detecting the first failure.

Transmission Line Failure

Create:

type GridFailure struct {
    ID         string
    EntityType string
    EntityID   string
    Timestamp  time.Time
    Reason     string
}

When a line fails:

Status = failed
CurrentFlowMW = 0

Existing allocations depending on the line must be reconsidered.

Overload Detection

An overload exists when a proposed or current flow exceeds allowed capacity.

CurrentFlowMW > CapacityMW

The system should detect overload before accepting a new allocation whenever possible.

Overload Resolution

Possible actions:

  • reduce transfer
  • use alternative route
  • increase local generation
  • discharge local storage
  • shed load

The system must record which action was selected.

Cascading Failure Scenario

Create a controlled simulation:

TL-05 fails

Its previous flow is redistributed. This causes:

TL-08

to exceed capacity. The system must:

  • detect the overload
  • prevent or isolate invalid flow
  • recalculate available routes
  • determine remaining supply
  • apply load shedding if required

The simulation should be bounded.

Do not implement an uncontrolled infinite failure cascade.

Maintenance

Grid components may enter planned maintenance. Examples:

- generator
- transformer
- transmission line
- storage system

Create:

type MaintenanceWindow struct {
    ID         string
    EntityType string
    EntityID   string
    StartTime  time.Time
    EndTime    time.Time
    Status     MaintenanceStatus
}

Maintenance Planning

Before approving maintenance, calculate whether the grid can still satisfy required demand.

The report should identify:

- lost capacity
- alternative generation
- alternative routes
- reserve requirements
- expected deficit
- expected load shedding

Maintenance should not be silently approved if it would violate configured critical-supply requirements.

Transformer Failure

If a transformer fails, downstream demand may become unreachable. The system must determine:

  • affected regions
  • affected critical consumers
  • alternative transformer path if one exists
  • unsupplied demand

Critical Supply

Critical consumers define:

MinimumSupplyMW

Example:

Hospital Complex = 12 MW
Water Treatment   = 8 MW
Telecom Core      = 5 MW

During severe deficit:

25 MW

must be protected for these consumers before discretionary lower-priority load.

Blackout

A consumer region enters blackout when:

SuppliedMW = 0

A partial supply reduction is:

brownout / load shedding

for the purposes of this exercise.

Islanding

A failure may split the grid into disconnected components. Example:

        Grid before failure:
        
            A --- B --- C
                  |
                  D
        
        After failures:
        
            A --- B
            
            C --- D

The system must detect independent connected components.

Each component must independently evaluate:

  • local generation
  • local demand
  • local storage
  • local deficit

A surplus in one island cannot supply another disconnected island.

Restoration

When failed infrastructure becomes available again, restoration should occur explicitly. Conceptually:

        component repaired
              |
              v
        topology restored
              |
              v
        capacity recalculated
              |
              v
        load shedding reduced
              |
              v
        normal supply restored

Restoration should prioritize:

  • critical
  • high
  • normal
  • low

consumers.

Restoration Event

type RestorationAction struct {
    RegionID       string
    RestoredMW     float64
    PreviousSupplyMW float64
    NewSupplyMW    float64
}

Grid Snapshot

The system should produce a complete snapshot.

type GridSnapshot struct {
    Timestamp          time.Time
    GenerationMW       float64
    DemandMW           float64
    SuppliedMW         float64
    StorageEnergyMWh   float64
    ActiveFailures     int
    OverloadedLines    []string
    UnsuppliedRegions  []string
}

Historical Metrics

Store snapshots over time. This allows queries such as:

  • peak demand
  • minimum generation
  • largest deficit
  • maximum storage usage
  • number of failures
  • total shed energy

Operational Event

Create:

type GridEvent struct {
    ID         string
    Timestamp  time.Time
    EntityType string
    EntityID   string
    EventType  string
    Details    string
}

Examples:

  • generator started
  • generator failed
  • line overloaded
  • line failed
  • storage discharge started
  • reserve activated
  • load shedding started
  • region restored
  • maintenance started
  • maintenance completed

Idempotency

Important commands should contain:

RequestID

Examples:

  • activate reserve
  • change generator output
  • start maintenance
  • report failure
  • restore component
  • execute load shedding

Processing the same request twice must not:

  • activate reserve twice
  • shed the same load twice
  • double-count generation
  • duplicate failure events
  • restore capacity twice

Grid Invariants

The implementation should preserve:

        generator output <= available generator capacity
        
        line flow <= active line capacity
        
        transformer load <= transformer capacity
        
        storage energy <= storage capacity
        
        storage energy >= 0
        
        supplied regional demand <= requested regional demand
        
        failed line carries no power
        
        failed generator produces no power
        
        disconnected islands cannot exchange power
        
        critical minimum supply is protected according to configured policy
        
        duplicate commands do not duplicate operational effects

Query Operations

Support:

  • get current grid balance
  • get power plant status
  • get generator status
  • get regional demand
  • get regional supply
  • get transmission-line load
  • get transformer load
  • get storage state
  • get active failures
  • get active maintenance
  • get overloaded components
  • get load-shedding actions
  • get critical consumer status
  • find alternative route
  • get connected grid components
  • get grid snapshot
  • get historical metrics

Queries must not modify grid state.

Command Operations

State-changing operations include:

  • set generator output
  • activate reserve generation
  • start storage charging
  • start storage discharge
  • report generator failure
  • report line failure
  • report transformer failure
  • restore component
  • schedule maintenance
  • start maintenance
  • complete maintenance
  • reroute allocation
  • execute load shedding
  • restore shed load

Validation

Validate:

  • duplicate IDs
  • unknown node
  • unknown plant
  • unknown generator
  • unknown region
  • unknown transmission line
  • unknown transformer
  • unknown storage system
  • negative demand
  • negative generation
  • negative capacity
  • generation above available capacity
  • flow above line capacity
  • transformer overload
  • storage overcharge
  • storage over-discharge
  • invalid maintenance interval
  • allocation through failed component
  • duplicate RequestID

Required Test Scenarios

Create tests for at least:

  • balanced generation and demand
  • generation surplus
  • generation deficit
  • reserve activation
  • storage charging
  • storage discharge
  • storage capacity limit
  • load shedding
  • critical consumer protection
  • deterministic shedding order
  • successful alternative route
  • route bottleneck calculation
  • no alternative route
  • generator failure
  • transmission-line failure
  • transformer failure
  • local deficit despite global generation surplus
  • overload detection
  • maintenance capacity analysis
  • maintenance rejection
  • grid island detection
  • independent island balancing
  • cascading overload scenario
  • component restoration
  • load restoration
  • duplicate failure request
  • duplicate load-shedding request
  • grid snapshot
  • historical metrics

Large Grid Scenario

Create a simulation containing at least:

  • 5 power plants
  • 10 generators
  • 8 substations
  • 12 transformers
  • 15 transmission lines
  • 6 consumer regions
  • 10 critical consumers
  • 3 storage systems

Simulate the following sequence:

1. Normal grid operation
2. Demand increases by 12%
3. Renewable generation falls
4. Reserve generator activates
5. Major transmission line fails
6. Power is rerouted
7. Alternative line reaches capacity
8. Storage begins discharging
9. Remaining deficit requires load shedding
10. Critical consumers remain protected
11. Failed line is repaired
12. Normal topology is restored
13. Shed demand is progressively restored
14. Storage begins recovery charging

After every step, generate a new:

GridSnapshot

and verify all grid invariants.

Modeling Goal

The purpose of this task is to model a distributed resource network where total capacity alone does not determine whether demand can be satisfied.

A useful conceptual architecture is:

        Generation Service
              |
              +-- Power Plants
              +-- Generators
              +-- Renewable Availability
              +-- Reserve Capacity
              |
              v
        Grid Topology
              |
              +-- Substations
              +-- Transformers
              +-- Transmission Lines
              |
              v
        Demand Service
              |
              +-- Consumer Regions
              +-- Critical Consumers
              +-- Demand Snapshots
              |
              v
        Grid Balancer
              |
              +-- Generation Allocation
              +-- Storage
              +-- Deficit Detection
              +-- Surplus Handling
              |
              v
        Network Allocation
              |
              +-- Route Capacity
              +-- Bottlenecks
              +-- Rerouting
              +-- Island Detection
              |
              v
        Grid Protection
              |
              +-- Overload Detection
              +-- Failure Handling
              +-- Load Shedding
              +-- Critical Supply
              |
              v
        Recovery
              |
              +-- Component Restoration
              +-- Load Restoration
              +-- Storage Recovery
              |
              v
        Operational History

The main challenge is understanding that:

enough total generation

does not necessarily mean:

every consumer can receive enough power

because power delivery is constrained by topology, component state, transfer capacity, storage availability, and failures.

Task 13 — Manufacturing and Production Line Management

Objective

Create a manufacturing and production-line management system.

The system manages factories, production lines, machines, products, raw materials, bills of materials, work orders, production batches, workers, maintenance, quality checks, warehouses, and finished-goods inventory.

The implementation must model the complete production lifecycle:

        Production Request
            ↓
        Material Validation
            ↓
        Material Reservation
            ↓
        Work Order
            ↓
        Production Line Assignment
            ↓
        Batch Production
            ↓
        Quality Control
            ↓
        Accepted / Rejected Units
            ↓
        Finished Goods Inventory

The system must also react to:

  • insufficient materials
  • machine failures
  • production-line downtime
  • rejected quality samples
  • partial batch completion
  • maintenance
  • delayed work orders
  • warehouse capacity limits

Domain Overview

Conceptually:

        Factory
          |
          +-- ProductionLine
          |      |
          |      +-- Machine
          |      +-- Worker
          |
          +-- RawMaterialWarehouse
          |
          +-- FinishedGoodsWarehouse

Product relationships:

        Product
           |
           +-- BillOfMaterials
                  |
                  +-- MaterialRequirement

Production relationships:

        ProductionRequest
              |
              v
        WorkOrder
              |
              +-- ProductionLine
              +-- MaterialReservation
              |
              v
        ProductionBatch
              |
              +-- MachineUsage
              +-- WorkerAssignment
              +-- QualityCheck
              |
              v
        FinishedGoods

Factory

Each factory contains:

  • ID
  • Name
  • Location
  • Production Lines
  • Raw Material Warehouse
  • Finished Goods Warehouse

A possible model is:

type Factory struct {
    ID                       string
    Name                     string
    RawMaterialWarehouseID   string
    FinishedGoodsWarehouseID string
}

Production Line

A production line belongs to a factory. Each line contains:

  • ID
  • Factory ID
  • Name
  • Supported Product Types
  • Maximum Units Per Hour
  • Status
  • Machines

For example:

type ProductionLine struct {
    ID                    string
    FactoryID             string
    Name                  string
    SupportedProductTypes []string
    MaxUnitsPerHour       int
    Status                ProductionLineStatus
}

Production Line Status

Possible states:

  • available
  • running
  • maintenance
  • failed
  • disabled

Only an available line may receive a new work order.

Machine

Each machine belongs to a production line. A machine contains:

  • ID
  • Production Line ID
  • Machine Type
  • Supported Operations
  • Maximum Throughput
  • Status
  • Last Maintenance

For example:

type Machine struct {
    ID                 string
    ProductionLineID   string
    Type               string
    SupportedOperations []string
    MaxUnitsPerHour    int
    Status             MachineStatus
}

Possible machine states:

  • available
  • running
  • maintenance
  • failed
  • disabled

Product

Each product contains:

  • ID
  • Name
  • Product Type
  • Unit Weight
  • Unit Volume

For example:

type Product struct {
    ID          string
    Name        string
    ProductType string
    UnitWeight  float64
    UnitVolume  float64
}

Raw Material

A raw material contains:

  • ID
  • Name
  • Unit
  • Current Stock
  • Reserved Stock
  • Minimum Stock

For example:

type RawMaterial struct {
    ID            string
    Name          string
    Unit          string
    CurrentStock  float64
    ReservedStock float64
    MinimumStock  float64
}

Available quantity is:

Available = CurrentStock - ReservedStock

Reserved stock must never exceed current stock.

Bill of Materials

Every manufactured product has a bill of materials. For example:

type BillOfMaterials struct {
    ProductID     string
    Requirements  []MaterialRequirement
}

with:

type MaterialRequirement struct {
    MaterialID      string
    QuantityPerUnit float64
}

Example Product

Create:

        Product ID: product-x100
        Name: Industrial Control Unit X100
        Product Type: electronic-control-unit

Bill of materials per unit:

        Aluminum Housing       = 1 unit
        Control Board          = 1 unit
        Power Module           = 1 unit
        Cooling Fan            = 2 units
        Copper Wire            = 4.5 meters
        Mounting Screw         = 8 units
        Thermal Compound       = 12 grams
        Packaging Box          = 1 unit

Production Request

A production request contains:

  • Request ID
  • Product ID
  • Requested Quantity
  • Priority
  • Deadline

For example:

type ProductionRequest struct {
    RequestID string
    ProductID string
    Quantity  int
    Priority  ProductionPriority
    Deadline  time.Time
}

Possible priorities:

  • normal
  • high
  • critical

Material Requirement Calculation

For:

Requested Quantity = Q
Material Per Unit   = M

required quantity is:

Required = Q * M

For 100 X100 units:

        Aluminum Housing = 100
        Control Board    = 100
        Power Module     = 100
        Cooling Fan      = 200
        Copper Wire      = 450 meters
        Mounting Screw   = 800
        Thermal Compound = 1200 grams
        Packaging Box    = 100

Material Validation

Before production starts, verify that all required materials are available.

The system must report all shortages.

For example:

        Control Board:
            required = 100
            available = 82
            missing = 18
        
        Cooling Fan:
            required = 200
            available = 190
            missing = 10

The implementation must not stop at the first missing material.

Material Reservation

If all required materials are available:

reserve all required materials

A material reservation may use:

type MaterialReservation struct {
    ID          string
    WorkOrderID string
    MaterialID  string
    Quantity    float64
    Status      ReservationStatus
}

Possible states:

  • reserved
  • consumed
  • released

The same stock must not be reserved twice.

Atomic Reservation

For the base task, material reservation is atomic.

Either:

all materials are reserved

or:

none are reserved

if any requirement cannot be satisfied.

Work Order

A production request that passes validation becomes a work order. For example:

type WorkOrder struct {
    ID                 string
    ProductionRequestID string
    ProductID          string
    Quantity           int
    ProductionLineID   string
    Status             WorkOrderStatus
}

Possible states:

  • created
  • materials_reserved
  • scheduled
  • running
  • paused
  • completed
  • failed
  • cancelled

Production Line Assignment

A work order may only be assigned to a production line when:

  • line supports product type
  • line status = available
  • required machines are available
  • line has sufficient throughput
  • deadline can reasonably be satisfied

Machine Requirements

A product may require multiple manufacturing operations. For example:

  • assembly
  • soldering
  • cooling-installation
  • testing
  • packaging

The selected production line must contain machines capable of all required operations.

Worker

A worker contains:

  • ID
  • Name
  • Skills
  • Shift
  • Status

For example:

type Worker struct {
    ID        string
    Name      string
    Skills    []string
    ShiftStart time.Time
    ShiftEnd   time.Time
    Status     WorkerStatus
}

Possible worker states:

  • available
  • assigned
  • off_shift
  • suspended

Worker Assignment

Some production operations require qualified workers. For example:

        assembly:
            skill = assembly
        
        electrical testing:
            skill = electrical-test
        
        quality inspection:
            skill = quality-control

A work order must not assign a worker who lacks the required skill.

Production Batch

A work order may be divided into batches. For example:

type ProductionBatch struct {
    ID              string
    WorkOrderID     string
    PlannedQuantity int
    ProducedQuantity int
    AcceptedQuantity int
    RejectedQuantity int
    Status          BatchStatus
}

Possible states:

created
running
quality_check
completed
failed

Batch Scenario

For:

Work Order Quantity = 100

split production into:

Batch 1 = 40
Batch 2 = 40
Batch 3 = 20

Each batch must be tracked independently.

Production Progress

The implementation must preserve:

  • planned quantity
  • produced quantity
  • accepted quantity
  • rejected quantity

For every batch.

The following must always hold:

accepted + rejected <= produced
produced <= planned

Material Consumption

Materials should be consumed according to actual produced quantity. For example:

40 units produced

means that material consumption corresponds to 40 units, not the entire work order.

Unused reserved material must eventually be released.

Machine Usage

Record which machines were used for every batch. A possible model:

type MachineUsage struct {
    BatchID    string
    MachineID  string
    StartTime  time.Time
    EndTime    time.Time
}

Machine Failure

During production, a machine may fail. Example:

machine-solder-02 failed

The implementation must determine:

  • which batch is affected
  • which work order is affected
  • whether the production line can continue
  • whether an alternative machine exists
  • whether production must pause

Machine Failure Scenario

Assume Batch 2 is running. At 13:20 machine-solder-02 fails.

If another compatible machine exists on the same line:

batch may continue after reassignment

Otherwise:

  • batch becomes paused
  • work order becomes paused

Maintenance

Machines have planned maintenance windows.

A possible model:

type MaintenanceWindow struct {
    ID        string
    MachineID string
    StartTime time.Time
    EndTime   time.Time
    Reason    string
}

Machines under active maintenance cannot be assigned to production.

Quality Check

Every batch must pass quality control. For example:

type QualityCheck struct {
    ID              string
    BatchID         string
    InspectedUnits  int
    PassedUnits     int
    FailedUnits     int
    Result          QualityResult
}

Possible results:

  • passed
  • partially_passed
  • failed

Quality Scenario

Batch:

Produced = 40

Quality result:

Accepted = 37
Rejected = 3

Only the accepted units may enter finished-goods inventory.

Quality Failure Threshold

For the base task:

if more than 10% of inspected units fail,
the batch requires manual review

For:

40 inspected
5 failed

failure percentage is:

12.5%

Therefore:

manual review required

Rejected Units

Rejected products must not enter normal finished-goods inventory. They may enter:

  • scrap
  • rework
  • manual_review

A possible model:

type RejectedUnitRecord struct {
    BatchID  string
    Quantity int
    Reason   string
    Action   string
}

Rework

Some rejected units may be eligible for rework. For example:

  • 3 rejected units
  • 2 can be reworked
  • 1 must be scrapped

If rework succeeds:

reworked accepted units

may enter finished-goods inventory.

Finished Goods Inventory

Accepted units are added to finished-goods inventory. For example:

type FinishedGoodsInventory struct {
    ProductID string
    Quantity  int
}

After:

37 accepted units

inventory increases by:

+37

Finished Goods Warehouse Capacity

The finished-goods warehouse may have limited capacity. A possible model:

type WarehouseCapacity struct {
    MaximumUnits int
    CurrentUnits int
}

Production completion must not silently exceed warehouse capacity.

If there is insufficient warehouse capacity:

  • batch may complete production
  • but finished goods cannot be fully stored

The system must report the blocked quantity.

Work Order Completion

A work order is complete when:

  • all batches are completed
  • all accepted units are processed
  • all remaining reserved materials are consumed or released

The final work-order result should include:

  • requested quantity
  • produced quantity
  • accepted quantity
  • rejected quantity
  • scrapped quantity
  • reworked quantity
  • final inventory increase

Production Shortfall

It is possible for:

accepted quantity < requested quantity

because of rejected or scrapped units. The implementation must report the shortfall. For example:

requested = 100
accepted = 94
shortfall = 6

The source system may then create a follow-up production request.

Cancellation

A work order may be cancelled before production begins. Allowed:

created -> cancelled
materials_reserved -> cancelled
scheduled -> cancelled

When cancellation occurs:

reserved materials must be released

A running work order should not be cancelled without an explicit stop policy. For the base task:

running work orders cannot be directly cancelled

Priority Scheduling

When several work orders compete for the same production line, process priority in this order:

  • critical
  • high
  • normal

Within the same priority:

earlier deadline first

Then:

WorkOrder ID ascending

Multiple Factory Scenario

Create at least two factories. For example:

  • factory-1
  • factory-2

Both factories may manufacture the same product but have:

  • different line capacity
  • different material inventory
  • different machine availability

The system should determine which factory can fulfill a production request.

Factory Selection

For a new production request, evaluate:

  • material availability
  • compatible production line
  • machine availability
  • worker availability
  • estimated completion time
  • warehouse capacity

The selected factory must satisfy all required conditions.

Supply Delivery

Raw materials may arrive from suppliers. A material delivery contains:

  • Delivery ID
  • Material ID
  • Warehouse ID
  • Quantity
  • Timestamp

For example:

type MaterialDelivery struct {
    ID          string
    MaterialID  string
    WarehouseID string
    Quantity    float64
    Timestamp   time.Time
}

Delivery increases raw-material stock.

Material Delivery Scenario

Example:

Control Board +100
Cooling Fan +250
Copper Wire +1000 meters

The update must target the correct warehouse and material.

Low Stock Warning

When:

CurrentStock - ReservedStock < MinimumStock

the system should report a low-stock condition.

This warning does not automatically block production unless the required quantity is unavailable.

Audit History

Every important state-changing operation should generate an audit event. Examples:

  • production request created
  • materials reserved
  • work order scheduled
  • batch started
  • machine failed
  • batch paused
  • quality check completed
  • units rejected
  • units reworked
  • finished goods stored
  • work order completed

A possible model:

type AuditEvent struct {
    ID         string
    Timestamp  time.Time
    EntityType string
    EntityID   string
    Action     string
    Details    string
}

Idempotency

Production requests should contain:

RequestID

If the same request is processed twice, the system must not create duplicate work orders or reserve materials twice.

Material deliveries should also have unique delivery IDs. Processing the same delivery twice must not duplicate stock.

Queries

The system should support queries such as:

  • get available materials
  • get material shortages
  • get production-line status
  • get machine status
  • get active work orders
  • get batches for work order
  • get quality results
  • get finished-goods inventory
  • get low-stock materials
  • get machine failure history
  • get production history for product

Queries must not mutate production state.

Commands

State-changing operations include:

  • create production request
  • reserve materials
  • create work order
  • schedule work order
  • start batch
  • pause batch
  • resume batch
  • record machine failure
  • complete batch production
  • perform quality check
  • record rework
  • store finished goods
  • cancel work order
  • process material delivery

Validation

The implementation should validate:

  • duplicate IDs
  • unknown factory
  • unknown production line
  • unknown machine
  • unknown product
  • unknown material
  • unknown worker
  • invalid quantity
  • negative inventory
  • reserved stock greater than current stock
  • unsupported product type
  • missing required machine operation
  • worker missing required skill
  • invalid state transition
  • maintenance conflict
  • warehouse capacity exceeded
  • duplicate request ID
  • duplicate delivery ID

Required Test Scenarios

Create tests for at least:

  • successful production request
  • material shortage
  • atomic reservation rollback
  • successful work-order scheduling
  • unsupported product on production line
  • machine failure with replacement machine
  • machine failure without replacement machine
  • maintenance machine excluded
  • worker skill mismatch
  • successful batch production
  • quality check passed
  • quality check partially passed
  • manual review threshold
  • rework
  • scrap
  • finished-goods inventory update
  • warehouse capacity exceeded
  • work-order cancellation
  • reserved material release
  • priority scheduling
  • factory selection
  • material delivery
  • duplicate production request
  • duplicate material delivery
  • low-stock warning

Modeling Goal

The purpose of this task is to model a production system where inventory, machines, workers, quality control, and production state all interact.

A useful conceptual architecture is:

        Product Catalog
              |
              +-- Product
              +-- Bill of Materials
              |
              v
        Material Service
              |
              +-- Stock
              +-- Reservation
              +-- Delivery
              |
              v
        Production Planner
              |
              +-- Factory Selection
              +-- Line Selection
              +-- Scheduling
              |
              v
        Work Order Service
              |
              +-- Batches
              +-- Machine Usage
              +-- Worker Assignment
              |
              v
        Quality Service
              |
              +-- Inspection
              +-- Rework
              +-- Scrap
              |
              v
        Finished Goods Inventory
              |
              v
        Audit History

The main challenge is keeping production state, material state, machine state, worker assignments, quality results, and warehouse inventory consistent throughout the complete manufacturing lifecycle.

Task 14 — International Airport and Air Traffic Network

Objective

Create a large air-traffic and airport-operations management system for a network of ten airports.

The system manages:

  • airports
  • runways
  • terminals
  • gates
  • aircraft
  • aircraft types
  • airlines
  • flights
  • flight schedules
  • flight legs
  • passengers
  • tickets
  • baggage
  • cargo
  • pilots
  • cabin crew
  • crew assignments
  • ground crews
  • gate assignments
  • runway assignments
  • fuel requirements
  • aircraft maintenance
  • weather restrictions
  • delays
  • cancellations
  • diversions
  • rerouting
  • connections
  • airport capacity
  • air-traffic sectors
  • operational events

The implementation must coordinate resources across the entire airport network while preserving scheduling, capacity, safety, and operational constraints.

This task is intentionally large.

The goal is to model a realistic distributed transportation system where many independent entities affect each other.

Airport Network

Create ten airports:

AP01 — London
AP02 — Paris
AP03 — Frankfurt
AP04 — Madrid
AP05 — Rome
AP06 — Belgrade
AP07 — Istanbul
AP08 — Dubai
AP09 — New York
AP10 — Tokyo

Use these IDs throughout the task.

Airport

Each airport contains:

  • ID
  • Name
  • City
  • Country
  • Timezone
  • Terminals
  • Runways
  • Gates
  • Maximum Hourly Arrivals
  • Maximum Hourly Departures

For example:

type Airport struct {
    ID                    string
    Name                  string
    City                  string
    Country               string
    Timezone              string
    MaxHourlyArrivals     int
    MaxHourlyDepartures   int
}

Terminal

Each terminal belongs to an airport. A terminal contains:

  • ID
  • Airport ID
  • Name
  • Supported Flight Types
  • Gates

Supported flight types may include:

  • domestic
  • international
  • cargo

Gate

Each gate contains:

  • ID
  • Terminal ID
  • Maximum Aircraft Size
  • Supported Flight Types
  • Status

Possible gate states:

  • available
  • occupied
  • maintenance
  • closed

A gate may only serve aircraft compatible with its size and flight type.

Runway

Each runway contains:

  • ID
  • Airport ID
  • Length
  • Supported Aircraft Categories
  • Status

Possible states:

  • available
  • occupied
  • maintenance
  • closed
  • weather_restricted

Aircraft Type

Aircraft types define physical and operational characteristics. A possible model:

type AircraftType struct {
    ID                 string
    Manufacturer       string
    Model              string
    PassengerCapacity  int
    CargoCapacityKg    float64
    FuelCapacityLiters float64
    MinimumRunwayM     int
    Category           string
}

Example aircraft types:

A320
A321
B737
B787
A350
B777

Aircraft

Each aircraft contains:

  • ID
  • Airline ID
  • Aircraft Type ID
  • Registration
  • Current Airport
  • Current Status
  • Flight Hours
  • Cycles

Possible states:

  • available
  • scheduled
  • boarding
  • in_flight
  • maintenance
  • grounded
  • delayed

Airline

Create several airlines.

For example:

AL01
AL02
AL03
AL04
AL05

Each airline contains:

  • ID
  • Name
  • Home Airport
  • Aircraft Fleet

Flight

A flight represents a commercial service. For example:

type Flight struct {
    ID             string
    AirlineID      string
    FlightNumber   string
    OriginAirport  string
    DestinationAirport string
    ScheduledDeparture time.Time
    ScheduledArrival   time.Time
    AircraftID     string
    Status         FlightStatus
}

Possible states:

  • scheduled
  • boarding
  • gate_closed
  • taxiing
  • departed
  • in_flight
  • landed
  • completed
  • delayed
  • cancelled
  • diverted

Flight Leg

A route may contain multiple legs. For example:

London -> Belgrade -> Istanbul -> Dubai

A flight itinerary may therefore contain several flight legs. A possible model:

type FlightLeg struct {
    ID               string
    FlightID         string
    OriginAirportID  string
    DestinationAirportID string
    DepartureTime    time.Time
    ArrivalTime      time.Time
}

Base Route Network

Create scheduled routes between the ten airports. At minimum, include:

AP01 <-> AP02
AP01 <-> AP03
AP01 <-> AP09

AP02 <-> AP04
AP02 <-> AP05
AP02 <-> AP06

AP03 <-> AP06
AP03 <-> AP07
AP03 <-> AP10

AP04 <-> AP05
AP04 <-> AP09

AP05 <-> AP06
AP05 <-> AP07

AP06 <-> AP07
AP06 <-> AP08

AP07 <-> AP08
AP07 <-> AP10

AP08 <-> AP09
AP08 <-> AP10

AP09 <-> AP10

The network must support both direct flights and connecting itineraries.

Airport Time Zones

Every airport has its own timezone. Flight scheduling must preserve:

  • UTC timestamp
  • local departure time
  • local arrival time

Internal processing should use a consistent absolute time representation.

Local time should be derived for display.

Airport Capacity

Every airport has:

  • maximum arrivals per hour
  • maximum departures per hour

The scheduler must reject or reschedule flights that exceed airport capacity.

For example:

AP06
Max Arrivals Per Hour = 12
Max Departures Per Hour = 12

If 13 departures are scheduled inside the same operational hour:

one flight must be moved or rejected

Runway Scheduling

Every departure and arrival requires a runway slot. A possible model:

type RunwaySlot struct {
    AirportID string
    RunwayID  string
    FlightID  string
    StartTime time.Time
    EndTime   time.Time
    Operation string
}

Operation:

  • takeoff
  • landing

Two flights must not occupy the same runway at overlapping times.

Gate Scheduling

Flights require gate occupancy before departure and after arrival. A gate assignment contains:

type GateAssignment struct {
    FlightID   string
    AirportID  string
    GateID     string
    StartTime  time.Time
    EndTime    time.Time
}

Gate assignments must not overlap.

Aircraft Size Compatibility

Each gate supports a maximum aircraft category. For example:

  • small
  • medium
  • widebody

An A350 or B777 must not be assigned to a gate that only supports medium aircraft.

Runway Compatibility

Each aircraft type has:

MinimumRunwayM

The airport must contain an available runway whose length is sufficient.

Aircraft Scheduling

The same aircraft cannot operate overlapping flights.

The scheduler must also consider turnaround time. For example:

        Flight A arrival: 14:00
        Minimum turnaround: 60 minutes
        Next departure: must be >= 15:00

Aircraft Turnaround

Turnaround may include:

  • passenger unloading
  • baggage unloading
  • cleaning
  • refueling
  • catering
  • crew change
  • boarding

A possible model:

type TurnaroundRequirement struct {
    AircraftTypeID string
    MinimumMinutes int
}

Passenger

Each passenger contains:

  • ID
  • First Name
  • Last Name
  • Passport Number
  • Nationality

Ticket

A ticket connects:

  • Passenger
  • Flight
  • Seat
  • Booking

For example:

type Ticket struct {
    ID          string
    PassengerID string
    FlightID    string
    Seat        string
    Status      TicketStatus
}

Possible states:

  • reserved
  • confirmed
  • checked_in
  • boarded
  • cancelled
  • used

Capacity Validation

The number of confirmed passengers must not exceed aircraft passenger capacity. For example:

Aircraft Capacity = 180
Confirmed Tickets = 181

must be rejected.

Booking

A passenger may book an itinerary containing multiple flights. For example:

AP06 -> AP03 -> AP10

The system must verify connection feasibility.

Connection Time

Every airport has minimum connection time. For example:

AP03 Minimum Connection Time = 50 minutes

For:

Flight 1 arrives = 10:00
Flight 2 departs = 10:35

connection is invalid.

Missed Connection

If an inbound flight is delayed and the passenger can no longer make the connection:

connection becomes missed

The system should search for an alternative route.

Baggage

Each baggage item contains:

  • ID
  • Passenger ID
  • Flight ID
  • Weight
  • Current Airport
  • Status

Possible states:

  • checked
  • loaded
  • in_transit
  • unloaded
  • transferred
  • delivered
  • lost

Baggage Weight

Every ticket may have a baggage allowance. The implementation should detect excess baggage.

Baggage Connection

For connecting passengers, baggage must also transfer between flights. For example:

AP06 -> AP03 -> AP10

At AP03:

baggage must move from inbound aircraft to outbound aircraft

Cargo

Flights may carry cargo.

Cargo contains:

  • ID
  • Weight
  • Volume
  • Origin
  • Destination
  • Priority
  • Type

Possible cargo types:

  • standard
  • perishable
  • fragile
  • medical
  • hazardous

Aircraft cargo capacity must not be exceeded.

Cargo Restrictions

Some aircraft or airports may not support hazardous cargo.

The implementation must validate:

  • aircraft capability
  • airport handling capability
  • cargo type

Pilot

A pilot contains:

  • ID
  • Name
  • Licenses
  • Certified Aircraft Types
  • Maximum Duty Hours
  • Current Duty Hours
  • Current Status

Possible status:

  • available
  • assigned
  • off_duty
  • suspended

Cabin Crew

Cabin crew contains:

  • ID
  • Name
  • Qualified Aircraft Types
  • Maximum Duty Hours
  • Current Duty Hours
  • Status

Crew Assignment

Every flight requires:

  • pilot
  • co-pilot
  • minimum cabin crew

A possible model:

type CrewAssignment struct {
    FlightID       string
    PilotIDs       []string
    CabinCrewIDs   []string
}

Crew Validation

Crew must satisfy:

  • correct aircraft certification
  • available during flight
  • no overlapping assignment
  • duty-hour limit
  • minimum rest period

Duty Time

For example:

Maximum duty = 10 hours
Already used = 8 hours
New flight duty = 3 hours

assignment must be rejected.

Ground Crew

Each airport may have ground teams. Teams may handle:

  • baggage
  • fuel
  • catering
  • cleaning
  • pushback
  • cargo
  • maintenance

A flight turnaround may require several ground-service assignments.

Fuel

Every flight requires estimated fuel. A possible simplified model:

type FuelRequirement struct {
    FlightID        string
    RequiredLiters  float64
    ReserveLiters   float64
}

Total fuel must not exceed aircraft capacity.

Airport Fuel Stock

Airports may also track fuel inventory. For example:

type FuelInventory struct {
    AirportID      string
    AvailableLiters float64
}

Refueling reduces airport fuel inventory.

Aircraft Maintenance

Aircraft maintenance may depend on:

  • flight hours
  • flight cycles
  • calendar date
  • reported defects

A possible model:

type MaintenanceRequirement struct {
    AircraftID      string
    DueFlightHours  float64
    DueCycles       int
    DueDate         time.Time
}

Maintenance State

An aircraft due for mandatory maintenance must not be scheduled for a new flight.

Aircraft Failure

An aircraft may report a technical problem before departure.

Example:

Flight F120
Aircraft AC17
Engine sensor fault

The aircraft becomes:

grounded

The system must determine whether a replacement aircraft is available.

Replacement Aircraft

A replacement aircraft must:

  • belong to compatible airline pool
  • support required passenger capacity
  • support required cargo
  • be available at departure airport
  • satisfy route/runway constraints
  • not have overlapping schedule

Weather

Airports may have weather conditions. For example:

type WeatherCondition struct {
    AirportID  string
    Timestamp  time.Time
    WindSpeed  float64
    Visibility float64
    Storm      bool
}

Weather Restrictions

Weather may cause:

  • reduced runway capacity
  • departure delays
  • arrival delays
  • runway closure
  • airport closure

For example:

Visibility < minimum

may prevent landing for some aircraft categories.

Airport Closure Scenario

Assume:

AP07 closed from 14:00 to 18:00

due to severe weather. The system must determine:

  • which departures are affected
  • which arrivals are affected
  • which aircraft are already in flight
  • which passengers have connections through AP07
  • which cargo routes use AP07

Flight Delay

A delay contains:

  • Flight ID
  • Original Time
  • New Time
  • Reason

Delay reasons may include:

  • weather
  • technical
  • crew
  • airport_capacity
  • late_aircraft
  • security

A delay may propagate to later flights using the same aircraft.

Delay Propagation

For example:

        Aircraft AC10
        
        Flight F1:
            arrival delayed by 90 minutes
        
        Flight F2:
            same aircraft scheduled 45 minutes after original arrival

Flight F2 must also be delayed because turnaround is no longer possible.

Cancellation

A flight may be cancelled. The system must then process:

  • passengers
  • baggage
  • cargo
  • crew assignments
  • gate assignment
  • runway slot
  • aircraft schedule
  • connections

Resources reserved for the cancelled flight must be released.

Diversion

An aircraft already in flight may be unable to land at its destination.

The system must select a diversion airport. A valid diversion airport must satisfy:

  • runway length
  • aircraft category
  • airport status
  • available arrival capacity
  • fuel range

Diversion Scenario

Flight:

AP06 -> AP07

AP07 closes after departure. Candidate diversion airports:

AP03
AP05
AP08

The implementation must determine which are valid.

The airport network must support itinerary search.

Search parameters may include:

  • origin
  • destination
  • departure date
  • maximum connections
  • maximum total travel time

The system should return valid route combinations.

Direct and Connecting Routes

For:

AP06 -> AP10

possible routes may include:

AP06 -> AP03 -> AP10
AP06 -> AP07 -> AP10
AP06 -> AP08 -> AP10

The implementation must verify actual flight schedules and connection times.

Route Cost

An itinerary may be evaluated by:

  • total duration
  • number of connections
  • price

The base task should support at least:

shortest total travel time

Passenger Rebooking

When a flight is cancelled or a connection is missed, search for a replacement itinerary.

The replacement must:

  • start from current airport
  • reach original destination
  • have enough seat capacity
  • respect connection times

Cargo Rerouting

Cargo affected by cancellation or diversion may also require rerouting.

Cargo constraints must still apply after rerouting.

Air Traffic Sector

Create logical air-traffic sectors between airport regions. A possible model:

type AirTrafficSector struct {
    ID              string
    MaxActiveFlights int
    ActiveFlightIDs []string
}

Sector Capacity

A sector must not exceed:

MaxActiveFlights

If the sector is full, new flights may need:

  • delay
  • alternate route
  • holding

Flight Path

A flight may pass through several air-traffic sectors. For example:

AP01 -> AP09

may traverse:

sector-west-europe
sector-atlantic-east
sector-atlantic-west
sector-us-east

Holding

An arriving flight may enter a holding state when no landing slot is available.

Possible flight state:

holding

Holding increases:

  • flight duration
  • fuel consumption

Fuel Reserve

An aircraft must maintain reserve fuel.

A flight must not remain in holding when projected fuel would fall below required reserve. At that point:

diversion becomes mandatory

Flight State Machine

A normal flight lifecycle may be:

        scheduled
           ↓
        boarding
           ↓
        gate_closed
           ↓
        taxiing
           ↓
        departed
           ↓
        in_flight
           ↓
        landed
           ↓
        completed

Alternative states:

  • delayed
  • cancelled
  • holding
  • diverted

Invalid transitions must be rejected.

Example Invalid Transitions

Examples:

scheduled -> landed
completed -> boarding
cancelled -> departed
in_flight -> boarding

Gate Conflict Scenario

Two flights are assigned to:

Gate G12

Intervals:

Flight A: [12:00 - 13:00]
Flight B: [12:30 - 13:30]

This is invalid. The system must detect the overlap.

Runway Conflict Scenario

Two operations use the same runway:

Flight A landing: [14:00 - 14:05]
Flight B takeoff: [14:03 - 14:08]

This is invalid.

Aircraft Conflict Scenario

Aircraft:

AC20

is assigned to:

Flight F200: [10:00 - 13:00]
Flight F201: [12:00 - 15:00]

This is invalid.

Crew Conflict Scenario

Pilot:

P100

is assigned to overlapping flights. The second assignment must be rejected.

Passenger Connection Scenario

Passenger itinerary:

AP06 -> AP03 -> AP10

Flights:

F610:
    AP06 -> AP03
    08:00 - 09:30

F320:
    AP03 -> AP10
    10:30 - 22:00

Minimum connection at AP03:

50 minutes

Connection time:

60 minutes

Valid.

If F610 arrives at:

09:50

connection becomes:

40 minutes

and is no longer valid.

Passenger Rebooking Scenario

The system must search for another itinerary from:

AP03

to:

AP10

after 09:50.

Airport Operational Snapshot

Create a snapshot containing:

type AirportNetworkSnapshot struct {
    Airports         []Airport
    Flights          []Flight
    Aircraft         []Aircraft
    GateAssignments  []GateAssignment
    RunwaySlots      []RunwaySlot
    CrewAssignments  []CrewAssignment
}

A snapshot represents network state at a specific moment.

Event History

The system must preserve operational history. A possible model:

type OperationalEvent struct {
    ID         string
    Timestamp  time.Time
    EntityType string
    EntityID   string
    EventType  string
    Details    string
}

Examples:

  • flight scheduled
  • gate assigned
  • runway assigned
  • boarding started
  • flight delayed
  • aircraft grounded
  • replacement aircraft assigned
  • flight departed
  • flight diverted
  • flight landed
  • flight completed

Idempotency

Operational commands should contain unique request IDs where duplicate processing could cause state corruption.

Examples:

  • ticket purchase
  • flight creation
  • gate assignment
  • fuel update
  • maintenance completion
  • baggage load event

Processing the same request twice must not duplicate the effect.

Queries

The system should support queries such as:

  • all departures from airport
  • all arrivals to airport
  • all flights for aircraft
  • all flights for passenger
  • all available gates
  • all available runways
  • all delayed flights
  • all cancelled flights
  • all flights affected by airport closure
  • all passengers affected by cancellation
  • all missed connections
  • all aircraft due for maintenance
  • all crews currently assigned
  • all baggage for flight
  • all cargo for flight
  • all active flights in air-traffic sector

Commands

State-changing operations include:

  • schedule flight
  • assign aircraft
  • assign gate
  • assign runway
  • assign crew
  • sell ticket
  • check in passenger
  • load baggage
  • load cargo
  • start boarding
  • delay flight
  • cancel flight
  • depart flight
  • record landing
  • complete flight
  • ground aircraft
  • schedule maintenance
  • refuel aircraft
  • divert flight
  • rebook passenger
  • reroute cargo

Large Network Scenario

Create at least:

  • 10 airports
  • 5 airlines
  • 30 aircraft
  • 80 scheduled flights
  • 250 passengers
  • 100 baggage items
  • 40 cargo shipments
  • 40 pilots
  • 80 cabin crew members

The numbers are intentionally large enough to create realistic interactions between resources.

Required Failure Scenarios

The implementation must include scenarios for:

  • airport closure
  • runway closure
  • gate unavailable
  • aircraft technical failure
  • crew unavailable
  • crew duty limit exceeded
  • weather delay
  • airport capacity exceeded
  • flight cancellation
  • missed connection
  • aircraft schedule conflict
  • gate conflict
  • runway conflict
  • passenger overbooking
  • cargo capacity exceeded
  • baggage connection failure
  • fuel shortage
  • holding with low fuel
  • diversion
  • maintenance due

Required Test Scenarios

Create tests for at least:

  • valid direct flight scheduling
  • valid connecting itinerary
  • invalid connection time
  • gate assignment
  • gate overlap rejection
  • runway assignment
  • runway overlap rejection
  • aircraft overlap rejection
  • aircraft turnaround validation
  • passenger-capacity validation
  • cargo-capacity validation
  • crew certification
  • crew overlap rejection
  • crew duty-time rejection
  • maintenance aircraft excluded
  • airport hourly capacity exceeded
  • weather delay
  • delay propagation
  • flight cancellation
  • resource release after cancellation
  • passenger rebooking
  • missed connection
  • baggage transfer
  • cargo rerouting
  • airport closure
  • diversion airport selection
  • holding
  • fuel reserve violation
  • flight-state transition
  • duplicate operational request

Modeling Goal

The purpose of this task is to model a complex transportation network where many independent resources and state machines interact.

A useful conceptual architecture is:

        Airport Registry
              |
              +-- Airports
              +-- Terminals
              +-- Gates
              +-- Runways
              |
              v
        Flight Scheduler
              |
              +-- Flight Plans
              +-- Aircraft Assignment
              +-- Gate Scheduling
              +-- Runway Scheduling
              |
              v
        Passenger Service
              |
              +-- Booking
              +-- Ticketing
              +-- Connections
              +-- Rebooking
              |
              v
        Baggage and Cargo Service
              |
              +-- Loading
              +-- Transfer
              +-- Capacity
              +-- Rerouting
              |
              v
        Crew Management
              |
              +-- Certification
              +-- Duty Time
              +-- Assignment
              |
              v
        Aircraft Operations
              |
              +-- Fuel
              +-- Maintenance
              +-- Turnaround
              +-- Replacement Aircraft
              |
              v
        Air Traffic Management
              |
              +-- Sector Capacity
              +-- Holding
              +-- Diversion
              +-- Airport Closure
              |
              v
        Operational Event History

The main challenge is keeping flight schedules, airport capacity, aircraft state, crew assignments, passenger connections, baggage, cargo, fuel, maintenance, and air-traffic constraints consistent across the complete network.

Technical Position Separation


Separation of Positions

One of the things that creates unnecessary confusion at the beginning of a technical career is the number of different job titles.

You will encounter titles such as:

  • Software Developer
  • Software Engineer
  • Frontend Developer
  • Backend Developer
  • QA Engineer
  • DevOps Engineer
  • SysOps Engineer
  • Cloud Engineer
  • Software Architect

And very quickly, a simple question appears:

What is the actual difference between all of them?

Before we separate these roles, there is something important you need to understand.

Job titles in software engineering are not standardized.

Two companies may use completely different titles for people doing almost exactly the same work.

One company may call someone a Backend Developer, another may call the same position a Software Engineer, while a third may use Platform Engineer.

The opposite also happens.

Two people may both have the title Software Engineer, while the scope, complexity, and responsibility of their work are completely different.

Because of that, do not become obsessed with titles.

Focus on:

  • what the person actually builds;
  • what they are responsible for;
  • what decisions they are allowed to make;
  • what systems they understand;
  • how much responsibility they carry;
  • how independently they can operate;
  • how wide their technical perspective is.

That tells you much more than the title itself.

For the purposes of this Task Library, however, I want to establish a practical separation between several major roles.

This is not an attempt to create an absolute industry standard.

It is a model that will help you understand the different directions in which your technical development can move.


Software Developer

A Software Developer is primarily focused on implementing software functionality.

The central question is usually:

What needs to be built, and how do I implement it correctly?

A developer spends a significant amount of time working directly with:

  • application code;
  • business logic;
  • libraries;
  • frameworks;
  • APIs;
  • databases;
  • tests;
  • debugging;
  • feature implementation;
  • bug fixing;
  • code maintenance.

A strong developer should be able to receive a functional requirement and transform it into working software.

That does not mean blindly translating tickets into code.

A good developer should understand what they are building and why they are building it.

But the primary focus is still implementation.


Frontend Software Developer

A Frontend Developer works primarily on the part of the system that users directly interact with.

This may include:

  • web interfaces;
  • desktop interfaces;
  • mobile interfaces;
  • dashboards;
  • forms;
  • visualization;
  • interaction logic;
  • client-side state;
  • communication with backend APIs.

The work often involves technologies related to:

  • HTML;
  • CSS;
  • JavaScript or TypeScript;
  • frontend frameworks;
  • browser APIs;
  • accessibility;
  • responsive design;
  • client-side performance;
  • UI testing.

A frontend developer should not think only in terms of making something visually attractive.

The frontend is software.

It contains:

  • state;
  • data flows;
  • error handling;
  • validation;
  • concurrency;
  • networking;
  • caching;
  • security considerations;
  • performance constraints.

The visual interface is only the visible part of that system.


Backend Software Developer

A Backend Developer works primarily on the parts of a system that operate behind the user-facing interface.

This may include:

  • APIs;
  • business logic;
  • authentication;
  • authorization;
  • databases;
  • background workers;
  • message processing;
  • distributed communication;
  • data validation;
  • caching;
  • persistence;
  • service integration.

A backend developer may work with:

  • Go;
  • Rust;
  • C++;
  • Crystal;
  • Java;
  • Python;
  • C#;
  • databases;
  • queues;
  • networking protocols;
  • operating-system interfaces;
  • cloud or infrastructure services.

At the beginning, backend development may appear to be primarily about receiving a request, processing some data, and returning a response.

At higher levels, it becomes significantly broader. You begin dealing with:

  • concurrency;
  • consistency;
  • fault tolerance;
  • latency;
  • resource management;
  • distributed systems;
  • observability;
  • scalability;
  • storage design;
  • security;
  • deployment constraints.

The deeper you go, the less the work is about individual endpoints and the more it becomes about systems.


Software Engineer

The terms Software Developer and Software Engineer are frequently used interchangeably.

In many companies, there is no practical distinction at all.

For the purposes of this book, however, I make a useful distinction.

A Software Developer is primarily concerned with:

How do I implement this functionality?

A Software Engineer increasingly becomes concerned with:

How should this functionality exist inside the entire system?

That difference becomes extremely important.

Software engineering includes development, but it extends beyond implementation.

An engineer should increasingly think about:

  • architecture;
  • system boundaries;
  • interfaces;
  • failure modes;
  • performance;
  • scalability;
  • maintainability;
  • observability;
  • deployment;
  • security;
  • resource usage;
  • compatibility;
  • testing strategy;
  • long-term technical consequences.

Writing code is still an important part of the work. But code is no longer the entire problem.


Frontend Software Engineer

A Frontend Software Engineer may write the same frontend code as a Frontend Developer, but the expected perspective becomes broader.

Instead of thinking only about individual screens or components, the engineer may also think about:

  • frontend architecture;
  • application-wide state management;
  • rendering strategies;
  • performance budgets;
  • module boundaries;
  • dependency management;
  • accessibility architecture;
  • frontend observability;
  • security boundaries;
  • deployment models;
  • long-term maintainability.

The distinction is primarily one of scope and responsibility, not necessarily programming language or framework.


Backend Software Engineer

The same principle applies on the backend.

A Backend Software Developer may be asked to implement a service.

A Backend Software Engineer may also need to determine:

  • whether that service should exist at all;
  • what its boundaries should be;
  • how it communicates with other services;
  • how failures propagate;
  • how data consistency is maintained;
  • how the service behaves under load;
  • how it is deployed;
  • how it is monitored;
  • how it is upgraded;
  • how backward compatibility is preserved;
  • what happens when dependencies become unavailable.

Consider a simple requirement:

Store a user record.

A developer may immediately think about:

        POST /users
                ↓
        validate request
                ↓
        insert into database
                ↓
        return response

An engineer may additionally ask:

        Who owns this data?
                ↓
        What is the consistency model?
                ↓
        Can the operation be retried safely?
                ↓
        What happens during partial failure?
                ↓
        Do we need idempotency?
                ↓
        How is the data migrated?
                ↓
        How is it backed up?
                ↓
        Who is authorized to modify it?
                ↓
        How do we observe failures?
                ↓
        What happens at 100x the current load?

The implementation is still necessary.

But the surrounding questions define the engineering problem.


Quality Assurance

Quality Assurance is another area that is frequently misunderstood. QA is not simply:

Find bugs after developers finish working.

That is an extremely limited interpretation.

The purpose of QA is to help determine whether the system behaves according to its expected requirements and quality standards.

Depending on the organization, QA work may include:

  • manual testing;
  • automated testing;
  • integration testing;
  • regression testing;
  • API testing;
  • UI testing;
  • performance testing;
  • compatibility testing;
  • exploratory testing;
  • test planning;
  • release validation;
  • defect analysis.

A strong QA engineer does not only ask:

Does this button work?

They may ask:

  • What happens if the request is repeated?
  • What happens if the network fails halfway through?
  • What happens with malformed input?
  • What happens under concurrent access?
  • What happens after an upgrade?
  • What happens if the dependency is unavailable?
  • What happens on another operating system?
  • What happens when the system receives unexpected data?

Good QA work requires curiosity.

It requires the ability to think about how something can fail rather than only how it is supposed to work.

That ability is extremely valuable.


QA Automation Engineer

As systems become larger, manual testing alone becomes insufficient.

QA Automation Engineers build systems that automatically verify software behavior. Their work may include:

  • test frameworks;
  • automated regression suites;
  • API test systems;
  • browser automation;
  • integration environments;
  • test data generation;
  • load testing;
  • CI integration;
  • failure reporting.

At this point, the separation between development and QA becomes smaller.

A strong automation engineer is also a software engineer.

They write software whose purpose is to test other software.


DevOps

The term DevOps requires some care.

Originally, DevOps describes a set of practices and a culture intended to reduce the separation between software development and software operations.

It is not simply a collection of tools. However, the industry widely uses job titles such as DevOps Engineer.

Therefore, when we discuss the position, we are usually talking about someone working around the boundary between development and operations.

Typical responsibilities may include:

  • CI/CD;
  • build pipelines;
  • deployment automation;
  • infrastructure automation;
  • containers;
  • orchestration;
  • monitoring;
  • logging;
  • secrets management;
  • release processes;
  • environment management.

A DevOps engineer may work with systems such as:

  • Git;
  • CI platforms;
  • Docker;
  • Kubernetes;
  • Terraform;
  • Ansible;
  • monitoring systems;
  • artifact registries;
  • cloud services.

But again, knowing how to write a YAML file does not automatically make someone a DevOps engineer.

The real value comes from understanding the complete path between source code and a running production system.

For example:

        Source Code
            ↓
        Build
            ↓
        Tests
            ↓
        Artifact
            ↓
        Distribution
            ↓
        Deployment
            ↓
        Configuration
            ↓
        Runtime
            ↓
        Monitoring
            ↓
        Failure Detection
            ↓
        Recovery

A good DevOps engineer understands this chain and works to make it reliable, repeatable, observable, and automated.


SysOps

SysOps is more directly focused on operating systems and running infrastructure.

A SysOps engineer may be responsible for:

  • Linux or Windows servers;
  • operating-system configuration;
  • users and permissions;
  • filesystems;
  • networking;
  • services;
  • process management;
  • storage;
  • backups;
  • system monitoring;
  • patching;
  • system security;
  • hardware resources;
  • incident response.

If DevOps is heavily concerned with the path between development and deployment, SysOps is often more concerned with:

What happens after the system is actually running?

A strong SysOps engineer understands what the operating system is doing underneath your application.

That knowledge can become extremely valuable to software engineers as well.

If your application suddenly consumes:

        100% CPU

or:

        64 GB RAM

or starts producing:

        10,000 open sockets

you eventually leave the comfortable world of application code.

You need to understand the machine.


Cloud Engineer

A Cloud Engineer works with infrastructure and services provided through cloud platforms.

Typical areas may include:

  • compute;
  • networking;
  • storage;
  • databases;
  • identity;
  • access management;
  • load balancing;
  • autoscaling;
  • monitoring;
  • infrastructure as code;
  • managed services.

Common cloud platforms include:

  • AWS;
  • Microsoft Azure;
  • Google Cloud Platform;
  • private cloud infrastructure.

Cloud engineering overlaps significantly with:

  • DevOps;
  • SysOps;
  • networking;
  • security;
  • infrastructure engineering.

But there is an important mistake you should avoid.

Cloud engineering is not learning where buttons are located inside a cloud dashboard.

A cloud service is still built on fundamental computing concepts. You should understand:

  • Compute
  • Networking
  • Storage
  • Identity
  • Security
  • Databases
  • Distributed Systems

before thinking that knowing the name of a managed cloud product means understanding the underlying problem.

Products change. Interfaces change. Names change.

Fundamental concepts survive.


Software Architect

Software Architecture is one of the roles that I believe should be approached with particular care.

An Architect is not simply someone who stopped writing code and started drawing boxes.

An architect is expected to understand systems at a much wider level.

That may include:

  • business requirements;
  • technical requirements;
  • system boundaries;
  • communication models;
  • data ownership;
  • scalability;
  • security;
  • reliability;
  • deployment;
  • infrastructure;
  • performance;
  • integration;
  • operational complexity;
  • long-term maintainability.

An architect constantly makes trade-offs. For example:

  • Performance VS Simplicity
  • Consistency VS Availability
  • Development speed VS Long-term maintainability
  • Operational complexity VS System flexibility
  • Cost VS Redundancy

There is rarely a perfect architecture. The architect's job is to strive for the ideal solution.

There are architectures that are appropriate for a particular set of requirements and constraints. That distinction is extremely important.

A system designed for 100 users does not necessarily need the same architecture as a system designed for 100,000,000 users.

And building the second architecture for the first problem can be just as bad as building the first architecture for the second problem.

Architecture requires judgment. Judgment requires experience. And experience requires exposure to both successful and unsuccessful decisions.


An Architect Should Understand Development

Personally, I place very little value on architecture that exists only in diagrams.

If you design systems that other engineers must implement, you should understand what those decisions mean at implementation level.

You should understand the difference between saying:

We will simply introduce a distributed cache here.

and actually dealing with:

  • cache invalidation;
  • consistency;
  • network failures;
  • serialization;
  • memory limits;
  • eviction;
  • monitoring;
  • deployment;
  • failure recovery.

The same applies to databases, queues, microservices, event-driven systems, distributed storage, and almost everything else.

Drawing:

Service A
    ↓
  Queue
    ↓
Service B

takes several seconds.

Making that system reliable in production may take months.

Never confuse the simplicity of a diagram with the complexity of its implementation.


The Roles Overlap

Do not imagine these positions as completely isolated boxes. In reality, they overlap heavily.

A Backend Engineer may understand:

  • Linux;
  • networking;
  • deployment;
  • databases;
  • cloud infrastructure.

A DevOps Engineer may write significant amounts of software.

A SysOps Engineer may automate almost everything with code.

A QA Automation Engineer may build large distributed testing systems.

A Cloud Engineer may need deep networking knowledge.

An Architect may still write production code. The borders are not walls.

A better mental model looks something like this:

                    Software Architecture
                           /     \
                          /       \
                 Software Engineering
                   /             \
                  /               \
          Frontend                Backend
                                    \
                                     \
                                      DevOps
                                     /     \
                                    /       \
                               SysOps       Cloud
                                  \          /
                                   \        /
                                    Platform

                 QA / Automation
                       |
                       |
             interacts with all layers

The exact shape differs between organizations.

The point is that knowledge begins to overlap as your level increases.


Do Not Rush Toward Titles

One of the biggest mistakes you can make early in your career is to chase titles instead of capabilities. Do not think:

How quickly can I become a Senior?

or:

How quickly can I become an Architect?

Ask instead:

  • What can I build independently?
  • What systems do I understand?
  • What kinds of failures have I experienced?
  • What decisions can I make responsibly?
  • How much complexity can I manage?
  • Can other people depend on my technical decisions?
  • Can I explain why a system behaves the way it does?
  • Can I recognize when I do not know enough to make a decision?

A title can be assigned in a day. Capability cannot.

You can put Software Architect into a profile immediately. That does not create ten years of technical depth.


Responsibility Grows With Scope

A useful way to think about technical progression is through the scope of responsibility.

At the beginning, your responsibility may be:

Function

Then:

Module

Then:

Application

Then:

Service

Then:

Multiple Services

Then:

System

And eventually:

Entire Technical Ecosystem

As the scope increases, the nature of the questions changes.

At function level:

Does this code work?

At service level:

Does this service behave correctly?

At system level:

What happens when this service fails?

At architecture level:

Should this service exist in the first place?

That progression is much more meaningful than the title written next to your name.


Choose a Direction, but Do Not Build Walls Around Yourself

At some point, specialization becomes necessary.

You cannot develop every technical field to the same depth.

There simply is not enough time.

You may decide that your primary direction is:

  • backend engineering;
  • frontend engineering;
  • infrastructure;
  • cloud;
  • security;
  • databases;
  • distributed systems;
  • QA automation;
  • architecture.

That is completely normal.

But specialization should not mean ignorance of everything around you.

A strong backend engineer benefits enormously from understanding:

  • operating systems;
  • networking;
  • databases;
  • deployment;
  • infrastructure.

A frontend engineer benefits from understanding:

  • HTTP;
  • APIs;
  • caching;
  • authentication;
  • browser internals;
  • backend constraints.

A DevOps engineer benefits from understanding how software is actually written.

An architect benefits from understanding all of them.

The deeper you progress in one direction, the more useful neighboring knowledge becomes.


Final Perspective

Do not define yourself exclusively by your current job title.

Think about your technical spectrum.

Think about what you understand. Think about what you can build.

Think about what you can diagnose when it breaks. Think about the decisions you are capable of making.

Most importantly, understand the difference between:

  • Knowing that something exists
  • Understanding how it works
  • Being able to design, implement, operate, debug, and improve it yourself
  • Apply it and implement on another place or subject

Those are completely different levels of knowledge.

Your long-term goal should not be to collect as many titles as possible.

Your goal should be to continuously expand the range of problems you are capable of understanding and solving.


Scalionix Docs

Keyboard Shortcuts

Navigate the documentation without leaving the keyboard.
Navigation
Previous subject
←
Next subject
→
Previous subsection
Alt + ↑
Next subsection
Alt + ↓
Interface
Documentation Home
Ctrl + Enter
Search
Alt + Q
Open shortcuts
?
Close dialog
Esc
Scalionix Docs

Search Documentation