Skip to content
Tarik Hireche
Portrait of Tarik Hireche

Bilingual Software Developer

Tarik Hireche

I build backend services and the infrastructure they run on. Most of what breaks in production is below the application, so that is the layer I have spent my time on.

  • Java
  • C#
  • SQL
  • Cloud & Infrastructure
Degree
B.Sc. Computer Science · Université de Montréal (DIRO)
Focus
JVM services, containers, CI
Degree
B.Sc. Computer Science, UdeM
Strongest in
Java · C# · SQL · Cloud & Infrastructure
Based in
Montréal, QC
Languages
English · French · Arabic

Experience

  1. Mar 2026 to Present

    Software Developer

    I took the client's site off WordPress and wrote them a PHP content platform to replace it. It handles uploads safely. An append-only activity log and per-document version history make the trail reconstructable after the fact. Lossless compression on the server cut page weight by 45%. I am the only technical person the company has, so I also handle deploys and content releases, and I wrote the runbook they work from, because nobody had ever written the steps down. The newsletter automation grew out of this work: Python and JavaScript across three languages, taking a one to three hour job down to about thirty minutes over more than ten campaigns.

  2. May 2022 to Feb 2024

    Gatestone

    Montréal, QC

    Technical Support Representative

    I diagnosed connectivity and account problems for enterprise clients, under time pressure. Every call started with identity verification before I could touch sensitive data.

Selected projects

5 projects

The strongest one is first. One of them runs live on this page.

uiResidentMenu · IntervenantMenuOpenDataSourceinterfaceMontrealOpenDataHTTP · CKANJSON fixtures35 offline testsdonnees.montreal.ca
The seam the refactor introduced: the console screens depend on an OpenDataSource interface. MontrealOpenData implements it over HTTP against the city's CKAN portal; the tests implement it from JSON fixtures, so the suite runs offline.

JVM application

2026

My City

Montréal publishes its road work and street obstructions as open data. A resident who wants to know what is being dug up on their street, or a contractor looking for work to bid on, has no way to read any of it.

  • Reads two City of Montréal CKAN datasets and joins each obstruction back to the road work that caused it.
  • Residents file and track work requests. Contractors filter those by category, borough or start date, then bid.
  • A clean clone builds and tests with one command. 35 tests run offline in under a second.
  • Java 17
  • Maven
  • JUnit 4
  • Gson
  • REST / JSON
  • Montréal open data
What splitting a 780-line class turned up
Resident.java
780 → 106 lines

The screens, an HTTP client, JSON parsing and the Scanner reads came out. What is left is a person's seven fields and the two operations that belong to them.

Menu.java
579 → 97 lines

Both sign-in screens, both registration screens, both post-sign-in menus and five input validators came out. It now picks between signing in and registering.

Test suite
9 → 35 tests

Across 6 files, from 3. The old suite needed a live third-party API to pass; mvn test now needs neither a network nor a terminal.

Duplication defects found
6

Each one surfaced while collapsing copies of the same code. Listed in the commit that removed them.

The build file was the real fix. We built this for a team of three and it only ever compiled inside the IDEs we wrote it in. JUnit and Hamcrest sat in the repo as jars under lib/. Gson came in through an IntelliJ library entry pointing into somebody's local Downloads folder. So a fresh clone got you a wall of errors. It builds with Maven now and the jar it produces runs.

Two classes held most of the application. Resident was 780 lines: seven fields describing a person, plus every screen a resident could reach, an HTTP client for the city's data portal, the JSON parsing, the console output and the keyboard reads. Menu was 579. I pulled them apart along what each piece was actually responsible for. A data package for the city API, a ui package for the screens, and models that are just models. I moved the code. I did not rewrite it.

Six bugs fell out of that, all of them from duplication. My favourite: the list of ten work categories existed in three copies that had drifted apart. Option 8 read "Travaux résidentiel" on the search screen and "Travaux résidentiels" on the submission screen. So a resident could file a residential request and then never find it again. One enum now, and a test that fails the moment two labels stop matching.

The rest were the same shape. Closing a request removed by email from a map keyed by object, so it removed nothing and the request stayed open forever. Every screen opened its own Scanner on System.in, and a Scanner reads ahead, so one screen could swallow a line the next one was waiting for. Date parsing ran on the default SMART resolver, which happily accepts 31/02/2024 and hands back 29 February. The sign-in loop was written for three attempts but redirected after two, so the third could never happen.

The old suite had nine tests and one of them called the live donnees.montreal.ca API, so the build failed on any machine without a route to it. Splitting the parsing from the fetching sorted that out. Field mapping is covered against fixtures and runs offline. The check that the city has not renamed a column lives in an integration test under failsafe, where it can still catch a real change without breaking an offline build.

watcher repoprivate · scheduledETag · 304job boards421 trackeddedupe · score 0-100 · work-auth parsereasons kept on every scoredata repopublic · jobs + healthCORSdashboardstatic Reactno server between the sweep and the browserfreshness published, so a dead sweep is visible
Four stages. A private scheduled repository holds the schedule and the filters and runs the sweep. The sweep fetches job boards with ETags, then deduplicates, scores and parses work authorization. It writes JSON to a separate public data repository. A static React dashboard reads that JSON in the browser over CORS, with no server in the path.

Backend automation & data pipeline

2026

Job Watcher

Good roles close in days. Checking a wall of job boards by hand every morning is a chore, and chores get dropped. Then you find out a role existed a week after it closed.

  • AI-assisted implementation. The architecture, the specifications, the evaluation criteria, the data contracts, the failure-mode analysis and the production decisions were mine.
  • A four stage pipeline: a private scheduled repository fetches and scores, a public repository holds the JSON, and a static React dashboard reads it in the browser. No server in the path.
  • 421 boards on a schedule. ETags on every fetch, duplicates folded across sources, work authorization parsed out of the posting text, and freshness published so a dead sweep is visible.
  • System design
  • Scheduled automation
  • GitHub Actions
  • JSON data contracts
  • ETag caching
  • CORS
  • TypeScript
  • React
The pipeline, the contract, and who decided what
Boards tracked
421

boards_known in the live health.json the sweep publishes. Read it yourself; the data repository is public.

Roles in the current feed
442

Across 268 distinct companies, after deduplication, scored and ranked. Counted from the live jobs.json.

Upstream postings scanned
3,331

last_count from the most recent sweep. Fetched with an ETag, so an unchanged source costs one 304 and no parsing.

Dashboard refresh
120 s

The page re-reads the feed on a timer. Every request is cache-busted, since the CDN sends max-age=300 and a five minute cache would undo the point of the tool.

The pipeline has four stages and each one has a job. A private repository holds the schedule, the profile and the filters, and it runs the sweep. The sweep fetches each board with an ETag, so a source that has not changed answers 304 and costs nothing to parse. What comes back gets deduplicated across sources, scored out of 100, and checked for work authorization. The result is written as JSON to a second repository, public, which exists only to hold data. A static React dashboard reads that JSON straight from the browser over CORS. Nothing runs in between, so there is no server to pay for and nothing to keep alive.

The JSON contract is the part that made the rest possible. I wrote it before either side existed: what a role record contains, what the health record contains, which fields are optional and what a missing one means. The sweep and the dashboard were then built against that contract separately and met in the middle. It is also why the dashboard survives the sweep changing shape underneath it.

Freshness is a first-class field because silent failure is the thing that actually breaks a tool like this. A schedule that stops running will not tell you. It keeps serving whatever it last managed to fetch and the page looks completely normal. You find out a week later. So every run writes down its own state. Last run time. Which sources answered. How many rows each returned. How long each took. The dashboard reads that first and tells you the age of the data before it shows you a single role.

Scores are explainable by design. Each one carries the list of reasons that produced it, so a ranking I disagree with becomes a rule I can go and find. 296 of the 442 roles currently in the feed carry reasons. Work authorization is the same idea taken further: 47 roles are pinned to zero because the posting blocks me, and every one of those quotes the sentence from the posting that says so, so I can check the parser against the source. Deduplication runs across sources before any of that, and it has folded 121 duplicate rows into 46 roles.

Persistence lives in the browser, on purpose, since the dashboard has nowhere else to put it. Version one stored a status word and nothing else. I could see what I had applied to and never when, so the funnel could not be measured. Version two stores a timestamp with it and migrates the old records forward. There is a backup path to ntfy so the history survives a cleared browser.

On the AI: I want to be exact about the split rather than vague in either direction. I wrote a specification for each component before any code existed, defined the contracts between them, decided what counts as a correct result, and worked out the failure modes. The implementation was written with an AI, reviewed by me against those specs, and sent back when it missed. What I kept by hand are the places where being wrong is quiet: the scoring rules, the work authorization gate, and the decision to publish health at all. A tool that is confidently stale does more damage than one that is visibly broken, and that judgement does not come out of a prompt.

pushbuildPIT runscorevs baselinedroppedexit 1, build failsheldmerge allowed
CI quality gate: every push runs PIT mutation testing, compares the score against a committed baseline, and fails the build if it dropped.

Testing & CI

2025

Mutation Testing in CI for GraphHopper

GraphHopper's pipeline ran the test suite on every push. It never asked whether those tests were any good. A course project, on a fork of the upstream repository, worked in a pair.

  • A PIT mutation job fails the build when the score drops below a baseline committed in the repository.
  • The baseline, the threshold and what happens on a failure are all written down in the repo.
  • I verified the gate by breaking an assertion on purpose, watching the score fall to 91%, and watching the build go red.
  • Java
  • PIT
  • Mockito
  • Maven
  • GitHub Actions
How I know the gate fails

A gate you have never seen fail is not a gate. I broke a test to push the score from 92% to 91%. The job went red. The drop signal reached the next step and fired a custom action that Rickrolls whoever caused it. That path runs end to end.

The pipeline is split in two so ordinary builds stay fast, and mutation runs on the core module only. Mutation testing is expensive: PIT reruns the suite once per surviving mutant, so pointing it at the whole project would have made every push slow enough that someone would have turned it off.

The suite needed tests that run without loading a real routing graph. I wrote three with Mockito, in their own package. They mock PointList, DistanceCalcEarth and EdgeIteratorState. A test can then pin exact coordinates and edge attributes and check the code that consumes them.

containersentinelPID 1 · subreaper./appown process groupfork · execorphanadoptedreapeddocker stopSIGTERMkill(-pgid)still alive after 5s, then SIGKILLexit status: the child's own, or 128 + N
Process supervision inside a container: sentinel runs as PID 1, forks and execs the application into its own process group, forwards an incoming SIGTERM to that whole group and escalates to SIGKILL after five seconds, adopts and reaps orphaned descendants, and exits with the child's own status.

Containers & reliability

2026

sentinel

A container that will not shut down cleanly drops whatever it was doing on every deploy. Docker sends SIGTERM, waits ten seconds, then SIGKILLs, and a service that never handled the signal loses its in-flight work.

  • A container under sentinel exits about 3 ms after SIGTERM. The same program alone stays up until the ten second deadline and is killed.
  • It restarts a dead child on a 1, 2, 4, 8, 16 second backoff, and serves restart, crash and failure counters for Prometheus to scrape.
  • 13 ctest cases and 5 CI jobs. CI stands the whole Compose stack up and fails unless Prometheus is scraping and Grafana has the dashboard.
  • C++17
  • Linux
  • POSIX signals
  • Docker Compose
  • Prometheus
  • Grafana
  • GitHub Actions
What PID 1 costs you
Shutdown after SIGTERM
2.5 to 3.1 ms

Seven runs, worker supervised by sentinel on PID 1 of a PID namespace, timed through wait() so there is no polling error. The same worker alone was still alive at the ten second deadline and needed SIGKILL.

Child that ignores SIGTERM
5.003 s

Same harness, worker in stubborn mode, three runs inside 1 ms of each other. That is the SIGKILL escalation firing on schedule.

Restart backoff
1, 2, 4, 8, 16 s

Timed from the supervisor's own log against a child that dies at once. Five restarts in forty seconds, and the pause caps at sixteen.

Test suite
13 of 13

ctest on a Release build, 33 s. CI runs build, test, shellcheck, docker and compose as separate jobs.

Supervisor footprint
3.9 MB RSS

Read from /proc while supervising, on two threads. The binary is 36 KB, or 31 KB stripped.

The kernel will not deliver a signal to PID 1 unless PID 1 installed a handler for it. So the container sits through the whole grace period and then dies by SIGKILL. sentinel takes the PID 1 slot and lets your program run as an ordinary child. It exits with the child's status code, or 128 plus the signal number if the child was killed. Exit 137 out of a container is 128 plus 9. That is the OOM killer.

The handlers go in without SA_RESTART. That matters: with the flag, the kernel restarts waitpid after a signal and the reap loop never gets a turn. Without it, waitpid returns EINTR and the loop keeps control. The handler calls kill() and nothing else. Very little is safe to call from signal context. Escalation lives under the same constraint. The SIGTERM handler arms alarm(5), and a SIGALRM handler does the killing.

There used to be a window between the fork and the line that installed the handlers. A SIGTERM landing in it killed sentinel and left the child running as an orphan. That is the exact failure sentinel exists to prevent. I marked it as a TODO and lived with it for a while. The fix is sigprocmask around the fork. It blocks SIGINT and SIGTERM before, and unblocks once the handlers are up. The child restores the original mask before exec. Otherwise everything sentinel launches starts out deaf to those signals.

The metrics server needs its own thread, because the main one sits blocked in waitpid and cannot also wait in accept. That thread blocks every signal for itself. SIGTERM has to keep landing on the main thread where the handler lives, or waitpid never gets its EINTR. The counters are atomic since both threads touch them.

One compose command brings up sentinel, a Prometheus scraping it every 5 s, and a Grafana with the dashboard already provisioned. The stack runs a child that crashes on purpose, so the graphs have something real on them. Two alert rules ship with it, one for a crashloop and one for a child that has been down two minutes. CI stands the whole thing up on every push and fails unless Prometheus reports the target up, the restart counter has moved, both rules loaded, and Grafana is serving the dashboard.

Live inference

55,050 params · 88.5% on 10k test set

Loading model…

Machine learning

2026

Neural Network & Hyperparameter Study

I wanted to know which training choices change the result. So I built the network from scratch, one piece at a time.

  • The neurons, the layers, four activation functions and four loss functions are all mine, written in NumPy.
  • I ran nine controlled experiments, one variable at a time. The clearest result was about the loss function.
  • The trained network runs in the panel on the left, in your browser. Quantised to int8, it downloads in 55 KB.
  • Python
  • NumPy
  • PyTorch
Why MSE learns slowest where it is most wrong

When the model gets an example badly wrong, the MSE gradient is scaled by the output derivative. That derivative is near zero exactly when the error is largest. The network then learns most slowly on the examples it has most to learn from. Cross-entropy cancels the term. That is why it is the default for classification.

Skills

Reliability & operations
  • Graceful shutdown
  • Restart policy & backoff
  • Runbooks & handover docs
  • Audit logging
  • Incident debugging

Used in: sentinel · six months as the only technical person at Crono Design

CI/CD & deployment
  • GitHub Actions
  • Multi-job pipelines
  • Merge gating
  • Docker
  • Multi-stage builds
  • Docker Compose
  • Nginx

Used in: sentinel · Mutation Testing in CI · My City

Observability
  • Prometheus metrics
  • Text exposition format
  • Counters & gauges
  • Alert rules
  • Grafana dashboards

Used in: sentinel, where CI fails unless the stack really scrapes

Linux & systems
  • Processes & signals
  • fork / exec / wait
  • Process supervision
  • Concurrency
  • Manual memory management

Used in: sentinel · Three Allocators in Zig · Fedora daily driver

Languages
  • Java
  • SQL
  • C++
  • Python
  • Bash
  • PHP
  • C
  • Zig

Used in: Every project here, and the Crono Design work

JVM & data
  • Java 17
  • Maven
  • JUnit & Mockito
  • Spring Boot
  • Spring Data JPA
  • REST APIs
  • PostgreSQL schema design

Used in: My City · Mutation Testing in CI

Tooling
  • Git
  • GDB
  • strace
  • CMake
  • ctest
  • Make

Used in: Refactoring My City · building sentinel

Recognition

DIRO Excellence Scholarship2024
The computer science department at Université de Montréal awarded it for academic standing.
Top 30 at the NorthSec CTF2025
My team placed in the top 30 of roughly 93 teams at the largest applied-security competition in North America.

About

Montréal, QC

For six months I've been the only technical person on a client's web presence. I handle the hosting, the deploys and the content releases. I wrote the runbook so they could stop calling me for routine changes. It's unglamorous work. I learned more about reliability from it than from anything else I have built.

The layer underneath keeps pulling me back. What is PID 1 in a container responsible for? Why can a test suite be green and still miss bugs? The interesting problems are the ones the framework does not answer for you, and those are the ones I go after.

Up next

My City keeps everything in memory, so a restart loses it. The next piece is Postgres behind that store.

Contact

What I want to work on

Backend services that have to stay up: JVM applications and the data behind them, containers and deployment, process lifecycle, and pipelines that catch real regressions.

tarik.hireche@umontreal.ca
Résumé
PDF