January 12, 2026
For years, forming student teams was a manual process driven by intuition and prone to failure. This project aimed to operationalize that intuition, replacing guesswork with an optimization engine designed to minimize social friction and maximize peer learning.
The project spans two distinct implementations. It begins with a prototype that validated a core psychological hypothesis: that skill homogeneity mitigates social friction in project-based learning environments. It concludes with a deep dive into systems engineering, detailing how I rewrote the core engine using a modern systems programming language to tackle critical performance bottlenecks.
The following report documents the complete project lifecycle, from the initial behavioral analysis and mathematical formalization to the final systems-level implementation. It illustrates how bridging the gap between high-level domain logic and low-level memory optimization creates a robust, scalable solution to a complex resource allocation problem.
In our high-intensity technical bootcamps, week-long projects serve as a critical active break from the fast-paced curriculum. These sprints have a dual pedagogical purpose: they allow students to consolidate recently taught material and build core competencies through hands-on application. While the primary goal is learning and skill acquisition, these projects also build the portfolios students later use to secure employment. During this week, direct instruction is paused, and students work with considerable autonomy. In this environment, success depends less on individual brilliance and more on the effectiveness of group dynamics.
Historically, cohorts of 15-25 students were partitioned into teams manually by the teaching staff. This process was guided entirely by experience, familiarity with students, and professional intuition. However, this manual approach had two fundamental weaknesses. First, it was unscalable; forming teams for a single cohort could consume several hours of an educator's time. Balancing out all the different variables, from skill level to availability and thematic preference, was not an easy task, and in some more complex cohorts it could even take up an entire day. Second, it was susceptible to bias. While a teacher's assessment of competency is valuable, the method relied too heavily on subjective beliefs about what constitutes an effective team, often leading to suboptimal groupings.
These limitations frequently resulted in preventable interpersonal conflicts. While some issues stemmed from personality clashes, my observations indicated that the root cause was rarely personal. It was structural.
The Friction
Through observation, I identified that the primary driver of team friction was the magnitude of the skill gap between the strongest and the weakest member. As this gap widened, the peer-to-peer dynamic weakened. This triggered a compounding cycle of dysfunction:
- Carrying: Advanced students often worked at a significantly faster pace. Driven by their own personal standards and strict project deadlines, they felt compelled to complete the complex architectural work alone. This created a dual failure mode. It led to burnout for the lead student, since they were effectively carrying the team's weight on their shoulders. Simultaneously, it benched the rest of the team, denying them the opportunity to interact with the core codebase and preventing them from gaining the hands-on experience the project was meant to provide.
- Disengagement: The least experienced students in high-gap groups often felt paralyzed. The speed at which advanced members solved problems was overwhelming. Moreover, the complexity of the challenges they self-imposed was often beyond the reach of less experienced peers, which contributed to a sense of imposter syndrome. Ultimately, instead of asking questions and trying to keep pace, the gap was so large that these students disengaged to avoid slowing down the team. The result was that they effectively learned nothing during the project.
Both effects are symptoms of a breakdown in collaboration. Beyond impeding skill acquisition, in extreme scenarios, this dynamic led to students dropping out altogether.
Status Quo
The prevalence of these issues was not accidental. It was a direct result of trying to map a corporate solution onto an educational problem.
In a corporate setting, professional teams follow a division of labor model. Tech companies organize employees into small, agile groups where each member is responsible for a specific part of the product. These teams are effective because they are generally balanced in terms of professional competence and are assembled through hiring filters designed to find specific talent.
Historically, the teaching staff tried to mirror this dynamic. They deliberately constructed heterogeneous groups, pairing stronger lead students with less experienced peers, expecting them to work similarly to how a professional team would. The rationale was that this natural role differentiation would allow teams to build more complex applications, producing better portfolio pieces for their job hunt and our marketing campaigns. It was also assumed that this grouping of students would serve as a direct practice for workflows found in professional environments.
However, a corporate environment is fundamentally different from a pedagogical one. A business can use its hiring process to assemble a team with somewhat comparable skill levels. Furthermore, businesses often work in a highly hierarchical structure as well, which enables a junior-senior kind of dynamic. An educational institution admits students based on broader criteria creating a wider spectrum of initial skills. More importantly, student teams lack the hierarchical structure required to absorb those disparities. In a student team, the structure is flat. Even the most advanced student is still a student. Their primary goal is to be challenged and learn, not to manage a junior developer. They lack the authority to direct their peers and the experience to mentor them effectively.
Applying this model to this flat and constrained system introduced critical failure points. The goal of a professional team is to deliver a product, while the goal of a student team is to learn. When students in these mixed-skill groups attempted to split the work to mimic a corporate structure, they struggled to integrate their contributions. This created a dependency on expert mediation that was fundamentally broken. The issue was not just the volume of tutoring required during project weeks, but also the very nature of student behavior during a crisis. Crucially, when students hit a wall, they rarely sought help proactively. Instead, they tended to freeze and stay silent, rendering timely intervention impossible. Moreover, the argument that role diversity produces better portfolio projects proved misleading. That benefit assumes a long-term project where teams have time to build chemistry and workflows. In a single-week sprint, there is no runway for that cohesion to develop.
Homogeneity
This realization led to a critical decision: to prioritize the learning process over the final deliverable. I chose to abandon the corporate simulation. Instead, the focus was placed strictly on maximizing intra-group skill homogeneity.
The core logic is straightforward. When students are at a comparable skill level, they are forced to confront challenges together. Because they operate with a similar mental model of the problem, communication increases. They cannot rely on a senior team member to solve the problem for them; they have to work together, learn together. While a corporate team collaborates to deliver, these student teams collaborate to survive the challenge. This approach prevents the carrying effect and ensures every team member remains an active participant.
The Engineering Goal
The objective was to operationalize these psychological observations into a reproducible system. My efforts focused on replacing manual intuition with an optimization engine designed primarily for risk reduction in social dynamics.
By strictly bounding the skill range within a team, the system enforces a baseline for effective technical communication. The priority was to minimize the probability of interpersonal conflict and isolation, ensuring that every student had a peer within their immediate zone of development.
Crucially, the system design relied on a foundational hypothesis: that team health is the leading indicator for all other success metrics. I operated under the assumption that a psychologically safe environment would trigger a cascade of downstream effects, driving deeper peer-to-peer learning and higher student satisfaction (NPS). I also hypothesized that the tangible success of delivering a high-quality project, built through genuine collaboration, would bolster student self-efficacy. By proving to themselves what they were capable of, students would approach the complexity of subsequent material with increased confidence and momentum.
Mathematical Formalization
To solve this, I proposed to transition from subjective intuition to a formal optimization model. The grouping challenge was framed as a Multi-Objective Set Partitioning Problem (SPP).
Given a set of students , the goal is to find a partition such that every student belongs to exactly one subset (team) , satisfying specific size constraints while maximizing a global utility function.
The Search Space
The complexity of this problem precludes brute-force solutions. For a standard cohort of partitioned into groups of sizes 3 to 5, the search space is discrete, non-convex, and combinatorially explosive. This landscape justifies the use of metaheuristic approaches over deterministic solvers, as finding the global optimum is less critical than finding a robust, "good enough" local optimum within a reasonable timeframe.
Feature Engineering
A core engineering challenge was dimensionality reduction. To make the problem tractable, I engineered a composite scalar metric called Workforce ().
I defined for a student as the product of their composite skill level and their dedicated effort:
Where:
- Effort: Total hours the student committed to dedicating to the project.
- Skill: An unweighted product of grades, tutor assessment, and the student's self-efficacy (measured by an item in a survey).
The Abstraction Trade-off: This product formula introduces a deliberate abstraction. A score of could result from a high-skill student with limited hours, from a novice student with massive dedication, or even from a low-skilled student with inflated self-efficacy. From a resource allocation perspective, I treated technical talent and time as fungible assets; either can be used to "pay" for the project's completion. Empirical testing during the PoC phase showed that the model remained robust with this simplified engineering feature. While this abstraction theoretically permits teams with high skill variances, the bootcamp's admissions process effectively neutralizes this edge case. Since all students are pre-filtered for high commitment, variance is minimal in practice, leaving as the principal component of .
The Fitness Function
I defined a parametrized fitness function to evaluate the quality of a partition. The function is a weighted sum of five objectives, where each weight can be manually calibrated for alignment with business goals.
I. Intra-Group Homogeneity () — Primary Priority
To minimize the carrying effect, we minimize the range between the strongest and weakest member of each team. For a team , let and be the maximum and minimum Workforce scores. The homogeneity score calculates the normalized tightness of this range:
II. Temporal Synchronization () — Secondary Priority
We mathematically model student availability as a discrete set of time slots. To ensure collaboration is logistically possible, we maximize the global Jaccard Index of the team. Crucially, this is calculated as the intersection of availability for all members against their union (not pairwise averages), ensuring strictly common slots for the entire group:
III. Inter-Group Balance () — Tertiary Priority
To ensure fairness, we minimize the deviation of each team's total capacity from the cohort target (). This was modeled as a ratio to ensure a normalized score between 0 and 1:
IV & V. Social Boosters () — Low Priority
Finally, the algorithm considers Affinity (shared interests/hobbies) and Geography (location matches) as first-class citizens in the optimization loop, albeit with significantly lower weights. These act as soft guides for the solver when the primary mathematical constraints are equally met by multiple candidates. Both were modeled as a slight modification of the global Jaccard Index used for temporal synchronization. Specifically, it was modeled as a sloped all-or-nothing fitness metric. To prevent the formation of teams that can potentially marginalize a minority member, the metric penalizes partial matches. This prioritizes scenarios where all members share an attribute, rather than just a subset. The parameter was used to control the slope, and it was set to .
Constraints
The optimization engine operates within strict boundaries:
- Topology Constraint: Team sizes must be strictly bounded between 3 and 5 members ().
- Inclusivity Constraint: and . Every student must appear exactly once.
The Algorithmic Strategy
With the mathematical objective defined, now I needed a solver capable of traversing the discrete, non-convex search space. Traditional gradient-based methods were inapplicable as no gradients exist in set partitions, and brute force was so computationally expensive that it was off the table.
I selected an Evolutionary Strategy approach. Unlike deterministic algorithms, an ES embraces stochasticity to escape local optima, iteratively refining a population of candidate solutions toward the global maximum.
Constraint Preservation
Standard Genetic Algorithms typically rely on Crossover (Sexual Reproduction), combining parts of Parent A and Parent B to create an offspring.
In the context of Set Partitioning, Crossover is structurally destructive. Merging half of the teams from Partition A with half from Partition B almost invariably results in an invalid state:
- Duplication: Student appears in both halves.
- Omission: Student appears in neither.
Repairing these invalid chromosomes is computationally expensive and, more importantly, it biases the search. Therefore, I engineered an Asexual Evolutionary Engine. Instead of mating, the system relies on Mitosis (cloning) followed by high-chance () mutations. The intelligence of the search is not in the combination of solutions, but in the specific design of the mutation operators themselves.
Lifecycle
The engine operates on a strict generational loop designed to balance stability (exploiting good solutions) with pressure (exploring new ones):
- Evaluation: Every candidate partition is scored using the Fitness Function .
- Selection: To prevent population explosion (or extinction), we enforce a strict survival rate of 50%.
- Elitism: The top 1% of solutions survive automatically and unconditionally. This ensures that the best-known configuration is never lost due to random chance.
- Rank-Biased Probabilistic Survival: The remaining slots are filled stochastically based on rank. While higher-fitness candidates have a higher probability of survival, lower-fitness candidates still retain a non-zero chance of passing to the next generation. This mechanism is critical to maintain genetic diversity, preventing the algorithm from converging prematurely on a local optimum that is good but not great.
- Mitosis: Surviving candidates clone themselves to replenish the population back to capacity.
- Mutation: Clones undergo stochastic modification detailed below.
Mutation
A critical feature of this system is that the number of teams () is not fixed; it is a variable to be optimized within the bounds of group size (). This means that the genome can have an arbitrary number of chromosomes (teams).
I implemented four distinct mutation operators, and to explore the dynamic topology I had to be a little creative with some of them. When a candidate is selected for mutation, only one of these operations is applied probabilistically:
- Swap Genes (High Probability): Two students from different teams exchange places. This is the primary mechanism for fine-tuning. It allows the system to optimize across all fitness objectives (homogeneity, availability, balance) without disrupting the structural topology of the groups. It is a low-volatility move designed to climb local gradients.
- Move Gene (Medium Probability): A student moves from Team to Team . This acts as a load balancer. It alters the size distribution, allowing the system to fix under-filled or over-filled teams. It allows the algorithm to migrate members from a group of 5 to a group of 3, refining constraint satisfaction.
- Dissolve Chromosome (Low Probability): A specific team is destroyed. Its members are distributed into other existing teams. This operator reduces , effectively compacting the partition. It forces the system to explore denser configurations (larger average group sizes) and eliminates fragmented or low-fitness outlier groups.
- Nucleate Chromosome (Low Probability): The inverse of dissolve. The algorithm scavenges single members from varying teams to form a new, valid team. This operator increases , allowing the system to relieve pressure from large groups. It expands the topology, creating new space to resolve conflicts where students might not fit well in any existing group.
By balancing these operators, the algorithm naturally converges not just on the right people for each team, but on the optimal number of teams for the specific cohort.
The Proof of Concept
Before committing to a high-performance solution, I needed to validate the core hypothesis: Could the mathematical model actually produce psychologically viable teams?
I chose Python for the initial implementation to prioritize development velocity. Beyond the algorithm itself, I architected an end-to-end data pipeline, including a custom ingestion module that extracted assessment grades directly from the company's internal API. This ensured the model was fed with fresh, high-fidelity data rather than static exports.
Validation
Defining "success" in a live educational environment presented an ethical dilemma. Rigorous A/B testing would involve providing a potentially inferior grouping service to half the cohort, which was deemed unacceptable.
Instead, I established a conservative heuristic benchmark. I compared the algorithm's output against manual assignments performed by experienced educators. To ensure independence, these educators designed their partitions without seeing the algorithmic proposal, following the core principles we had discussed previously. The metric was "Perceived Fitness": when shown both options side-by-side, which one did they prefer?
This evaluation method contains an inherent bias. Educators are naturally inclined to prefer solutions they invested time in creating, since admitting that a machine outperformed their professional intuition induces cognitive dissonance. Consequently, this acted as a high-confidence threshold. The fact that the algorithm consistently matched or exceeded human preference, despite this adverse bias, provided strong validation that the model was producing psychologically viable teams.
Architectural Constraints
While the logic was sound, the runtime characteristics posed a challenge. To ensure convergence within the non-convex search space, the Evolutionary Strategy required a population of nearly a thousand candidates. Consequently, running a standard cohort () through the necessary generation cycles took between 2 to 3 minutes on a standard machine.
For a manual CLI tool, this latency is acceptable. However, the long-term vision was to integrate this engine into a fully automated pipeline triggered by calendar events. In this context, potentially running in resource-constrained environments or serverless functions, a multi-minute runtime introduces fragility and unnecessary cost.
I identified three theoretical bottlenecks inherent to the Python runtime for this specific workload, although no profiling was performed:
- Object Overhead: In an Evolutionary Strategy, thousands of candidate solutions are generated and discarded per second. In Python, every
Teaminstance is a heap-allocatedPyObjectwith significant metadata overhead. - Garbage Collection: The massive churn of short-lived objects (due to the 50% generation cull rate) triggers constant GC cycles, pausing execution repeatedly.
- Pointer Chasing: Since Python lists store references to objects scattered across the heap, the CPU is unable to leverage cache locality. Therefore, the fitness loop was dominated by memory lookups rather than arithmetic processing.
Why not Numba? I had also considered using JIT compilers like Numba to patch these performance issues. However, the domain logic relied heavily on set operations and graph-like relationships rather than simple matrix arithmetic. The heavy reliance on set operations contributed to the excessive runtime and precluded effective JIT optimization. Ultimately, I decided against forcing Python to act like a low-level language. The first iteration was meant to be a throwaway proof of concept anyway, so I re-architected the optimization core in a language natively designed for memory control, ensuring a lightweight, portable binary without heavy runtime dependencies.
The Rewrite
The primary goal of this phase was to eliminate the runtime bottleneck inherent in the Python prototype. While the logic was sound, the execution needed to be orders of magnitude faster to become usable in a fully automated production environment.
So, I selected Zig for the rewrite. While Rust or C++ are the industry standards for this domain, I prioritized developer velocity once again. As the sole maintainer, the most pragmatic choice was the systems language I had most experience with.
I was fully aware that introducing a niche language creates technical debt regarding future maintenance. However, I considered this an acceptable trade-off, almost a no-brainer I dare to say. The codebase is small, self-contained, very well documented, and logically explicit; a future maintainer could read the Zig source almost as pseudo-code or port it to C++ with minimal effort. I had also considered C for this rewrite; and it would've been a great choice, as it reduced the bus factor and I was fairly comfortable with it. Zig, however, offered the low-level control of C combined with modern developer ergonomics and safety features, such as checked arithmetic and spatial memory protection. It allowed me to write safe, performant code while keeping all the freedom and explicitness C has to offer, if not more.
Data Oriented Design
The initial Python prototype relied heavily on abstractions that proved costly at scale. For the rewrite, I avoided simply porting the logic syntax-for-syntax and I took my time to think through the implementation and re-architect the entire thing.
I simplified the data structures to their bare minimum. Zig's focus on memory layout guided me toward primitives rather than objects, leading to a design that was significantly leaner and cache-friendly by default.
- The Team: In Python, a team was a list of Student references. In Zig, I redesigned the Team as a thin wrapper struct over a single
u64bitmask. Since the cohort size () comfortably fits within a 64-bit integer, a team is represented simply by toggling bits. - The Partition: The collection of teams (the genome) was implemented as a contiguous dynamic array.
Conceptually, this created a sparse matrix-like structure for each one of the solutions, where each row was a u64 team representation, and each column was a single bit of information encoding student membership. As for most matrix representations, this one too had a contiguous memory layout.
This architectural shift yielded a massive performance dividend. By converting heavy heap objects into simple arrays of primitives, I eliminated the pointer chasing overhead. This also drastically reduced the entire memory footprint of the data, which improved cache utilization by reducing evictions caused by metadata overhead. Crucially, the Team entity transformed into a value small enough to fit entirely within a CPU register, which opened the door to some interesting performance optimizations.
The bitwise strategy extended naturally to the temporal availability checks. In Python, I relied on set theory logic which involved hashing and iterating over collection objects. In Zig, I mapped the weekly schedule (21 slots) to a u32. This allowed me to replace loops with bitwise operators. Using Zig's standard library, I utilized @popCount, a builtin that compiles down to a single hardware instruction (like POPCNT in x86) to count the set bits, making the intersection logic exceptionally fast.
// NOTE: Not the actual implementation
fn jaccard(cohort: Cohort, team: TeamMask) f32 {
var intersection: ScheduleMask = std.math.maxInt(ScheduleMask);
var union_mask: ScheduleMask = 0;
var bits: u64 = team.students;
while (bits != 0) {
const index = @ctz(bits);
const schedule = cohort.availability[index];
intersection &= schedule;
union_mask |= schedule;
bits &= (bits - 1);
}
const total_slots = @popCount(union_mask);
if (total_slots == 0) return 0.0;
const common_slots = @popCount(intersection);
return @as(f32, @floatFromInt(common_slots)) / @as(f32, @floatFromInt(total_slots));
}
This same logic applied to the social boosters (). Shared interests and geographic locations were similarly encoded as bitmasks, reducing complex intersection calculations to a handful of hardware instructions.
Since the students themselves were modeled as bit positions in a u64, operations like membership checks or metadata access also became simple bitwise operations. Each student's metadata attribute (availability, workforce, etc.) was stored in a custom data structure that resembled a Struct of Arrays pattern. The entire cohort was stored in a monolithic struct where each field was an array representing a specific attribute of all students (e.g. workforce: []u16, availability: []u32). Accessing all metadata of one specific student had expensive side effects (cache line evictions), but iterating over one specific attribute at a time was fairly cheap in comparison, which is what the fitness function required as it computed each fitness metric score one at a time. This specific design pattern was possible thanks to comptime capabilities of Zig. While this could also be modeled with macros if I had decided to use C, C++ or Rust, Zig's metaprogramming was so much cleaner and easier to work with.
The Custom Parser
Another key component worthy of a brief mention was the data ingestion. Since the Zig ecosystem lacked (and still lacks at the time of writing) a maintained generic CSV library, I got to write a custom parser that tokenized the input stream. This allowed the engine to load and validate the cohort data with minimal overhead, strictly parsing only what was necessary for the internal representation.
Memory Management
In the Python version, the GC was a primary bottleneck. In Zig, the shift to manual management provided stable performance.
For the population storage, I utilized a standard GeneralPurposeAllocator. While I did not implement more advanced allocation strategies at this stage (a decision revisited in the Retrospective), simply moving to manual memory management removed the GC pauses. The performance gains were a compound effect of the bitwise data structures and the removal of runtime overhead.
The Interop
While the algorithm needed to be fast, the data loading did not. Writing C-bindings to link Python and Zig directly in memory felt like unnecessary complexity for this use case.
Instead, I opted for a loosely coupled architecture. The Python ETL pipeline dumps the processed student data into a sanitized CSV. Then, it spawns the Zig program as a subprocess, which reads this CSV, runs the optimization, and streams the result to stdout. Python captures this stream and parses the result. This kept the architecture modular and allowed me to focus on the optimization logic without fighting build systems or complex linking. More importantly, it made it that much easier to reason about and debug.
The Result
The performance improvement was over 200x. The runtime dropped from ~3 minutes to milliseconds.
Impact & Retrospective
Since rigorous A/B testing was not possible in a live educational environment, I gauged the system's impact by observing the long-term stability of the cohorts.
Business Impact
The most immediate difference was that the last-day team crisis, or worse, the post-deadline complaints, simply ceased to happen. Previously, I could rely on at least one group per cohort imploding due to personality clashes or unmanageable skill gaps, requiring staff mediation. The algorithm didn't necessarily produce a dream team every time, but it reliably prevented these disaster scenarios. This peace was the strongest validation that the core hypothesis (risk reduction via homogeneity) was correct.
This stability created downstream effects. Fewer initial conflicts meant fewer lingering grievances. I also noticed a significant increase in a specific phenomenon: teams approached me with requests to remain together for subsequent projects far more frequently. This was a clear signal that the groupings were not just functional, but psychologically safe and effectively balanced.
That said, let's address the elephant in the room: these results are observational. While the correlation between the system's deployment and the stability of the cohorts is strong, external factors in the curriculum or student selection could also have played a role.
Finally, one notable impact was the operational time saved. Previously, teaching staff would spend half a day building teams that often proved to be dysfunctional. This was a minimum 4 hour cost per teacher per month. Now, this entire process takes a few minutes. It's fast, it doesn't disrupt anyone's workflow, and it yields better results. That is a huge win in my book.
Performance
The transition from the Python prototype (2+ minutes) to the Zig engine (<1 second) did more than just save time; it fundamentally changed how I operated as an engineer.
With the prototype script, the tool was a black box. I would run it once, maybe twice, and we had to work with whatever it produced. The high latency discouraged experimentation. The Zig rewrite transformed it into an interactive exploration tool. I could generate a dozen distinct partitions in a minute, allowing me to apply professional judgment to a set of machine-vetted, high-quality choices. I could see concrete trade-offs: one partition might offer perfect homogeneity but sideline a student with a tricky schedule; another might widen the skill gap slightly to keep a local group of students together. This was ultimately what allowed me to balance out the default hyperparameter configuration for the engine itself.
Crucially, this speed acted as a diagnostic tool. Because I was now generating hundreds of variations, I began to notice statistical patterns that were invisible when I was running single batches.
Mathematical Flaws
The interactive nature of the new engine revealed that the algorithm had a persistent bias: it consistently favored solutions composed of many small teams (size 3) over larger ones (size 5).
The root cause is the fitness function's response to topology. In hindsight this seems really obvious, but it absolutely was not at the time. Basically, minimizing the skill variance () in a group of 3 is statistically easier than in a group of 5. Because the fitness function treated a tight range as an absolute value regardless of team size, the optimization gradient constantly pulled the topology toward smaller groups. This phenomenon extended to most other fitness metrics for that matter. A global Jaccard Index is easier to fit when team sizes are small, so were also culprits.
Furthermore, analyzing the outputs revealed a misalignment in the Inter-Group Balance () objective. My formula minimized the deviation from the cohort target, effectively pulling all groups toward a specific skill level. In hindsight, this was an over-correction. The pedagogical goal was strictly to prevent weak teams (raising the floor), not to suppress strong ones (capping the ceiling). By penalizing positive outliers, I was artificially preventing high-performing groups from emerging simply to satisfy a symmetry constraint that existed only in the math (and in my mind), not in the requirements.
In short, these defects were not implementation errors, but natural consequences of the model's axioms. Regardless of the specific mathematical fix, likely involving size-normalized weights and changes to the formula to remove the artificial ceiling, there was a valuable lesson in the engineering side of things: latency hides bugs. Had the tool remained slow, I likely never would have generated enough samples to spot these biases. Or, at the very least, it would've taken me a long time to do so. The rewrite didn't just buy me time; it bought me the bandwidth to be wrong, and the speed to eventually get it right.
Systems Engineering Lessons
On the implementation side, this project was my introduction to manual memory management and data oriented design in production. Looking back, my strategy was functional but naive.
I utilized a standard GeneralPurposeAllocator. At the time, this felt like a victory because it eliminated the GC pauses that plagued the Python version. Moreover, the initial performance gains were so large that I didn't even consider that there was much more room for improvement. However, for an Evolutionary Strategy where thousands of short-lived Team structs are created and destroyed every second, this approach causes heap fragmentation, especially for long-running simulations. The CPU is forced to chase pointers across non-contiguous memory, causing cache misses that leave performance gains on the table.
Today, I would implement this very differently. Since the lifecycle of a generation is predictable and the population size is fixed, I could pre-allocate one contiguous memory block (an Arena or Pool) for everything. Crucially, not only is the population size fixed, but there is a computable upper bound for the partition size (@divFloor(N, min_team_size)), which could greatly simplify the partition representation to a simple bounded array that does not need reallocations and memcopies like a dynamic one. This would ensure near-perfect data locality and reduce the cost of allocation and deallocation, each to a single operation at the very beginning and the very end of the runtime, respectively.
Similarly, I missed an opportunity to leverage concurrency. I kept the engine single-threaded for simplicity during initial development. When I saw the performance results, just like with the memory handling approach, adding multi-threading felt unnecessary. However, parallel execution could have significantly increased the population size without impacting runtime. Granted, a larger population size usually takes longer to converge, but it would ensure a wider coverage of the search space.
Furthermore, in relation to concurrency, I overlooked the algorithmic benefits of some more advanced strategies, such as the Island Model. Running isolated populations on separate threads with occasional migration of top solutions would have maintained higher genetic diversity and prevented the premature convergence I sometimes observed. I could've also modeled a better balance between exploration and exploitation by configuring each island's hyperparameters independently. Of course, this system would've been far more complex, and maybe the potential benefits would not justify such complexity (especially since the current model already works so well and solves the problem), but I have to admit that something inside me yearns for these kinds of engineering challenges.
Conclusion
This project was a success. It solved the business problem, operationalized the team-building process, and significantly improved the student experience.
But for me, the lasting value lies in the technical retrospective. It taught me that a mathematical model is only as good as the feedback loop that validates it. It also demonstrated that performance is not a luxury. Nor is it, as most have heard, merely "the root of all evil" when applied prematurely (also, what even is "prematurely"?). Instead, to me, performance is the lens through which we understand the behavior of our software. Just like security, it must never be an afterthought.
To wrap this up, here's a quote from someone dear to my heart. You might even know them from their work on the Linux kernel:
To some degree, people say you should not micro-optimize. But if what you love is micro-optimization, that's what you should do.