Interview notes
ALWAYS ASK WHAT THE FOCUS OF AN INTERVIEW IS
ALWAYS TALK SLOWLY AND CLEARLY
BE YOURSELF AND THEY'LL LOVE YOU
Description of current role
Daily routine:
Three Amigos
Backlog refinement
Sprint planning
Review
Retrospective
Work within Agile process.
Asynchronous programming
Implemented using the async and await keywords. Used for I/O bound operations, such as file access, database queries, and network communication.
Prevents main-thread blocking while performing synchronous I/O operations.
IDesposible
Used for unmanaged resources via the dispose method or using statement
Inversion of Control
Control of flow is inverted to an external framework or container rather than the program itself.
Promotes loose coupling.
Improve the modularity.
Delegates to an external container which manages the lifecycle and dependencies.
Dependency injection
Implementation of Inversion of Control,
Class no longer initializes its dependencies but instead accepts them through constructors or properties.
Three commonly used service lifetimes are Singleton, Scoped, and Transient.
Singleton: Created once and lasts the application's runtime.
Scoped: Created for each scope, usually each request
Transient: Created every time the dependency is called.
Simplifies unit testing by replacing actual dependencies with mockups.
Enhances modularity and flexibility.
Unit testing
Unit testing is used to ensure application logic runs according to specifications.
Testing frameworks: NUnit or xUnit.
Mocking libraries: Moq
Code coverage: ideally around 80%.
Check code coverage: SonarCube or JetBrains dotCover
Locking mechanisms
Avoid race conditions and deadlocks in multithreading code. Locking mechanisms can be implemented using. lock, Monitor, Mutex, and Semaphore.
L Locks: Ensure only one thread.
· Mutex (Mutual exclusion): Similar to a lock. Ensures that only one thread can access a protected resource at a time. mut.WaitOne();
A mutex (Mutual exclusion) is the same as a lock but it can be system wide (shared by multiple processes). It is used to synchronise access to a resource.
· Semaphores: Limited number of threads.
_pool = new Semaphore(initialCount: 0, maximumCount: 3);
Monitors include mutual exclusion (just like locks) but also offer waiting, signaling, and notification functionality.
Monitors can put threads in a wait state and signal specific threads to resume execution.
Example in C#: Using Monitor.Enter and Monitor.Exit, with optional Monitor.Wait and Monitor.Pulse.
Monitors are best for managing synchronization within a single process and offer additional inter-thread signaling capabilities, while Mutexes are suitable for scenarios where multiple processes need to coordinate access to shared resources, albeit with a slightly higher overhead.
A lambda function is a concise, anonymous function defined without a name, often used for short, simple operations. Lambda functions are typically defined inline and are useful when a function is needed temporarily or as an argument to higher-order functions.
LINQ
Allows writing queries to data sources in a declarative and consistent way, used to perform queries to memory (lists or arrays), databases, and XML Database via an ORM, such as Entity Framework.
Troubleshooting
Check server memory and CPU. Check recently added code. Check exceptions in logs. Is it just for one or all users? Is there a data leak?
What you know about Cubic – would be great if you could do some research on who we are, what we do, some of our products etc
They specialize in providing solutions for connected cars, Internet of Things (IoT) devices, and other connected platforms.
Global Connectivity Management Platform: Cubic Telecom offers a platform that allows companies to manage and optimize connectivity for their devices and vehicles globally. This includes services such as SIM card management, data analytics, and real-time monitoring.
Connected Car Solutions: Cubic Telecom provides connectivity solutions for the automotive industry, enabling connected car services such as in-car entertainment, navigation, and vehicle telematics.
Internet of Things (IoT) Connectivity: Their platform supports IoT devices, helping companies connect and manage large numbers of devices across various industries, including healthcare, agriculture, and smart cities.
Embedded SIM (eSIM) Technology: Cubic Telecom has been involved in providing embedded SIM solutions, allowing devices to have programmable SIMs that can be remotely provisioned and managed.
Mobile Data Plans: Cubic Telecom has offered mobile data plans for various connected devices, allowing users to access the internet and data services globally.
Why do you want to work for Cubic
Attracted to the tech stack and then successful company. Different sort of work from the average fintech or gaming or educational. Good Glassdoor score.
What can you bring to the role
Bring over 8 years experience of working with C# within TDD to industry standards, Security Champion.
Leadership and management skills.
Coaching and mentoring skills.
Bring over 20 years experience of developing software solutions.
Bring an enthusiasm to learning new skills and new technology.
Set up a team before, understand what is required to mentor and coach
Agile experience / Scrum Knowledge
Planning - story points - 1,2,3,5,8,13 - get sprint capacity
Acceptance criteria: given -when -then
Verifiable outcomes: definition of done
Expectations, user stories
Scope
Priorities/critical
Edge cases: if it can go wrong, it will go wrong.
Review - showing off what you've done
Retrospective - what went well
Process for communication, process for conflict
C# .Net experience
Coaching/Mentoring
I have a coached and mentored. Difference between coaching and mentoring.
Mentoring:
- Establish Clear Expectations: Clearly communicate the goals and expectations of the mentoring relationship. Help your junior colleague understand what they can expect from you as a mentor and what you expect from them in terms of commitment, effort, and goals.
- Build Trust and Rapport: Create a supportive and trusting relationship with your mentee by being approachable, empathetic, and respectful. Show genuine interest in their growth and development, and be open to their thoughts, concerns, and ideas.
- Provide Constructive Feedback: Offer feedback regularly and constructively, focusing on both strengths and areas for improvement. Provide specific examples and actionable suggestions to help your mentee learn and grow. Encourage them to reflect on their experiences and take ownership of their development.
- Encourage Continuous Learning: Foster a culture of continuous learning and development by encouraging your mentee to seek out new opportunities for skill enhancement, knowledge acquisition, and professional growth. Share resources, recommend relevant workshops or courses, and support their efforts to expand their expertise.
- Offer Guidance and Advice: Provide guidance and advice based on your own experiences and insights. Share lessons learned, best practices, and strategies for overcoming challenges. Encourage your mentee to explore different approaches, think critically, and make informed decisions.
- Set Realistic Goals: Help your mentee set realistic and achievable goals that align with their interests, skills, and career aspirations. Break larger goals into smaller, manageable tasks, and track progress over time. Celebrate milestones and successes along the way to maintain motivation and momentum.
- Promote Networking and Relationship Building: Encourage your mentee to build professional networks and establish meaningful relationships within their industry or field of interest. Offer introductions, facilitate networking opportunities, and share tips for effective networking and relationship building.
- Lead by Example: Serve as a role model for professionalism, integrity, and ethical conduct. Demonstrate a strong work ethic, effective communication skills, and a commitment to continuous improvement. Lead by example in how you approach challenges, collaborate with others, and strive for excellence in your own work.
- Be Patient and Supportive: Recognize that learning and development take time, and be patient and supportive as your mentee navigates their professional journey. Offer encouragement, reassurance, and guidance during times of difficulty or uncertainty. Foster a positive and nurturing environment where your mentee feels empowered to learn and grow.
- Seek Feedback and Adapt: Solicit feedback from your mentee about the mentoring process, and be open to adapting your approach based on their needs and preferences. Continuously assess and refine your mentoring strategies to ensure they remain effective and meaningful.
Microservices experience
AutoEntry started as a monolithic app and has broken some services out into microservices.
Processing PDF project using durable functions initiated by a service bus on Azure.
Describe a feature you are proud of
GDPR project:
Deleting non-active users and their data to fulfil GDPR requirements. Project that could not have bugs. It needed to be robust, simple and reliable. Used event sourcing to build an efficient and auditable system. Used even
Found and fixed bug where invoices were being processed from forwarded emails of cancelled subscription accounts. This cost the AutoEntry thousands.
Adding parallel for each to improve credits syncing which was undermining processing times. Much simpler and quicker solution than one proposed by Cosman which was to remove workflows from the system.
· Sage For Accountants Billing – Document counting
· IDP – OAuth2 Authentication – BFF pattern
· Fixed price subscriptions
· GDPR project
· QuickSights App Direct billing
· Go Cardless billing
· Subscriptions, integration with Stripe, multiple types of subscriptions, creating and sending invoices.
· Owned, brought developers into the wider project
· Integrate with many accounting software solutions
IBAN number paste
Post code verifier
Discuss a failure
Ability to end all other active sessions by removing the session from Redis cache. My belief was that a next click sign out would be sufficient, but the product owner wanted it to happen immediately. Which meant polling and endpoint. We have no web sockets or signal r set up on AutoEntry. This took the site offline when released into the production environment.
Benefit of going from monolith to microservices
Scale up services that are being heavily used without scaling up the entire app.
Scale down services that are not being used.
Reuse services across different products.
Faster deployment. Can develop, test and deploy features independently.
Fault isolation. One failing microservice won't necessarily effect the entire app.
When not to go from monolith to micro-services
When it creates greater overhead. Important not to break into micro services just for the sake of it.
Small scale and simple app would not be the best candidate to be divided into micro services.
MS require additional infrastructure and overhead.
Well established monolith that is well maintained and meets current and future requirements.
How do you ensure your application is scalable, extensible, and secure
Scalability:
- Horizontal Scaling: Design your application so that it can scale horizontally by adding more instances. Distribute the workload across multiple servers to handle increased demand.
- Load Balancing: Use load balancers to distribute incoming traffic evenly across multiple servers, ensuring optimal performance.
- Caching: Implement caching strategies to reduce the load on databases and improve response times.
Extensibility:
- Modular Design: Divide your application into modules with clear interfaces. This allows you to extend or replace modules without affecting the entire system.
- Dependency Injection: Use dependency injection to inject dependencies into components, making it easier to replace or extend functionalities.
- APIs and Microservices: Expose APIs for external integrations and consider a microservices architecture for independently deployable and scalable services.
Security:
- Follow OWASP Top 10. Run Bug bounty and DAST and SAST scans.
- Authentication and Authorization: Implement secure authentication mechanisms, such as OAuth2 or JWT, and ensure proper authorization to control access to resources.
- Data Encryption: Encrypt sensitive data both in transit (using protocols like HTTPS) and at rest (using encryption algorithms).
- Regular Security Audits: Conduct regular security audits and vulnerability assessments to identify and address potential security issues.
- Input Validation: Validate and sanitize user inputs to prevent common security vulnerabilities like SQL injection and cross-site scripting (XSS).
Performance:
- Optimized Database Queries: Ensure that database queries are optimized, and use indexing where necessary to improve query performance.
- Asynchronous Processing: Implement asynchronous processing for non-blocking operations, improving overall system responsiveness.
- Content Delivery Networks (CDNs): Use CDNs to distribute static assets and reduce latency for users across the globe.
Monitoring and Logging:
- Logging: Implement robust logging to capture errors, warnings, and relevant information for debugging and auditing purposes.
- Monitoring Tools: Use monitoring tools to track application performance, resource usage, and potential issues. Implement alerts for critical events.
- Documentation:
- Code Documentation: Maintain clear and up-to-date documentation for your codebase to assist developers in understanding and extending the application.
- API Documentation: Document APIs thoroughly to facilitate external integrations and collaborations.
Event sourcing
persistence pattern, ensuring that every data change is recorded as an immutable event. These events serve as the single source of truth for the system's state.
Event-driven architecture, these events are passed on to various parts of the system in a loosely coupled manner.
ideal for microservices-based systems
CQRS
Command Query Responsibility Separation. Separates the responsibilities of handling commands (writes) and queries (reads). You don't have to make compromises
CQRS also means the domain logic does not become welded to the data layer and this provides flexibility and the chance
The Command-Query Separation principle states that the method should be either a command or a query, but not both.
Violation of the CQS principle can degrade the code readability and testability.
If the command must return a value, consider split it into two separate methods: command and query.
Advantage of CQRS
Improved scalability
Better performance optimisation for read and write operations
Flexibility in choosing storage mechanisms
Clearer separation of concerns
DDD Understanding
Under domain-driven design, the structure and language of software code (class names, class methods, class variables) should match the business domain.
Difference between encryption and hashing
Encryption can be unencrypted with the encryption key while hashing is one way that is not meant to be reversed. SHA-256 or SHA-512 or the standard.
Understanding of event driven design
The code will have sources and listeners that get triggered when different events are raised. Events are a signal that a certain action has occurred. These components are decoupled for flexibility and extensibility. In a microservice environment the events are distributed using an Event Bus. Handlers and callbacks are methods that execute in the response of a specific event.
Functions as a Service (FaaS) offers several advantages and disadvantages:
Advantages:
- Scalability: FaaS platforms automatically scale functions in response to incoming requests or events, allowing applications to handle varying workloads without manual intervention. This enables cost-efficient use of resources and ensures optimal performance during peak loads.
- Cost Efficiency: FaaS typically follows a pay-per-use pricing model, where you only pay for the resources consumed during function execution. Since functions are short-lived and only run when triggered, you can save costs compared to traditional server-based architectures where resources may be underutilized.
- Reduced Operational Overhead: FaaS abstracts away infrastructure management, including server provisioning, scaling, and maintenance. Developers can focus solely on writing code and defining triggers, without needing to worry about underlying infrastructure concerns.
- Fast Development and Deployment: FaaS promotes rapid development and deployment cycles by allowing developers to focus on writing small, focused functions. This can lead to faster time-to-market for new features and updates.
- Event-Driven Architecture: FaaS encourages an event-driven architecture, where functions respond to specific events or triggers. This makes it well-suited for building event-driven and microservices-based applications, enabling greater flexibility and modularity.
Disadvantages:
- Cold Start Latency: FaaS platforms may experience latency during "cold starts," where the platform needs to initialize a new execution environment for a function that hasn't been used recently. This can introduce delays in response times for the first invocation of a function.
- Execution Time Limits: FaaS platforms typically impose limits on the maximum execution duration for functions. Long-running tasks may need to be broken down into smaller, shorter functions or handled using alternative approaches.
- Vendor Lock-In: Adopting a specific FaaS platform may lead to vendor lock-in, as each provider offers its own proprietary APIs and services. Migrating functions between different FaaS providers or to an on-premises environment may require significant effort and may not be straightforward.
- Limited State Management: FaaS functions are typically stateless, meaning they do not retain state between invocations. While this simplifies scaling and deployment, it may require additional effort to manage stateful operations or share data between function invocations.
- Debugging and Monitoring Challenges: Debugging and monitoring functions in a FaaS environment can be more challenging compared to traditional server-based architectures. Tools and techniques for debugging and monitoring may be limited, requiring additional effort to ensure visibility into function execution and performance.
Ownership of design and
implementation for multiple integration solutions
·
Sage For Accountants Billing –
Document counting
·
IDP – OAuth2 Authentication –
BFF pattern
·
Fixed price subscriptions
·
GDPR project
Daily job
1) Queue users into delete log: update active users and add in new inaction users
2) Send warning emails and update status
3) Disable users and update status
4) Delete users and update status
Generate reports
·
QuickSights App Direct billing
·
Go Cardless billing
·
Subscriptions, integration
with Stripe, multiple types of subscriptions, creating and sending invoices.
·
Owned, brought developers into
the wider project
·
Integrate with many accounting
software solutions
Building and leading a team of
engineers and providing governance and oversight of their deliveries
·
Contracted in UK. Very quick and adaptable at learning new
technologies. HMH 35 games, created
proof of concept in CreateJS to replace Flash games.
·
Leading teams in AutoEntry and
HMH
o Involved
in set up of team in AutoEntry
o Interviewed
staff
o Performed
one-on-ones: One on ones should have two streams: performance measurement and career progression.
o Performance
measurement
o A
lot of performance measurement took place through weekly one-on-one’s. Took notes on development and performance and
gave feedback of work through the week.
Meant that team members had a good idea where they were come end of year
assessment already.
o Team
cohesion:
§ Helps
that team members understand each other perspective
§ Had
issues with clash of personalities
§ Important
to not allow political discussion
§ DISC
(Dominance, Influence, Steadiness, Compliance) profiling: Peacock, Panther,
Dolphin, Owl
·
Panther: motivator, leader,
risk-taker, focused, big thinker, outspoken and adventurous.
·
Peacock: motivator,
entertainer, colourful, exciting, quick-witted, fun and adventurous.
·
Dolphin: peace-maker, giver,
follower, insightful, listener, slow to change and introspective.
·
Owl: thorough,
detail-oriented, sequential, analytical, methodical and orderly.
§ Belbin
team members
·
Build a culture of how we
react to different situations:
o We
react calmly to critical problems and take the issue step by step.
o We
have a collaborative, no blame
culture. Everyone will make a mistake at
some point. There should no sanctimony
within a team.
o A
culture of recognition and sharing praise, this needs to come from the leader.
o Non
competitive and collaborative.
·
Studied leadership, like to
employ a Servant Leadership style
o Listen
more than they speak
o Respectful,
emphatic and have a genuine interest in everyone
o Predictable
and clear => approachable and trustworthy
o Assist
and empower their team to grow and be well
o Prioritizes the needs of the team
o Commitment
to personal growth
·
Ken Blanchard three aspects of
Servant Leader
o Clear
goals
o One
minute praising
o One
minute redirect
·
Team direction
o Purpose:
provide convenience
o Picture
of the future: always be reliable
§ Values:
SEEU Security Efficiency Effectiveness
Usability
·
From this vision, the team can
set goals with direction and meaning.
·
Who’s got the monkey?
Manage relationships with
external partners and vendors
·
In HMH, managed relationship
with stakeholders in the US and development team in India.
·
Important to have reputation
of being trustworthy
·
Managed relationships with
internal stakeholders, different integrations within Sage.
·
Important to:
o Clearly
define expectations of a project to avoid misunderstandings
o Clearly
define requirements
o Define
detailed scope of a project
o Define
process of communication and regular updates
o When
dealing with external vendors we need contractual agreements
o Define
process for managing conflict resolution
o Collaborate
and build relationship
o Identify
risks and develop contingency plans
Deliver robust technical
solutions that form part of the core product
·
If it can go wrong, it will go
wrong
·
First and most important rule
when you work in a kitchen is clean as you go.
This is mutually beneficial for all workers in sharing a safe, efficient
and pleasant working environment.
Similarly, a codebase is a shared working environment. It’s imperative this is followed in software
team.
·
In Sage, projects form part of
the core product
o Follow
high standards of coding
o SOLID
What is the benefit of SOLID
Provide valuable guidelines for designing maintainable, readable and scalable software.
When is it ok to not use SOLID
It's important to remember there are just guidelines.
Simple and small projects: might create greater overhead and complexity to implement all principles
Open - Closed principle: when there's a bug, then modification is ok.
Legacy code: introducing SOLID principles might cause more problems.
§ Single
responsibility principle
·
Each class should have a single responsibility
·
Strive for high cohesion and loose coupling
·
Keep classes small, focused and testable.
§ Open/Closed
principle
·
Open for extension, closed for
modification
§ Liskov
Substitution Principle
The parent class must be able to be replaced by any derived class without causing errors in the program.
·
Subtypes must be
substitutable for their base types
·
Square is not
substitutable for a rectangle
§ Interface
Segregation Principle
Each class should only implement the interfaces that will actually be used.
· Clients should not be forced to depend on methods they do not use
·
Prefer small,
cohesive interfaces to large, expansive ones.
§ Dependency Inversion Principle
Example: Using interfaces in Controllers and using DI to fill with either a mock or repository (Database implementation).
·
High level modules should not depend on low level modules.
·
Both should depend on abstractions.
·
Abstractions should not depend on details.
·
Details should depend on abstractions.
·
Interfaces and mocks
Gang of Four design patterns
DAST: Dynamic Application Security Testing
Evaluating a running application from the outside,
mimicking a real world attack. Set up
Netsparker.
SAST: Static Application Security Testing
Analysis on source code without executing the program.
Set up the Fortify scans with Team city, run daily. Fine
tune to discard some warnings.
Bug bounty
Set up engagement rules in Hacker One. Created bugs from resulting reports.
File Path Traversal Attack: change the folder name for an
upload. BurpSuite man in the middle or Chrome Dev tools. Never trust any data from the user. ../../etc/password
Encoded Path Traversal attack: %2E%2E2F% = ../ or 2F% = /
Null byte injection path traversal path:
../../etc/passwd%00.png – checking is png, null byte will terminate the
sequence for a download, so actual file that gets downloaded is etc/passwd
Never let the users file input drive file I/O. Use an allow list.
The most effective way to prevent file path traversal
vulnerabilities is to avoid passing user-supplied input to filesystem APIs
altogether.
Run applications with minimal necessary permissions. Limits scope of damage.
Restrict file system access to only needed directories and
files.
File system access control should be very strict. Explicitly define what files and directories
an application can read and write to.
AVS5.0 File I/O Requirements
Don’t use user submitted file name.
Uploads
Disk Flooding attacks: Zip bombs: 42k zip file that
unzipped to 4.5 petabytes.
Injection: metadata, file paths, command injection
Malware distribution: verifying image headers not
enough.
Only allow authorised users can upload.
Per user, per file quota – write to individual user folder.
File name extension, MIME type detection and file header
check – light check
Headless antivirus: S3 Sophos and Clam AV, use all anti
virus products.
Use Image rewriting.
Use ImageMagick to change file by one pixel and save back to disk.
Drop Lambda if anything goes wrong.
Unzip strategies should have OS level configuration where
each zip file is unzipped within its own folder with only those permissions
inside of that. Avoid accepting zip
files.
Log failures and lock accounts.
Don’t depend on CloudFlare or WAFF to save you. Build software that is immune to the
problems. Firewalls can be bypassed.
SQL Injection
Never use string building to build a SQL query or you can
SQL injection as in 1 = 1. Universal
truth, flattens the where clause. Add cmd shell to SQL injection. Xp_cmdshell
Applications that insert untrusted data into a database
queries via string building allows attackers to execute arbitrary queries
against backend databases.
Look at: Payloads All the Things. SQL Map can pull a whole database one SQL at
a time.
Use parameterised query to build SQL queries, even for
static variables. ORM Lite or
EntityFramework.
Log injection
GDPR issues – don’t log PII or ensure that you encrypt PII
Security Logging
Event logging and event monitoring. Security Logging and monitoring. Be aware of GDPR.
§ Code
reviews:
·
Security
o Follow
Owasp top 10 security
§ Broken
object level authorisation
§ Broken
authentication, function level, object level
§ Unrestricted
– no rate limiting
§ Service
side request forgery
§ SQL
and other Injection
§ Security
misconfiguration
§ IDOR
Insecure direct object references
·
Efficient queries/code
·
Effectiveness
·
Readability:
o Use
abstraction
o
Correctly named variables
o
Use custom exceptions, if no
generic exception
o
Duplicated code
o
Long methods
o
Large classes
o
Long parameter list
o
Dead code
o
Feature envy
o
Data clumps
o
Primitive obsession
o
Code laid out in self-explanatory,
conspicuous. Comments only when necessary
·
Unit tests
o
Monitoring using vital statistics
and health checks
o
Collaborate across multiple
engineering teams and work across a diverse technology stack
·
As a contractor for three years, I
had to be adaptable to the tech stack that I was working with.
·
Used side project to build and
learn React
·
Already work across several teams.
Technical interview:
Sealed class
It prevents a class from being inherited and used as a base class
Dependency injection
A design pattern where dependencies are injected into a class, improving modularity and testability.
Dynamic v var
You might choose dynamic over var when dealing with scenarios where the type of a variable is not known until runtime. dynamic allows you to defer the type checking until runtime.
var cache = new Dictionary<string, dynamic>();
volatile keyword
ensures that a variable is read and written directly from and to the memory preventing certain compiler optimisations.
The field may be changed by multiple threads and certain optimisations should be avoided to ensure correct behaviour in a multithreaded environment.
Is
Is is used to check if object is a certain type.
As
Safe casting, cast an object as - returns null if not castable.
Lock
Lock is used for mutual exclusion, to ensure something is threadsafe. Only allows one thread at a time.
== vs .Equals()
== is used for reference or value equality while .Equals can be overridden for custom types to provide equality.
params
allows a method to accept a variable number of parameters
throw v throw ex
throw preserves the original call stack while throw ex resets the call stack potentially losing valuable debugging information
Value types v Reference
types:
Value types
Variables that store data.
Value types are stored on stack.
They contain the actual values. Example: int,
enum, structs.
Reference types
Variables that store reference
to actual data.
Reference types stored on
heap but contain the address on heap.
example: class, interface, delegate, string,
object, Array
|
Value Type |
Reference Type |
|
They are stored on stack |
They are stored on heap |
|
Contains actual value |
Contains reference to a value |
|
Cannot contain null values. However this can be
achieved by nullable types |
Can contain null values. |
|
Value type is popped on its own from stack when
they go out of scope. |
Required garbage collector to free memory. |
|
Memory is allocated at compile time |
Memory is allocated at run time |
Stack v Heap
The stack is generally used for storing small, short-lived
variables such as local variables and function parameters, while the heap is
generally used for storing larger, longer-lived objects.
Stack
·
Value types
·
Local variables and function
call information
·
Last in, first out
·
Very fast to allocate and
deallocate
·
Limited size. If the stack
grows too large, it can cause a stack overflow, which can lead to a crash or
other unpredictable behaviour.
·
Used for value types
·
Lifetime of a variable on the stack is limited to the
function in which it was created
Heap
·
Reference types
·
Used to store objects
·
Called heap because it is not
organised and accessed randomly.
·
Objects allocated using the new
keyword.
·
More flexible than stack, but
slower to allocate and deallocate.
·
No fixed size and garbage
collector must monitor.
·
Not tied to any particular function and can outlive
the function in which it was created
Boxing and unboxing
Boxing-- Converting a value type to a reference type
·
Memory is allocated on heap
·
Value is copied from stack to
heap
·
Reference is updated to point
on heap
·
Unboxing- Converting reference
type to value type.
Struct v Class
Struct
·
Struct is like a class that is
used to store data
·
Value type, stored on the
stack, so faster to access
·
When you assign to another
variable, a copy is made
·
Value based equality
comparison, changing one does not change others
·
Used for small, lightweight
data types, as limited size on stack.
·
Use for small, simple data
types, like numbers, points or coordinates
·
Use when performance critical,
to avoid heap allocation
Class
·
Reference type, stored on the
heap
·
When you assign or pass as
parameter it’s as a reference
·
Used for more complex data
types that represent objects with multiple properties
·
Larger and more complex as
allocated on the heap
·
Reference based equality comparison
·
Use classes when working with
large objects or you need flexibility of the heap
Using statement v Using declaration
Using statements defines the scopes of unmanaged resources
and disposes of them.
Using declaration has no brackets and disposes at the end
of the method.
Encapsulation
Bundling of data and methods into a single unit, known as a
class. The internal state is hidden from
the outside and interactions occur through well-defined interfaces. Access modifiers (private, public, protected)
control the visibility of class members, enforcing encapsulation.
Inheritance
Define a new class on an existing class, which inherits
attributes and behaviours from the base class.
Polymorphism
Treat classes as objects of a common base class. This promotes flexibility as you work at a
higher level of abstraction. C# achieves
this through interfaces, abstract classes and method overriding and overloading.
Abstraction
Simplifies complex systems by modelling at a higher level
of abstraction, making them easier to read and maintain. Provides clear separation between interface
and implementation.
Abstract classes and interfaces define common contracts and
provide a level of abstraction.
Interface: contract, no implementation
Abstract class: implementation, can contain mixture of:
·
abstract methods (must be
overridden, no implementation)
·
concrete methods (no
overriding, has implementation)
Normal class: can be extended or overridden
·
concrete methods (no
overriding, has implementation)
·
virtual methods (may be
overridden or extended, has implementation)
Association, Aggregation and Composition
Different types of relationships between classes and
objects.
Association
Association represents a loose relationship with no strong
ownership or dependency. Example whereby
a teacher may have many student and a student may have many teachers.
Aggregation
One class contains other classes, but these can exist on
their own. Example: A university may
have many departments, but a department may exist independently.
Composition
A stronger, tighter relationship. This is a “contains a” relationship whereby
the parts cannot exist without the whole.
This may be a car composed of an engine, seats etc.
Design patterns
Singleton: Ensures only one
instance of a class and provides a global point of access to that instance.
Allows access control or when you want to coordinate
actions across a system.
Examples: Logger, Cache.
Implementation:
Factory method: An interface
for creating an object. Allows
subclasses to alter the type of objects being created. Other creational methods are Abstract
Factory, Builder (car builder), Prototype (clone).
Implementation:
Abstract Factory:
Builder method: separate
the construction of a complex object from its representation, so the same
construction method can create different representations.
Prototype method: involves
creating new objects by copying existing objects. Copying objects for
experimentation in a graphics program or copying a template.
Observer: Defines one-to-many
dependency between objects so that when one object changes state all its
dependents are notified and updated automatically. Example update a group chat;
or weather monitoring system.
Strategy: Defines a family of
algorithms, encapsulates each one, and makes them interchangeable. Example choice of CSV, XML, JSON Exporter.
Decorator: Attaches additional
responsibilities to an object dynamically. Provide a flexible alternative to
subclassing for extending functionality.
Allows you to add new behaviours to an object without altering its
structure. Example is a coffee shop
application whereby a basic coffee can have milk, sugar and different options.
Composite: composes objects into
tree objects to represent part-whole hierarchies, allowing clients to treat
individual objects and compositions of objects uniformly. Example: graphic design, a complex image
could be represented by composite shapes.
Altering one would alter the entire image.
Façade: Provides a simplified
interface to a set of interfaces in a subsystem. Abstracts the implementation. Example is a calculate discount method
whereby a user’s id just needs to be passed and the façade pattern will take
care of the rest. In a multimedia application, a Facade pattern could be
implemented to provide a simplified interface for playing different types of
media files (audio, video) by abstracting the complexities of codec selection,
file loading, and playback control.
Proxy: Solves access control
issues. Provides a surrogate or placeholder for another object to control
access to it, often used for implementing lazy loading, access control, or
logging.
Bridge: Decouples an abstraction
from its implementation so the two can vary independently. Example is a menu
coupon being applied to a menu.
Visitor: Defines a new operation
without changing the classes of the elements on which it operates. Counting different types of documents.
Iterator: A way to aggregate
object sequentially without exposing its underlying representation. Going
through a collection of people or books.
State: Allow an object to alter its
behaviour when its internal state changes.
Changing the state of a bank account which will prevent money being
withdrawn.
Mediator: Centralises
communication between objects. Forces objects to communicate via the
mediator. Useful in a chat application,
managing who can chat with who.
Interpreter: defines a grammar for
interpreting a language and provides an interpreter to evaluate sentences in
that language. Converting a number to roman numerals.
Memento: captures and
externalizes an object's internal state, allowing the object to be restored to
this state later. Example: text editor,
being able to undo.
Command: encapsulates a request
as an object, allowing parameterization of clients with different requests,
queuing of requests, and the support of undoable operations. Example: the Command pattern could be
implemented where each button press corresponds to a command object (e.g., turn
on TV, increase volume), enabling easy customization of remote controls and
supporting undo functionality.
Template: defines the skeleton of
an algorithm in the superclass but let’s subclasses override specific steps of
the algorithm without changing its structure.
Example different ways of parsing an email.
Flyweight: minimizes memory usage
or computational expenses by sharing as much as possible with related objects. In
a text editor, the Flyweight pattern could be implemented to share common font
and formatting information (such as style and size) among multiple characters,
reducing the overall memory footprint for a large document.
Adaptor or Wrapper: convert
the interface of a class into another interface client expect. Adapter lets
classes work together that couldn't otherwise because of incompatible
interfaces. Example is converting an
external city type to an internal city type.
Chain of responsibility: passes
a request along a chain of handlers, where each handler decides either to
process the request or to pass it to the next handler in the chain. In a
workflow system, the Chain of Responsibility pattern could be implemented where
each handler represents a step in the workflow, deciding whether to handle a
task or pass it to the next step, allowing for flexible and dynamic task
processing.
Dependency Injection (DI): Addresses issue of how
components or classes obtain their dependencies. Uses Inversion of Control (IoC), whereby
control over object creation is inverted to a separate component. Removes hard coded dependencies between
components. Dependencies are abstracted away.
Makes it more reusable. Improves
quality of code.
Monolithic and microservices
Memory Management
Garbage collection is an automatic process in C#. It uses a generational approach of
Generation 0 (the most recent and short lived objects) being the first
generation where most objects are deposed of.
If an object is not deposed from Gen 0 it is promoted to Gen 1 and then
Gen 2. These have less frequent and
slower disposal processes and are for naturally longer living objects. This all occurs on the heap memory.
1) Memory
leaks
Memory leaks occur when
objects are no longer needed, but are not properly released, preventing garbage
collection.
·
Dispose unmanaged resources
(file handles, database connections) by using the using statement.
·
Remove event handlers to
prevent “linger references” that keep objects alive
·
Use memory profilers.
2) Excessive
memory consumption
Applications may consume more
memory than is necessary.
·
Minimise object creation:
reuse objects, use object pools, avoid temporary objects
·
Monitor memory usage
·
Use lightweight data
structures when dealing with large datasets
3) Large
Object Heap Fragmentation
LOH is a special area for objects greater than 85,000 bytes. Fragmentation in LOH can lead to performance
issues.
4) Reference
holding
Objects may hold references to other objects longer than necessary preventing
them from being collected. Use
WeakReference<T> for non-essential references to allow objects be
collected.
5) Inefficient
data structures
Poorly chosen data structures
can result in excessive memory usage.
Choose appropriate data structures, such as List<T> or
LinkedList<T>.
Memory efficient collections:
HashSet<T> for uniqueness or Dictionary for key value storage.
6) Large
number of objects in generations
If too many objects are in
generation 1 and 2, then garbage collection can become slower.
·
Reduce the number of long
lived objects to minimize unnecessary object promotion.
·
Implement proper object
disposal.
7) Excessive
finalisation
Heavy use of finalisers can
delay object reclaimation. Use
IDisposable.
8) Pinning
Pinning objects (using
GCHandle) can restrict the garbage collector’s ability to move objects.
Threads and Processes
Process
A process is an independent program that runs within its
own memory space. Each process is
isolated from other processes.
Mostly there is one process per application. Some applications, mainly server applications
may consist of multiple processes. These
process can communicate to each other through IPC (inter process communication)
mechanisms such as sockets or pipes.
Thread
A thread is the smallest unit of execution within a
process. Multiple threads within the same process share the same memory space
and resources.
Thread Class: allows you to create and manage threads
within a process.
Thread Pool: manages a pool of worker threads. Use to queue and execute tasks concurrently
without manually creating and managing threads.
Async/Await: Write asynchronous code without managing
threads. Await completion of
asynchronous tasks without blocking the main thread. Good for I/O tasks rather than CPU tasks.
Thread Safety: Thread safety mechanisms include locks,
mutexes, semaphones, concurrent queue and concurrent dictionary. These help protect shared resources from
concurrent access by multiple threads.
·
Locks: Ensure only on thread
can execute a specific block of code at a time.
Use with small amount of code and sparingly or you might create
deadlocks. . lock (balanceLock)
·
Mutex (Mutual exclusion):
Similar to a lock. Ensures that only one
thread can access a protected resource at a time. mut.WaitOne();
·
Semaphores: Control access to
a resource based on the number of available permits, example limiting the number
of concurrent connections to a database.
_pool = new Semaphore(initialCount: 0, maximumCount: 3);
·
Concurrent data structures: Specialized
collections, example concurrent dictionary, with built in thread safety
mechanisms like adding, removing and updating elements. Use dictionary when you need to share data
among multiple threads safely and efficiently.
Particularly useful where multiple threads need to access shared data
without explicit locks.
Linked List is better when
there are:
·
Frequent insertions and
deletions, especially at the beginning of the list
·
No random access and more
memory efficiency for smaller elements.
·
Iterating forward and
backwards
However they have
·
Higher overhead
·
Slower random access
Linked List Node
Fundamental to linked
list. Each stores data and a reference
to the next node (and the previous node in doubly linked list).
Linked List Node within a
concurrent dictionary
Provides efficient removal of
items during a concurrent operation.
Useful when you need to perform operations like removing specific
items.
Doesn’t need to search for the
item. Instead, you can quickly locate
the LinkedListNode associated with the key and use it for removal.
A linkedlist data structure
typically maintains the order in which elements were added. By associating a LinkedListNode with each key-value pair and
adding these nodes to a linked list, you can preserve the insertion order of items
in the concurrent dictionary.
When multiple threads are
concurrently accessing or modifying a dictionary the LinkedListNode can serve
as a way to track and manage the order of items safely and easily.
IDisposable
If Singleton class holds
resources that require cleanup.
Concurrent Dictionary
Thread safe: internal locks
and find grained synchronisation to ensure safe concurrent access
Concurrent operations: Can
perform Add, remove, update simultaneously without blocking each other. Provides TryAdd, TryRemove, TryUpdate
Better performance in
concurrent operations
Dictionary:
Not thread safe, single
threaded access.
Mock tests
Isolation of code: test code
in isolation of its dependencies
Faster
TDD
Attributes
[Obsolete("This method is
deprecated. Use the NewMethod instead.")]
static void OldMethod()
{
Console.WriteLine("This is the old
method.");
}
Dynamic variable is one where
you don’t know the type of the variable at run time.
Events
public class ItemRemoval : EventArgs {
public string Key { get; }
public string Type { get; }
public ItemRemoval(string key, string type)
{
Key = key;
Type =
type;
}
}
public event EventHandler<ItemRemoval>? OnRemoval;
protected
void RemoveItem() {
OnRemoval.Invoke(this, new
ItemRemoval(cacheItem.CacheKey, cacheItem.CacheType));
}
Subscribed
to event
var cache = ButlerCache.Instance;
cache.OnRemoval
+= ItemEvictionEvent;
IQueryable
You
should use IQueryable in C# when dealing with data sources that support query
composition, such as databases. IQueryable extends IEnumerable and allows you
to build queries that are executed on the server side (e.g., in a database)
rather than on the client side.
Using
IQueryable can lead to more efficient queries because it enables deferred
execution. This means that the query is not executed immediately when it's
defined but is instead executed when the data is actually needed. This is
particularly beneficial when working with large datasets.
Here's
a quick example:
//
Using IEnumerable (client-side execution)
IEnumerable<int>
numbers = GetNumbers();
var
result = numbers.Where(x => x > 5).ToList(); // Filter applied on the
client side
//
Using IQueryable (deferred execution)
IQueryable<int>
numbersQuery = GetNumbers().AsQueryable();
var
resultQuery = numbersQuery.Where(x => x > 5).ToList(); // Query is
translated and executed on the server side
IQueryable
is commonly used in LINQ to SQL or LINQ to Entities scenarios, where the query
can be translated into a database-specific query and executed efficiently on
the database server.
IValidateObject
Automatically
validates an object
public
class ValidateMe : IValidatableObject
{
[Required]
public bool Enable { get; set; }
[Range(1, 5)]
public int Prop1 { get; set; }
[Range(1, 5)]
public int Prop2 { get; set; }
public IEnumerable<ValidationResult>
Validate(ValidationContext validationContext)
{
var results = new
List<ValidationResult>();
if (this.Enable)
{
Validator.TryValidateProperty(this.Prop1,
new ValidationContext(this,
null, null) { MemberName = "Prop1" },
results);
Validator.TryValidateProperty(this.Prop2,
new ValidationContext(this,
null, null) { MemberName = "Prop2" },
results);
// some other random test
if (this.Prop1 > this.Prop2)
{
results.Add(new
ValidationResult("Prop1 must be larger than Prop2"));
}
}
return results;
}
}
public void DoValidation() { var toValidate = new ValidateMe() { Enable = true, Prop1 = 1, Prop2 = 2 }; bool validateAllProperties = false; var results = new List<ValidationResult>(); bool isValid = Validator.TryValidateObject( toValidate, new ValidationContext(toValidate, null, null), results, validateAllProperties);}
String
Builder
Strings
are immutable. Each concatenation creates a whole new string. StringBuilder is mutable.
Using
`StringBuilder` in C# offers performance benefits when you need to concatenate
multiple strings, especially within loops or when dealing with large amounts of
text. The key advantages include:
1.
**Mutable and Efficient Concatenation:** Strings in C# are immutable, meaning
each concatenation operation creates a new string object. This can lead to
performance issues, especially with frequent concatenation. `StringBuilder`,
being mutable, allows you to efficiently modify the content without creating
new instances.
2.
**Reduced Memory Overhead:** Since `StringBuilder` allows you to modify the
existing buffer, it avoids the overhead of creating multiple string objects.
This can be crucial when dealing with large datasets, as it helps reduce memory
usage.
3.
**Better Performance in Loops:** When concatenating strings inside loops, using
`StringBuilder` is significantly more efficient than using the `+` operator for
concatenation. The latter creates a new string in each iteration, leading to
poor performance and increased memory usage.
Here's
a simple example to illustrate the difference:
```csharp
//
Using StringBuilder
StringBuilder
sb = new StringBuilder();
for
(int i = 0; i < 10000; i++)
{
sb.Append(i.ToString());
}
string
result = sb.ToString();
//
Using string concatenation
string
resultConcat = "";
for
(int i = 0; i < 10000; i++)
{
resultConcat += i.ToString();
}
```
In
the example above, the version with `StringBuilder` is more efficient and
consumes less memory compared to the string concatenation version.
In
summary, `StringBuilder` is particularly useful when you need to build or
modify strings dynamically, especially in scenarios where performance and
memory efficiency are important.
Event v Delegate
·
Delegate can be passed as a
method parameter.
·
Cannot override a delegate
·
Can override an Event
·
Event declared inside a class,
while a delegate is declared outside a class.
·
Covariance and contravariance
provide extra flexibility to delegate objects.
This means they declaration of parameters and return types can be more
or less derived than originally specified. Declare a object but then pass in an
string (covariance, string is more derived than an object) or declare a string
and pass an object (contravariance, object is less derived than a string).
Delegates
Holds a reference to a
method. A way of telling which method to
call when an event is triggered.
Enables you to pass methods as
parameters, create event handlers and define callback functions.
Lambdas
Inline methods
regular method:
int Multiply(int a, int b) {
return a * b;
}
Lambda:
Func<int, int, int>
multiply = (a, b) => a * b;
Anonymous methods
Define a method without naming
it. Essentially replaced by Lambdas and
now used to create an instance of a delegate type. You would only use an anonymous method over a
lambda if you wanted to be explicit about the parameter and return types.
Yield: Used to minimise memory
usage. Used with an iterator method.
Iterate over a whole collection without having to load the whole
collection into memory.
Deferred execution: is the
postponement of the execution of a sequence of operations until they are
explicitly requested. LINQ queries
return IEnumerable and execution happens when they are converted to ToList. Also Lazy<T> class defers instantiation
of objects until requested.
Lazy loader example – only
created when it is actually needed.
public class ExpensiveObject
{
public ExpensiveObject()
{
Console.WriteLine("ExpensiveObject
is being created");
}
public void PerformSomeOperation()
{
Console.WriteLine("Performing some
operation on ExpensiveObject");
}
}
public class LazyLoaderExample
{
private Lazy<ExpensiveObject>
lazyExpensiveObject = new Lazy<ExpensiveObject>();
public ExpensiveObject GetExpensiveObject()
{
return lazyExpensiveObject.Value;
}
}
class Program
{
static void Main()
{
LazyLoaderExample example = new
LazyLoaderExample();
// ExpensiveObject is not created until
GetExpensiveObject is called
ExpensiveObject expensiveObject =
example.GetExpensiveObject();
// Now the ExpensiveObject is created
expensiveObject.PerformSomeOperation();
}
}
Differences between an array
and a list
Arrays have a lower memory
overhead to lists. So if you don’t need
the convenience of dynamic resizing or convenience of adding and removing
offered by lists then it’s better to use an array. Mutability is more awkward with an array.
1) Fixed
Size vs. Dynamic Size:
Array: Arrays have a fixed
size, meaning once you define the size of an array, you cannot change it.
List: Lists, on the other
hand, can dynamically resize themselves. You can add or remove elements from a
list without specifying the size beforehand.
2) Mutability:
Array: Elements in an array
cannot be easily added or removed once the array is created. You need to create
a new array with a different size if you want to change the number of elements.
List: Lists are more flexible.
You can easily add, remove, or insert elements without creating a new list.
3) Performance:
Array: Arrays may have
slightly better performance in certain scenarios since they are more
lightweight and have a fixed size.
List: Lists provide more
convenience and flexibility but may have a bit of overhead due to their dynamic
resizing capability.
4) Initialization:
Array: Arrays can be
initialized using array initializer syntax: int[] myArray = {1, 2, 3};
List: Lists are typically
initialized using the List<T> constructor or the Add method:
List<int> myList = new List<int> {1, 2, 3};
5) Methods
and Properties:
Array: Arrays have a fixed set
of methods and properties defined in the Array class.
List: Lists provide a more
extensive set of methods and properties, such as Add, Remove, Count, etc., in
the List<T> class.
6) Memory
Allocation:
Array: Arrays are allocated in
a single contiguous block of memory.
List: Lists are implemented
using an array internally, but they may need to allocate a new array and copy
elements when resizing.
Potential questions:
Name some weaknesses and
improvements:
- Regular expressions are a
weakness.
Questions:
What is your process and how
would I fit into that?
How do you foster
collaboration and a culture of camaraderie?
How do you ensure that highest
standards of professional practice are obtained?
What challenges or exciting
projects do you have in the next six months?
How do you support ongoing
professional development?
var results = someCollection.AsParallel().Where(item => item.SomeCondition).ToList();
Task<int> task1 = Task.Run(() => SomeMethod());
Task<int> task2 = Task.Run(() => SomeOtherMethod());
Task.WaitAll(task1, task2);
Comments
Post a Comment