Backend development student at Anadolu University, based in İzmir, Türkiye. I build server-side systems end to end — schema first, then the API, then the container it ships in.
Most of what is here started as something I wanted to understand rather than something I was told to build: how an ORM actually maps a schema, what a request touches between the route and the database, what it takes to run a service on a server instead of a laptop.
Open to internships, junior backend roles and open-source collaboration.
root-project/root#23002 — merged into ROOT, CERN's data analysis framework for particle physics.
tmva/tmva/inc/LinkDef5.h had been unreachable since 2015. The commit that split TMVA into
libTMVA and libTMVAGui dropped that file's #include from the master LinkDef but left the
file itself in the tree, and two later maintenance sweeps edited it without noticing it was
already dead. I traced the commit that orphaned it, checked that every symbol it declared was
already covered by the module that actually owns those classes, and confirmed against the
generated build graph that nothing referenced it — then proposed the removal.
Still open: ROOT #23004, the same class of
leftover in smatrix and genvector; a latent
undefined-behaviour fix in
Eclipse S-CORE, the BMW/Bosch/Mercedes automotive platform;
and an issue in
NASA's F´ flight software framework.
What I keep relearning here: the patch is the easy part. Proving the claim before making it is the actual work — several findings I was sure about turned out to be false positives, and never left my machine.
concurrent-ticketing — a ticketing API built around one question: what stops the same seat from being sold twice?
Two customers open the same event page and click seat A12 in the same millisecond. The
obvious implementation reads the seat, sees Available, and writes Held — and so does the
other request, because both read before either wrote. Nothing is wrong with either line; the
bug lives in the gap between them, and it only shows up under load.
The fix is to make the check and the write one statement. Seat maps PostgreSQL's xmin
system column as an EF Core concurrency token, so the write carries the version the read saw:
UPDATE seats SET status = 1 WHERE id = @id AND xmin = @version;The first transaction to commit changes xmin. The second matches zero rows and gets a 409
instead of overwriting a sale. No table locks, no SELECT FOR UPDATE, and two customers
buying different seats never contend.
Measured rather than asserted — twenty concurrent requests, one seat, real PostgreSQL in a container:
20 concurrent requests → 1 × 201 Created, 19 × 409 Conflict
database: 1 held seat, 1 reservation
The second race is telling anyone about it. Confirming a reservation writes to PostgreSQL and publishes to RabbitMQ, and no transaction spans both — publish first and the broker may hold an event for a commit that fails; publish after and the process can die in between. So the event is written as a row in the same transaction as the reservation, and a dispatcher moves it to the broker afterwards. That is at-least-once rather than exactly-once, and the consumer absorbs the difference: a receipt row keyed on the message id, inserted alongside the work, so a duplicate delivery hits the primary key instead of sending a second e-mail.
FOR UPDATE SKIP LOCKED is what lets a second dispatcher be started at all — FOR UPDATE
alone would make it queue behind the first.
The tests I value most here are the ones added last. With 84 passing, running the stack by
hand showed POST /api/auth/register accepting a three-character password and the literal
string bu-bir-email-degil as an e-mail address: the contracts carried [Required] and
[MinLength], but nothing evaluated them, and no test crossed the HTTP boundary where they
live. A green suite says the tested thing works. It says nothing about the untested one.
.NET 10 · PostgreSQL · RabbitMQ · Redis · JWT with refresh rotation ·
Clean Architecture · Testcontainers · 119 tests · Docker Compose · CI
Course Registration System — an ASP.NET Core MVC application that worked. Adding tests to it was supposed to be a formality: writing down behaviour that already held.
Several of the first tests failed.
| Finding | What it meant |
|---|---|
AdminController had no [Authorize] |
The whole management area answered anonymous requests — POST /Admin/KursSil deleted a course with no session at all |
| Administrator credentials were string literals | The working password shipped with the source |
| Passwords stored in clear text | Reading the database was reading every password |
| Cancellation never checked ownership | Any signed-in student could cancel anyone else's place by incrementing an id |
| Capacity was a read-then-write race | Counted, compared, then inserted |
The last one is the one worth measuring. Reproducing the original logic under 15 concurrent applications to a course with capacity 5:
old logic (count → compare → insert): 15 enrolled ← 3× over capacity
current logic (conditional UPDATE): 5 enrolled
62 tests now, and CI that fails the build on any dependency with a known advisory.
The same read-then-write shape turned up in the coffee shop till, and I only found it
because I was trying to make the ordering logic testable. Adding the first item to a table
read the table's state, saw it free, then opened a tab — so two waiters on two terminals
both read free and both opened one. The order screen only ever shows the newest tab, so
everything written to the other was never billed. One conditional UPDATE closes it, the
same way the course capacity was closed. Third time I have written that fix now; I have
stopped thinking of it as a trick and started looking for the shape.
A pipeline that scans uploads with YARA rules, parses PE structure with pefile, and
submits samples to a CAPE sandbox. Work is queued through Redis to a Celery worker
rather than blocking the request — analysing an untrusted file is slow, and it has no business
happening inside an HTTP handler.
The lesson that stuck came from a bug in my own code: YARA compile errors were caught by a bare
except and skipped, so a rule file with a syntax error made every sample come back clean. For
a scanner, no findings and the scan never ran look identical from the outside, and only one
of them means the file is safe. A crash is a good outcome; a false negative is the bad one.
| Area | What I'm actually doing about it |
|---|---|
| Concurrency | Optimistic concurrency against a real database, and tests that genuinely race rather than asserting they would |
| Messaging | Transactional outbox, at-least-once delivery, idempotent consumers, dead-letter queues — RabbitMQ driven directly rather than through a framework, because the mechanics are the point |
| API design | Paginated, validated REST endpoints — with ordering that makes pagination stable and ceilings on anything read into memory |
| Data modelling | Normalised schemas, code-first migrations, and constraints in the database rather than only in application code |
| Deployment | Docker Compose and AWS EC2, with credentials from the environment and nothing sensitive published on a port |
| Analysis tooling | Static and dynamic file analysis with YARA and Celery — the area I find most interesting right now |
|
|
|
|
|
|
|
|
|
|
|
Some of this is team work — the Redmine deployment was built with Atakan MERGEN (@hzflora), whose repositories I also contribute to.
- SQL query planning — reading execution plans instead of guessing at indexes
- What breaks when one service becomes several: distributed tracing, and knowing which failures a retry actually fixes
- Data structures and algorithms, properly rather than for exams
Get in touch — balcihkutsi@gmail.com
İzmir, Türkiye · open to remote and hybrid roles