A directory is implemented in the same way as files are implemented (with the direct blocks, indirect blocks, etc) - It is just a file which is formatted with a special format - A list of directory entries.
Follows the definition of a directory entry:
struct ext2_dir_entry {
__u32 inode; /* Inode number */
__u16 rec_len; /* Directory entry length */
__u16 name_len; /* Name length */
char name[EXT2_NAME_LEN]; /* File name */
};
Ext2fs supports file names of varying lengths, up to 255 bytes. The
name field above just contains the file name. Note that it is
not zero terminated; Instead, the variable name_len contains
the length of the file name.
The variable rec_len is provided because the directory entries are
padded with zeroes so that the next entry will be in an offset which is
a multiplition of 4. The resulting directory entry size is stored in
rec_len. If the directory entry is the last in the block, it is
padded with zeroes till the end of the block, and rec_len is updated
accordingly.
The inode variable points to the inode of the above file.
Deletion of directory entries is done by appending of the deleted entry space to the previous (or next, I am not sure) entry.